oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "reflect"
5 "testing"
6)
7
8// TestExtractEnvFlag covers the argv parser that alone decides the
9// security-relevant Env value (fix round 2, finding 2: cmd/docs had
10// no tests at all, so this parser was covered by neither go test nor
11// the gate). rastrillo.Resolve owns os.Args and errors on anything it
12// does not define, so getting the strip-before-Resolve behavior right
13// matters as much as getting the value right.
14func TestExtractEnvFlag(t *testing.T) {
15 cases := []struct {
16 name string
17 args []string
18 wantEnv string
19 wantRest []string
20 }{
21 {
22 name: "flag present, set to development",
23 args: []string{"-env", "development", "-addr", ":8080"},
24 wantEnv: "development",
25 wantRest: []string{"-addr", ":8080"},
26 },
27 {
28 name: "flag absent",
29 args: []string{"-addr", ":8080", "-db", "docs.db"},
30 wantEnv: "",
31 wantRest: []string{"-addr", ":8080", "-db", "docs.db"},
32 },
33 {
34 name: "equals form",
35 args: []string{"-env=development"},
36 wantEnv: "development",
37 wantRest: nil,
38 },
39 {
40 name: "double-dash equals form",
41 args: []string{"--env=production"},
42 wantEnv: "production",
43 wantRest: nil,
44 },
45 {
46 // A trailing bare -env with no value: extractEnvFlag must
47 // not treat the flag as if it had been given a value (and
48 // must not panic reading past the end of args). The
49 // reviewer confirmed this lands on non-development
50 // downstream (via chooseEnv, tested below) — this case
51 // pins down why: an empty env here, not a crash or a
52 // swallowed neighbor.
53 name: "trailing bare -env, no value",
54 args: []string{"-addr", ":8080", "-env"},
55 wantEnv: "",
56 wantRest: []string{"-addr", ":8080"},
57 },
58 {
59 // -env greedily takes the very next token as its value,
60 // so "-env -addr :8080" swallows "-addr" as the env
61 // string rather than leaving it for rastrillo.Resolve.
62 // This is the other case the reviewer flagged as
63 // "currently lands on non-development... but untested":
64 // pinned down here at the parser level (env == "-addr",
65 // definitely not "development"), and again below via
66 // chooseEnv, which is what actually decides
67 // docs.Config.Env from it.
68 name: "-env with no value, next token is another flag",
69 args: []string{"-env", "-addr", ":8080"},
70 wantEnv: "-addr",
71 wantRest: []string{":8080"},
72 },
73 }
74 for _, c := range cases {
75 t.Run(c.name, func(t *testing.T) {
76 env, rest := extractEnvFlag(c.args)
77 if env != c.wantEnv {
78 t.Errorf("extractEnvFlag(%v) env = %q, want %q", c.args, env, c.wantEnv)
79 }
80 if !reflect.DeepEqual(rest, c.wantRest) {
81 t.Errorf("extractEnvFlag(%v) rest = %v, want %v", c.args, rest, c.wantRest)
82 }
83 })
84 }
85}
86
87// TestExtractSeedFlag covers -seed's own argv parser, the same
88// strip-before-Resolve reasoning as TestExtractEnvFlag above. Unlike
89// -env, -seed is a bare boolean with no value form at all (its own
90// doc comment explains why: which address to seed is
91// $DOCS_SEED_ADDRESS's job, not this flag's), so there is no
92// swallow-the-next-token case to pin down here.
93func TestExtractSeedFlag(t *testing.T) {
94 cases := []struct {
95 name string
96 args []string
97 wantSeed bool
98 wantRest []string
99 }{
100 {
101 name: "flag present",
102 args: []string{"-seed", "-env", "development"},
103 wantSeed: true,
104 wantRest: []string{"-env", "development"},
105 },
106 {
107 name: "double-dash form",
108 args: []string{"--seed"},
109 wantSeed: true,
110 wantRest: nil,
111 },
112 {
113 name: "flag absent",
114 args: []string{"-env", "development", "-addr", ":8080"},
115 wantSeed: false,
116 wantRest: []string{"-env", "development", "-addr", ":8080"},
117 },
118 {
119 name: "flag in the middle of other args",
120 args: []string{"-env", "development", "-seed", "-addr", ":8080"},
121 wantSeed: true,
122 wantRest: []string{"-env", "development", "-addr", ":8080"},
123 },
124 }
125 for _, c := range cases {
126 t.Run(c.name, func(t *testing.T) {
127 seed, rest := extractSeedFlag(c.args)
128 if seed != c.wantSeed {
129 t.Errorf("extractSeedFlag(%v) seed = %v, want %v", c.args, seed, c.wantSeed)
130 }
131 if !reflect.DeepEqual(rest, c.wantRest) {
132 t.Errorf("extractSeedFlag(%v) rest = %v, want %v", c.args, rest, c.wantRest)
133 }
134 })
135 }
136}
137
138// TestSeedGateRefusesWithoutDevelopment calls checkSeedGate itself —
139// main.go's own predicate, not a copy of its expression — so this
140// test fails if the real gate ever changes. Fix round 1 on Task 3:
141// the version this replaces re-implemented "seedFlag && env !=
142// development" inline and compared it to itself; inverting the real
143// if, reordering its operands or dropping a clause left it green.
144// This calls checkSeedGate directly, so none of that is possible —
145// see main.go's own doc comment on checkSeedGate for why this one
146// function is the entirety of -seed's safety story.
147func TestSeedGateRefusesWithoutDevelopment(t *testing.T) {
148 cases := []struct {
149 name string
150 seed bool
151 env string
152 wantErr bool
153 }{
154 {name: "seed with development env runs", seed: true, env: "development", wantErr: false},
155 {name: "seed with production env is refused", seed: true, env: "production", wantErr: true},
156 {name: "seed with empty env is refused", seed: true, env: "", wantErr: true},
157 {name: "seed with staging env is refused", seed: true, env: "staging", wantErr: true},
158 {name: "no seed flag never refuses regardless of env", seed: false, env: "production", wantErr: false},
159 }
160 for _, c := range cases {
161 t.Run(c.name, func(t *testing.T) {
162 err := checkSeedGate(c.seed, c.env)
163 if (err != nil) != c.wantErr {
164 t.Errorf("checkSeedGate(%v, %q) = %v, wantErr %v", c.seed, c.env, err, c.wantErr)
165 }
166 })
167 }
168}
169
170// TestChooseEnvHonoursDevelopmentOnlyFromTheFlag is fix round 2's own
171// regression test for finding 1: the coordinator's original
172// instruction added a $DOCS_ENV fallback that could set
173// Env=development, reinstating exactly the trap the deleted
174// -dev-auth flag existed to close (no env var may ever grant
175// development — only the literal flag). $DOCS_ENV=development with
176// no flag is the case that must NOT produce a development app.
177func TestChooseEnvHonoursDevelopmentOnlyFromTheFlag(t *testing.T) {
178 cases := []struct {
179 name string
180 flagEnv string
181 osEnv string
182 want string
183 }{
184 {
185 name: "flag present and set to development",
186 flagEnv: "development",
187 osEnv: "",
188 want: "development",
189 },
190 {
191 name: "absent: no flag, no env var",
192 flagEnv: "",
193 osEnv: "",
194 want: "production",
195 },
196 {
197 // The regression this whole fix round exists for:
198 // $DOCS_ENV=development with no -env flag must not select
199 // development. Platform instance config IS environment
200 // variables (DOCS_ORIGIN, DOCS_INSTANCE_KEY, DOCS_SMTP_*),
201 // so this is the one value $DOCS_ENV must never be
202 // trusted with.
203 name: "$DOCS_ENV=development with no flag",
204 flagEnv: "",
205 osEnv: "development",
206 want: "production",
207 },
208 {
209 // $DOCS_ENV is still useful for any OTHER value: nothing
210 // in docs.New treats a non-"development" Env specially,
211 // so there is no risk here to guard against.
212 name: "$DOCS_ENV set to something other than development",
213 flagEnv: "",
214 osEnv: "staging",
215 want: "staging",
216 },
217 {
218 name: "flag always wins over $DOCS_ENV, even the other way",
219 flagEnv: "development",
220 osEnv: "staging",
221 want: "development",
222 },
223 {
224 name: "explicit -env production overrides a stray $DOCS_ENV=development",
225 flagEnv: "production",
226 osEnv: "development",
227 want: "production",
228 },
229 }
230 for _, c := range cases {
231 t.Run(c.name, func(t *testing.T) {
232 got := chooseEnv(c.flagEnv, c.osEnv)
233 if got != c.want {
234 t.Errorf("chooseEnv(%q, %q) = %q, want %q", c.flagEnv, c.osEnv, got, c.want)
235 }
236 })
237 }
238}
239
240// TestArgvEdgeCasesFailTowardProduction composes extractEnvFlag and
241// chooseEnv exactly as main() does, for the two argv shapes the
242// reviewer named as "currently land on non-development... but
243// untested": a trailing bare -env, and -env immediately followed by
244// another flag (which extractEnvFlag swallows as -env's own value).
245// Both must resolve to something other than "development" — "it
246// happens to fail safe" is not a property until something asserts
247// it.
248func TestArgvEdgeCasesFailTowardProduction(t *testing.T) {
249 cases := []struct {
250 name string
251 args []string
252 }{
253 {name: "trailing bare -env", args: []string{"-addr", ":8080", "-env"}},
254 {name: "-env swallows the next flag as its value", args: []string{"-env", "-addr", ":8080"}},
255 }
256 for _, c := range cases {
257 t.Run(c.name, func(t *testing.T) {
258 flagEnv, _ := extractEnvFlag(c.args)
259 got := chooseEnv(flagEnv, "")
260 if got == "development" {
261 t.Errorf("args %v resolved to Env=development, want anything else (fails toward production)", c.args)
262 }
263 })
264 }
265}
266