carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package mail
2
3import (
4 "context"
5 "crypto/ecdh"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/appconfig"
11 "github.com/carlosframework/platform/internal/dnsrec"
12 "github.com/carlosframework/platform/internal/release"
13)
14
15// enableFixture seeds a declared app with one VERIFIED domain — the state
16// Enable expects to find.
17func enableFixture(t *testing.T) (context.Context, release.Store, *MemProvisioner, *ecdh.PublicKey, time.Time) {
18 t.Helper()
19 ctx := context.Background()
20 s, p := release.NewDirStore(t.TempDir()), NewMemProvisioner("1")
21 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
22
23 _, pubB64, err := appconfig.GenerateKey()
24 if err != nil {
25 t.Fatalf("GenerateKey: %v", err)
26 }
27 pub, err := appconfig.ParsePublicKey(pubB64, "test")
28 if err != nil {
29 t.Fatalf("ParsePublicKey: %v", err)
30 }
31
32 if _, err := Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP",
33 "noreply@harbourmail.bag.oncarlos.com", "owner@example.test", now); err != nil {
34 t.Fatalf("Declare: %v", err)
35 }
36 if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), testDeployment(),
37 "bag", "harbourmail", "harbourmail.bag.oncarlos.com", now); err != nil {
38 t.Fatalf("EnsureDomain: %v", err)
39 }
40 p.SetIdentityStatus("eu-west-1", "harbourmail.bag.oncarlos.com",
41 Status{DKIM: "SUCCESS", MailFrom: "SUCCESS", Sending: "SUCCESS"})
42 if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", "harbourmail.bag.oncarlos.com", "eu-west-1", now); err != nil {
43 t.Fatalf("VerifyDomain: %v", err)
44 }
45 return ctx, s, p, pub, now
46}
47
48// The v1 criterion in one test: the five variables harbourmail already
49// reads arrive, with the right values, and the app changes nothing.
50func TestEnableDeliversHarbourmailsEnv(t *testing.T) {
51 ctx, s, p, pub, now := enableFixture(t)
52 rec, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
53 if err != nil {
54 t.Fatalf("Enable: %v", err)
55 }
56 if rec.Provisioned == nil || rec.Provisioned.KeyID == "" {
57 t.Fatalf("nothing provisioned: %+v", rec)
58 }
59 man, err := appconfig.Get(ctx, s, "bag", "harbourmail")
60 if err != nil {
61 t.Fatalf("appconfig.Get: %v", err)
62 }
63 if got := man.Vars["HARBOURMAIL_SMTP_HOST"].Value; got != "email-smtp.eu-west-1.amazonaws.com" {
64 t.Fatalf("HOST = %q", got)
65 }
66 if got := man.Vars["HARBOURMAIL_SMTP_PORT"].Value; got != "587" {
67 t.Fatalf("PORT = %q", got)
68 }
69 if got := man.Vars["HARBOURMAIL_SMTP_FROM"].Value; got != "noreply@harbourmail.bag.oncarlos.com" {
70 t.Fatalf("FROM = %q", got)
71 }
72 // The username is the access key id and is NOT a secret — it is
73 // delivered as a plain env var, unsealed, precisely so it can be
74 // compared against rec.Provisioned.KeyID as a staleness check. The
75 // password IS the secret and must arrive sealed.
76 if got := man.Vars["HARBOURMAIL_SMTP_USER"]; got.Kind != "env" || got.Value != rec.Provisioned.KeyID {
77 t.Fatalf("USER = %+v, want Kind=env Value=%q", got, rec.Provisioned.KeyID)
78 }
79 if man.Vars["HARBOURMAIL_SMTP_PASS"].Kind != "secret" {
80 t.Fatal("PASS was not delivered as a sealed secret")
81 }
82 if man.Vars["HARBOURMAIL_SMTP_PASS"].Envelope == "" {
83 t.Fatal("PASS has no envelope")
84 }
85}
86
87// The delivered password is the DERIVED one, not the raw IAM secret. This
88// is the test that catches delivering the secret access key by mistake —
89// which would authenticate against nothing and leak a real AWS credential
90// into an app's environment.
91//
92// This opens the actual delivered envelope rather than just re-deriving the
93// expected value from the fake's plaintext: a version that only checked
94// "the derivation function returns something different from its input"
95// would pass even if Enable delivered the raw secret, because it never
96// looks at what was actually sealed and sent.
97func TestEnableDeliversTheDerivedPasswordNotTheSecret(t *testing.T) {
98 ctx, s, p, _, now := enableFixture(t)
99 privB64, pubB64, err := appconfig.GenerateKey()
100 if err != nil {
101 t.Fatalf("GenerateKey: %v", err)
102 }
103 priv, err := appconfig.ParsePrivateKey(privB64, "test")
104 if err != nil {
105 t.Fatalf("ParsePrivateKey: %v", err)
106 }
107 ownPub, err := appconfig.ParsePublicKey(pubB64, "test")
108 if err != nil {
109 t.Fatalf("ParsePublicKey: %v", err)
110 }
111
112 rec, err := Enable(ctx, s, p, ownPub, testDeployment(), "bag", "harbourmail", now)
113 if err != nil {
114 t.Fatalf("Enable: %v", err)
115 }
116 raw := p.Users[UserName("bag", "harbourmail")].Keys[rec.Provisioned.KeyID].Secret
117 want := SMTPPassword(raw, "eu-west-1")
118 if want == raw {
119 t.Fatal("test is not meaningful — derivation returned the input")
120 }
121
122 man, err := appconfig.Get(ctx, s, "bag", "harbourmail")
123 if err != nil {
124 t.Fatalf("appconfig.Get: %v", err)
125 }
126 env := man.Vars["HARBOURMAIL_SMTP_PASS"].Envelope
127 if env == "" {
128 t.Fatal("PASS has no envelope")
129 }
130 plain, err := appconfig.Open(priv, appconfig.Binding{Account: "bag", App: "harbourmail"}, env)
131 if err != nil {
132 t.Fatalf("Open: %v", err)
133 }
134 if string(plain) != want {
135 t.Fatalf("delivered password = %q, want the derived password %q", plain, want)
136 }
137 if string(plain) == raw {
138 t.Fatal("Enable delivered the raw IAM secret instead of the derived SMTP password")
139 }
140}
141
142// A healthy provisioned app makes a re-run a no-op: no fresh key, no
143// rewrite. Minting on every pass would burn IAM's two-key ceiling and
144// churn every instance's config.
145// TestEnsureDomainRefreshesPoliciesWhenItBindsAConfigSet is the trap that
146// shipped in v0.12.0 and that #268's own advice walks a member straight into.
147//
148// EnsureDomain binds the identity to a configuration set. EnsureUser — the
149// only place a credential's policy is written — runs in Enable and
150// CreateStandalone, NOT here. So for an app enabled BEFORE a config set
151// existed (every app on a deployment that arms SNS later), `carlos email
152// domains add` bound the set and left the policy naming only the identity,
153// which SES answers with a 554 on every send. That is the #271 shape
154// arriving through the repair path instead of the provisioning one, and it
155// is strictly worse than the drift it was meant to fix: silent-no-events
156// becomes silent-no-sending.
157//
158// The fix is that binding and granting happen together. This test pins the
159// pairing rather than either half.
160func TestEnsureDomainRefreshesPoliciesWhenItBindsAConfigSet(t *testing.T) {
161 ctx := context.Background()
162 s, p := release.NewDirStore(t.TempDir()), NewMemProvisioner("1")
163 now := time.Date(2026, 8, 24, 12, 0, 0, 0, time.UTC)
164
165 _, pubB64, err := appconfig.GenerateKey()
166 if err != nil {
167 t.Fatalf("GenerateKey: %v", err)
168 }
169 pub, err := appconfig.ParsePublicKey(pubB64, "test")
170 if err != nil {
171 t.Fatalf("ParsePublicKey: %v", err)
172 }
173
174 // A deployment with NO SNS topic: the domain verifies and the app is
175 // enabled with no configuration set anywhere — the ordinary shape
176 // before event ingest is armed.
177 noSNS := Deployment{Region: "eu-west-1", PlatformSuffix: "oncarlos.com"}
178 if _, err := Declare(ctx, s, "bag", "seapointish", "SEAPOINTISH_SMTP",
179 "noreply@seapointish.bag.oncarlos.com", "paul@rslw.com", now); err != nil {
180 t.Fatalf("Declare: %v", err)
181 }
182 if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), noSNS,
183 "bag", "seapointish", "seapointish.bag.oncarlos.com", now); err != nil {
184 t.Fatalf("EnsureDomain: %v", err)
185 }
186 p.SetIdentityStatus("eu-west-1", "seapointish.bag.oncarlos.com",
187 Status{DKIM: "SUCCESS", MailFrom: "SUCCESS", Sending: "SUCCESS"})
188 if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", "seapointish.bag.oncarlos.com", "eu-west-1", now); err != nil {
189 t.Fatalf("VerifyDomain: %v", err)
190 }
191 if _, err := Enable(ctx, s, p, pub, noSNS, "bag", "seapointish", now); err != nil {
192 t.Fatalf("Enable: %v", err)
193 }
194 if _, err := CreateStandalone(ctx, s, p, noSNS, "bag", "seapointish", "smoke", "paul@rslw.com", now); err != nil {
195 t.Fatalf("CreateStandalone: %v", err)
196 }
197
198 // Now the deployment arms SNS and the member follows the advice the
199 // status warning prints.
200 withSNS := testDeployment()
201 if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), withSNS,
202 "bag", "seapointish", "seapointish.bag.oncarlos.com", now); err != nil {
203 t.Fatalf("EnsureDomain (with SNS): %v", err)
204 }
205
206 dom, _, err := GetDomain(ctx, s, "bag", "seapointish.bag.oncarlos.com")
207 if err != nil {
208 t.Fatalf("GetDomain: %v", err)
209 }
210 want := dom.VerifiedConfigSetARNs()
211 if len(want) == 0 {
212 t.Fatal("no configuration set was bound, so this test proves nothing")
213 }
214 for _, u := range []string{UserName("bag", "seapointish"), StandaloneUserName("bag", "seapointish", "smoke")} {
215 mu, ok := p.Users[u]
216 if !ok {
217 t.Fatalf("user %s does not exist", u)
218 }
219 for _, arn := range want {
220 if !strings.Contains(mu.Policy, arn) {
221 t.Errorf("%s: policy does not grant %s after the set was bound\n"+
222 "binding without refreshing the policy is a 554 on every send — worse than the drift it repairs\npolicy = %s",
223 u, arn, mu.Policy)
224 }
225 }
226 }
227}
228
229// TestEnableGrantsTheConfigSetToTheDeliveredUser is the WALK half of issue
230// #271, and it exists because mutation testing showed the policy-level tests
231// could not see this: deleting the line in Enable that gathers config-set
232// ARNs left every test in this package green while reproducing the exact
233// outage — an identity bound to a configuration set, and a delivered
234// credential whose policy does not mention it, which SES refuses with a 554.
235//
236// The assertion is deliberately taken from the RECORD rather than from a
237// hardcoded name, and it fails when the record contributes none, so it
238// cannot pass vacuously the day EnsureDomain stops binding a set.
239func TestEnableGrantsTheConfigSetToTheDeliveredUser(t *testing.T) {
240 ctx, s, p, pub, now := enableFixture(t)
241 if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err != nil {
242 t.Fatalf("Enable: %v", err)
243 }
244 dom, _, err := GetDomain(ctx, s, "bag", "harbourmail.bag.oncarlos.com")
245 if err != nil {
246 t.Fatalf("GetDomain: %v", err)
247 }
248 want := dom.VerifiedConfigSetARNs()
249 if len(want) == 0 {
250 t.Fatal("the fixture bound no configuration set, so this test proves nothing — testDeployment() must carry an SNS topic")
251 }
252 policy := p.Users[UserName("bag", "harbourmail")].Policy
253 for _, arn := range want {
254 if !strings.Contains(policy, arn) {
255 t.Errorf("delivered user's policy does not grant %s\npolicy = %s\n"+
256 "SES authorises a configuration set as its own resource: without this every send is a 554", arn, policy)
257 }
258 }
259}
260
261// TestCreateStandaloneGrantsTheConfigSetToo: the standalone credential is a
262// SEPARATE IAM user with its own policy, minted by its own walk, so it can
263// regress independently of the delivered one.
264func TestCreateStandaloneGrantsTheConfigSetToo(t *testing.T) {
265 ctx, s, p, pub, now := enableFixture(t)
266 if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err != nil {
267 t.Fatalf("Enable: %v", err)
268 }
269 if _, err := CreateStandalone(ctx, s, p, testDeployment(), "bag", "harbourmail", "smoke", "owner@example.test", now); err != nil {
270 t.Fatalf("CreateStandalone: %v", err)
271 }
272 dom, _, err := GetDomain(ctx, s, "bag", "harbourmail.bag.oncarlos.com")
273 if err != nil {
274 t.Fatalf("GetDomain: %v", err)
275 }
276 want := dom.VerifiedConfigSetARNs()
277 if len(want) == 0 {
278 t.Fatal("the fixture bound no configuration set, so this test proves nothing")
279 }
280 policy := p.Users[StandaloneUserName("bag", "harbourmail", "smoke")].Policy
281 for _, arn := range want {
282 if !strings.Contains(policy, arn) {
283 t.Errorf("standalone user's policy does not grant %s\npolicy = %s", arn, policy)
284 }
285 }
286}
287
288func TestEnableIsIdempotent(t *testing.T) {
289 ctx, s, p, pub, now := enableFixture(t)
290 first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
291 if err != nil {
292 t.Fatalf("first Enable: %v", err)
293 }
294 second, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
295 if err != nil {
296 t.Fatalf("second Enable: %v", err)
297 }
298 if first.Provisioned.KeyID != second.Provisioned.KeyID {
299 t.Fatal("a re-run minted a fresh key")
300 }
301}
302
303// Enable must refuse an app with no VERIFIED domain. A credential with no
304// identity is a principal that can send nothing while looking provisioned.
305func TestEnableRefusesWithoutAVerifiedDomain(t *testing.T) {
306 ctx := context.Background()
307 s, p := release.NewDirStore(t.TempDir()), NewMemProvisioner("1")
308 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
309 _, pubB64, _ := appconfig.GenerateKey()
310 pub, _ := appconfig.ParsePublicKey(pubB64, "test")
311
312 Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", "n@x.com", "owner@example.test", now)
313 EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), testDeployment(),
314 "bag", "harbourmail", "harbourmail.bag.oncarlos.com", now) // left pending
315 if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err == nil {
316 t.Fatal("Enable succeeded with only a pending domain")
317 }
318}
319
320func TestEnableRefusesAnUndeclaredApp(t *testing.T) {
321 ctx := context.Background()
322 s, p := release.NewDirStore(t.TempDir()), NewMemProvisioner("1")
323 now := time.Now().UTC()
324 _, pubB64, _ := appconfig.GenerateKey()
325 pub, _ := appconfig.ParsePublicKey(pubB64, "test")
326 if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err != ErrNotDeclared {
327 t.Fatalf("Enable on an undeclared app = %v, want ErrNotDeclared", err)
328 }
329}
330
331// The From address must be at a granted domain. Otherwise the policy's
332// ses:FromAddress condition refuses every send at runtime — a failure that
333// surfaces as "email silently does not work" rather than as a refusal here.
334//
335// Declare itself accepts any From (a member may declare before any domain
336// is verified); Enable, which knows the granted domains, is what refuses
337// one outside every verified domain.
338func TestDeclareAcceptsAnyFromButEnableRefusesAnUngrantedOne(t *testing.T) {
339 ctx, s, p, pub, now := enableFixture(t)
340 if _, err := Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP",
341 "noreply@somewhere-else.com", "owner@example.test", now); err != nil {
342 t.Fatalf("Declare itself may accept any From: %v", err)
343 }
344 // but Enable, which knows the granted domains, must refuse it
345 if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err == nil {
346 t.Fatal("Enable accepted a From address outside every granted domain")
347 }
348}
349
350// A pause must survive a re-walk. Task 13 adds the pause verb
351// (Provisioned.Active = false); nothing writes it yet, but Enable must
352// already carry an existing Active value forward rather than hardcoding
353// true, or the pause — whose whole purpose is stopping one tenant's bounce
354// rate from putting the shared SES account under review — would be undone
355// silently the next time delivery needed repair.
356func TestEnableCarriesAPausedActiveForwardThroughARewalk(t *testing.T) {
357 ctx, s, p, pub, now := enableFixture(t)
358 if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err != nil {
359 t.Fatalf("first Enable: %v", err)
360 }
361
362 // Simulate a pause directly on the record — the way the pause verb
363 // (Task 13) will, once it exists.
364 got, etag, err := Get(ctx, s, "bag", "harbourmail")
365 if err != nil {
366 t.Fatalf("Get: %v", err)
367 }
368 got.Provisioned.Active = false
369 got.Provisioned.PauseKind = PauseKindCap
370 if _, err := Put(ctx, s, "bag", "harbourmail", got, etag); err != nil {
371 t.Fatalf("Put: %v", err)
372 }
373
374 // Remove a delivered var so the idempotence short-circuit cannot fire —
375 // this forces Enable to actually re-walk rather than return early.
376 if _, err := appconfig.Unset(ctx, s, "bag", "harbourmail", []string{"HARBOURMAIL_SMTP_HOST"}); err != nil {
377 t.Fatalf("Unset: %v", err)
378 }
379
380 second, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
381 if err != nil {
382 t.Fatalf("re-walk Enable: %v", err)
383 }
384 if second.Provisioned.Active {
385 t.Fatal("a re-walk silently resumed a paused credential")
386 }
387 // R1: PauseKind must survive the rewrite alongside Active — dropping it
388 // turns a CAP pause into a kind-less one, which nothing auto-resumes.
389 if second.Provisioned.PauseKind != PauseKindCap {
390 t.Fatalf("PauseKind = %q, want %q to survive the re-walk", second.Provisioned.PauseKind, PauseKindCap)
391 }
392}
393
394// If the record ever names a key that is not what was actually delivered
395// — e.g. a failed final Put after a successful mint+deliver, or hand
396// edited bucket data — EnvDelivered must catch it via the _USER value
397// comparison, and Enable must re-walk rather than short-circuit on a
398// disagreement that would otherwise be permanent.
399func TestEnvDeliveredAndEnableCatchAStaleRecordedKeyID(t *testing.T) {
400 ctx, s, p, pub, now := enableFixture(t)
401 first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
402 if err != nil {
403 t.Fatalf("first Enable: %v", err)
404 }
405 deliveredKeyID := first.Provisioned.KeyID
406
407 got, etag, err := Get(ctx, s, "bag", "harbourmail")
408 if err != nil {
409 t.Fatalf("Get: %v", err)
410 }
411 got.Provisioned.KeyID = "AKIASTALE00000000"
412 if _, err := Put(ctx, s, "bag", "harbourmail", got, etag); err != nil {
413 t.Fatalf("Put: %v", err)
414 }
415
416 missing, err := EnvDelivered(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", "AKIASTALE00000000", "")
417 if err != nil {
418 t.Fatalf("EnvDelivered: %v", err)
419 }
420 if !containsString(missing, "HARBOURMAIL_SMTP_USER") {
421 t.Fatalf("missing = %v, want HARBOURMAIL_SMTP_USER reported", missing)
422 }
423
424 second, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
425 if err != nil {
426 t.Fatalf("second Enable: %v", err)
427 }
428 if second.Provisioned.KeyID == "AKIASTALE00000000" {
429 t.Fatal("Enable short-circuited on a stale recorded key id instead of re-walking")
430 }
431 if second.Provisioned.KeyID == deliveredKeyID {
432 t.Fatal("re-walk should mint a fresh key, not silently reuse the originally delivered one")
433 }
434}
435
436// C2 (review follow-up): the idempotence short-circuit used to return
437// BEFORE EnsureUser — the only place the delivered credential's policy is
438// written — so a second domain added and verified after the first enable
439// never reached the policy on a plain re-`enable`. Spec §4 states the
440// opposite explicitly: "attaching a custom domain adds an ARN and rewrites
441// the policy without rotating a credential."
442func TestEnableRewritesThePolicyWhenASecondDomainIsAdded(t *testing.T) {
443 ctx, s, p, pub, now := enableFixture(t)
444 first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
445 if err != nil {
446 t.Fatalf("first Enable: %v", err)
447 }
448 firstKeyID := first.Provisioned.KeyID
449
450 second := "second.bag.oncarlos.com"
451 if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), testDeployment(),
452 "bag", "harbourmail", second, now); err != nil {
453 t.Fatalf("EnsureDomain second: %v", err)
454 }
455 p.SetIdentityStatus("eu-west-1", second, Status{DKIM: "SUCCESS", MailFrom: "SUCCESS", Sending: "SUCCESS"})
456 if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", second, "eu-west-1", now); err != nil {
457 t.Fatalf("VerifyDomain second: %v", err)
458 }
459
460 rewalked, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
461 if err != nil {
462 t.Fatalf("re-enable: %v", err)
463 }
464 if rewalked.Provisioned.KeyID != firstKeyID {
465 t.Fatal("re-enable minted a fresh key — the short-circuit must still govern the expensive half")
466 }
467 policy := p.Users[UserName("bag", "harbourmail")].Policy
468 for _, want := range []string{
469 "harbourmail.bag.oncarlos.com", second, "*@harbourmail.bag.oncarlos.com", "*@" + second,
470 } {
471 if !strings.Contains(policy, want) {
472 t.Fatalf("policy after re-enable missing %q:\n%s", want, policy)
473 }
474 }
475}
476
477// The symmetric shrink case: a domain that WAS granted and verified stops
478// being verified (e.g. SES reports its DKIM/MAIL FROM failed), narrowing
479// grantedDomains on the NEXT Enable call. The surviving policy must not go
480// on naming the departed ARN and From pattern — the same defect C2 fixes,
481// approached from the other direction.
482func TestEnableRewritesThePolicyWhenADomainStopsVerifying(t *testing.T) {
483 ctx, s, p, pub, now := enableFixture(t)
484 second := "second.bag.oncarlos.com"
485 if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), testDeployment(),
486 "bag", "harbourmail", second, now); err != nil {
487 t.Fatalf("EnsureDomain second: %v", err)
488 }
489 p.SetIdentityStatus("eu-west-1", second, Status{DKIM: "SUCCESS", MailFrom: "SUCCESS", Sending: "SUCCESS"})
490 if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", second, "eu-west-1", now); err != nil {
491 t.Fatalf("VerifyDomain second: %v", err)
492 }
493
494 first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
495 if err != nil {
496 t.Fatalf("first Enable: %v", err)
497 }
498 policy := p.Users[UserName("bag", "harbourmail")].Policy
499 if !strings.Contains(policy, second) {
500 t.Fatalf("policy before the regression missing %q:\n%s", second, policy)
501 }
502
503 // second regresses: SES now reports it FAILED.
504 p.SetIdentityStatus("eu-west-1", second, Status{DKIM: "FAILED", MailFrom: "FAILED", Sending: "PENDING"})
505 if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", second, "eu-west-1", now); err != nil {
506 t.Fatalf("VerifyDomain regression: %v", err)
507 }
508
509 rewalked, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
510 if err != nil {
511 t.Fatalf("re-enable after regression: %v", err)
512 }
513 if rewalked.Provisioned.KeyID != first.Provisioned.KeyID {
514 t.Fatal("re-enable minted a fresh key — the short-circuit must still govern the expensive half")
515 }
516 policy = p.Users[UserName("bag", "harbourmail")].Policy
517 if strings.Contains(policy, second) {
518 t.Fatalf("policy after the regression still names the departed domain %q:\n%s", second, policy)
519 }
520 if !strings.Contains(policy, "harbourmail.bag.oncarlos.com") {
521 t.Fatalf("policy after the regression lost the SURVIVING domain too:\n%s", policy)
522 }
523}
524
525// I1 (review follow-up): the exact failure class the _USER staleness check
526// was added to catch, applied to the OTHER unsealed variable it was never
527// extended to. Declare freely overwrites Declared.From at any time (only
528// the env prefix is locked once delivered); a re-declare followed by a
529// re-enable must re-deliver the new From, not leave the stale one standing
530// forever because nothing ever compared _FROM's VALUE.
531func TestEnableRedeliversAChangedFromAddress(t *testing.T) {
532 ctx, s, p, pub, now := enableFixture(t)
533 first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
534 if err != nil {
535 t.Fatalf("first Enable: %v", err)
536 }
537
538 newFrom := "admin@harbourmail.bag.oncarlos.com"
539 if _, err := Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", newFrom, "owner@example.test", now); err != nil {
540 t.Fatalf("re-declare: %v", err)
541 }
542
543 missing, err := EnvDelivered(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", first.Provisioned.KeyID, newFrom)
544 if err != nil {
545 t.Fatalf("EnvDelivered: %v", err)
546 }
547 if !containsString(missing, "HARBOURMAIL_SMTP_FROM") {
548 t.Fatalf("missing = %v, want HARBOURMAIL_SMTP_FROM reported after a re-declare changed it", missing)
549 }
550
551 second, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now)
552 if err != nil {
553 t.Fatalf("re-enable: %v", err)
554 }
555 if second.Declared.From != newFrom {
556 t.Fatalf("record's Declared.From = %q, want %q", second.Declared.From, newFrom)
557 }
558 man, err := appconfig.Get(ctx, s, "bag", "harbourmail")
559 if err != nil {
560 t.Fatalf("appconfig.Get: %v", err)
561 }
562 fromVar, ok := man.Vars["HARBOURMAIL_SMTP_FROM"]
563 if !ok || fromVar.Value != newFrom {
564 t.Fatalf("delivered HARBOURMAIL_SMTP_FROM = %+v, want value %q", fromVar, newFrom)
565 }
566}
567