package mail import ( "context" "crypto/ecdh" "strings" "testing" "time" "github.com/carlosframework/platform/internal/appconfig" "github.com/carlosframework/platform/internal/dnsrec" "github.com/carlosframework/platform/internal/release" ) // enableFixture seeds a declared app with one VERIFIED domain — the state // Enable expects to find. func enableFixture(t *testing.T) (context.Context, release.Store, *MemProvisioner, *ecdh.PublicKey, time.Time) { t.Helper() ctx := context.Background() s, p := release.NewDirStore(t.TempDir()), NewMemProvisioner("1") now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC) _, pubB64, err := appconfig.GenerateKey() if err != nil { t.Fatalf("GenerateKey: %v", err) } pub, err := appconfig.ParsePublicKey(pubB64, "test") if err != nil { t.Fatalf("ParsePublicKey: %v", err) } if _, err := Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", "noreply@harbourmail.bag.oncarlos.com", "owner@example.test", now); err != nil { t.Fatalf("Declare: %v", err) } if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), testDeployment(), "bag", "harbourmail", "harbourmail.bag.oncarlos.com", now); err != nil { t.Fatalf("EnsureDomain: %v", err) } p.SetIdentityStatus("eu-west-1", "harbourmail.bag.oncarlos.com", Status{DKIM: "SUCCESS", MailFrom: "SUCCESS", Sending: "SUCCESS"}) if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", "harbourmail.bag.oncarlos.com", "eu-west-1", now); err != nil { t.Fatalf("VerifyDomain: %v", err) } return ctx, s, p, pub, now } // The v1 criterion in one test: the five variables harbourmail already // reads arrive, with the right values, and the app changes nothing. func TestEnableDeliversHarbourmailsEnv(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) rec, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("Enable: %v", err) } if rec.Provisioned == nil || rec.Provisioned.KeyID == "" { t.Fatalf("nothing provisioned: %+v", rec) } man, err := appconfig.Get(ctx, s, "bag", "harbourmail") if err != nil { t.Fatalf("appconfig.Get: %v", err) } if got := man.Vars["HARBOURMAIL_SMTP_HOST"].Value; got != "email-smtp.eu-west-1.amazonaws.com" { t.Fatalf("HOST = %q", got) } if got := man.Vars["HARBOURMAIL_SMTP_PORT"].Value; got != "587" { t.Fatalf("PORT = %q", got) } if got := man.Vars["HARBOURMAIL_SMTP_FROM"].Value; got != "noreply@harbourmail.bag.oncarlos.com" { t.Fatalf("FROM = %q", got) } // The username is the access key id and is NOT a secret — it is // delivered as a plain env var, unsealed, precisely so it can be // compared against rec.Provisioned.KeyID as a staleness check. The // password IS the secret and must arrive sealed. if got := man.Vars["HARBOURMAIL_SMTP_USER"]; got.Kind != "env" || got.Value != rec.Provisioned.KeyID { t.Fatalf("USER = %+v, want Kind=env Value=%q", got, rec.Provisioned.KeyID) } if man.Vars["HARBOURMAIL_SMTP_PASS"].Kind != "secret" { t.Fatal("PASS was not delivered as a sealed secret") } if man.Vars["HARBOURMAIL_SMTP_PASS"].Envelope == "" { t.Fatal("PASS has no envelope") } } // The delivered password is the DERIVED one, not the raw IAM secret. This // is the test that catches delivering the secret access key by mistake — // which would authenticate against nothing and leak a real AWS credential // into an app's environment. // // This opens the actual delivered envelope rather than just re-deriving the // expected value from the fake's plaintext: a version that only checked // "the derivation function returns something different from its input" // would pass even if Enable delivered the raw secret, because it never // looks at what was actually sealed and sent. func TestEnableDeliversTheDerivedPasswordNotTheSecret(t *testing.T) { ctx, s, p, _, now := enableFixture(t) privB64, pubB64, err := appconfig.GenerateKey() if err != nil { t.Fatalf("GenerateKey: %v", err) } priv, err := appconfig.ParsePrivateKey(privB64, "test") if err != nil { t.Fatalf("ParsePrivateKey: %v", err) } ownPub, err := appconfig.ParsePublicKey(pubB64, "test") if err != nil { t.Fatalf("ParsePublicKey: %v", err) } rec, err := Enable(ctx, s, p, ownPub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("Enable: %v", err) } raw := p.Users[UserName("bag", "harbourmail")].Keys[rec.Provisioned.KeyID].Secret want := SMTPPassword(raw, "eu-west-1") if want == raw { t.Fatal("test is not meaningful — derivation returned the input") } man, err := appconfig.Get(ctx, s, "bag", "harbourmail") if err != nil { t.Fatalf("appconfig.Get: %v", err) } env := man.Vars["HARBOURMAIL_SMTP_PASS"].Envelope if env == "" { t.Fatal("PASS has no envelope") } plain, err := appconfig.Open(priv, appconfig.Binding{Account: "bag", App: "harbourmail"}, env) if err != nil { t.Fatalf("Open: %v", err) } if string(plain) != want { t.Fatalf("delivered password = %q, want the derived password %q", plain, want) } if string(plain) == raw { t.Fatal("Enable delivered the raw IAM secret instead of the derived SMTP password") } } // A healthy provisioned app makes a re-run a no-op: no fresh key, no // rewrite. Minting on every pass would burn IAM's two-key ceiling and // churn every instance's config. // TestEnsureDomainRefreshesPoliciesWhenItBindsAConfigSet is the trap that // shipped in v0.12.0 and that #268's own advice walks a member straight into. // // EnsureDomain binds the identity to a configuration set. EnsureUser — the // only place a credential's policy is written — runs in Enable and // CreateStandalone, NOT here. So for an app enabled BEFORE a config set // existed (every app on a deployment that arms SNS later), `carlos email // domains add` bound the set and left the policy naming only the identity, // which SES answers with a 554 on every send. That is the #271 shape // arriving through the repair path instead of the provisioning one, and it // is strictly worse than the drift it was meant to fix: silent-no-events // becomes silent-no-sending. // // The fix is that binding and granting happen together. This test pins the // pairing rather than either half. func TestEnsureDomainRefreshesPoliciesWhenItBindsAConfigSet(t *testing.T) { ctx := context.Background() s, p := release.NewDirStore(t.TempDir()), NewMemProvisioner("1") now := time.Date(2026, 8, 24, 12, 0, 0, 0, time.UTC) _, pubB64, err := appconfig.GenerateKey() if err != nil { t.Fatalf("GenerateKey: %v", err) } pub, err := appconfig.ParsePublicKey(pubB64, "test") if err != nil { t.Fatalf("ParsePublicKey: %v", err) } // A deployment with NO SNS topic: the domain verifies and the app is // enabled with no configuration set anywhere — the ordinary shape // before event ingest is armed. noSNS := Deployment{Region: "eu-west-1", PlatformSuffix: "oncarlos.com"} if _, err := Declare(ctx, s, "bag", "seapointish", "SEAPOINTISH_SMTP", "noreply@seapointish.bag.oncarlos.com", "paul@rslw.com", now); err != nil { t.Fatalf("Declare: %v", err) } if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), noSNS, "bag", "seapointish", "seapointish.bag.oncarlos.com", now); err != nil { t.Fatalf("EnsureDomain: %v", err) } p.SetIdentityStatus("eu-west-1", "seapointish.bag.oncarlos.com", Status{DKIM: "SUCCESS", MailFrom: "SUCCESS", Sending: "SUCCESS"}) if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", "seapointish.bag.oncarlos.com", "eu-west-1", now); err != nil { t.Fatalf("VerifyDomain: %v", err) } if _, err := Enable(ctx, s, p, pub, noSNS, "bag", "seapointish", now); err != nil { t.Fatalf("Enable: %v", err) } if _, err := CreateStandalone(ctx, s, p, noSNS, "bag", "seapointish", "smoke", "paul@rslw.com", now); err != nil { t.Fatalf("CreateStandalone: %v", err) } // Now the deployment arms SNS and the member follows the advice the // status warning prints. withSNS := testDeployment() if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), withSNS, "bag", "seapointish", "seapointish.bag.oncarlos.com", now); err != nil { t.Fatalf("EnsureDomain (with SNS): %v", err) } dom, _, err := GetDomain(ctx, s, "bag", "seapointish.bag.oncarlos.com") if err != nil { t.Fatalf("GetDomain: %v", err) } want := dom.VerifiedConfigSetARNs() if len(want) == 0 { t.Fatal("no configuration set was bound, so this test proves nothing") } for _, u := range []string{UserName("bag", "seapointish"), StandaloneUserName("bag", "seapointish", "smoke")} { mu, ok := p.Users[u] if !ok { t.Fatalf("user %s does not exist", u) } for _, arn := range want { if !strings.Contains(mu.Policy, arn) { t.Errorf("%s: policy does not grant %s after the set was bound\n"+ "binding without refreshing the policy is a 554 on every send — worse than the drift it repairs\npolicy = %s", u, arn, mu.Policy) } } } } // TestEnableGrantsTheConfigSetToTheDeliveredUser is the WALK half of issue // #271, and it exists because mutation testing showed the policy-level tests // could not see this: deleting the line in Enable that gathers config-set // ARNs left every test in this package green while reproducing the exact // outage — an identity bound to a configuration set, and a delivered // credential whose policy does not mention it, which SES refuses with a 554. // // The assertion is deliberately taken from the RECORD rather than from a // hardcoded name, and it fails when the record contributes none, so it // cannot pass vacuously the day EnsureDomain stops binding a set. func TestEnableGrantsTheConfigSetToTheDeliveredUser(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err != nil { t.Fatalf("Enable: %v", err) } dom, _, err := GetDomain(ctx, s, "bag", "harbourmail.bag.oncarlos.com") if err != nil { t.Fatalf("GetDomain: %v", err) } want := dom.VerifiedConfigSetARNs() if len(want) == 0 { t.Fatal("the fixture bound no configuration set, so this test proves nothing — testDeployment() must carry an SNS topic") } policy := p.Users[UserName("bag", "harbourmail")].Policy for _, arn := range want { if !strings.Contains(policy, arn) { t.Errorf("delivered user's policy does not grant %s\npolicy = %s\n"+ "SES authorises a configuration set as its own resource: without this every send is a 554", arn, policy) } } } // TestCreateStandaloneGrantsTheConfigSetToo: the standalone credential is a // SEPARATE IAM user with its own policy, minted by its own walk, so it can // regress independently of the delivered one. func TestCreateStandaloneGrantsTheConfigSetToo(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err != nil { t.Fatalf("Enable: %v", err) } if _, err := CreateStandalone(ctx, s, p, testDeployment(), "bag", "harbourmail", "smoke", "owner@example.test", now); err != nil { t.Fatalf("CreateStandalone: %v", err) } dom, _, err := GetDomain(ctx, s, "bag", "harbourmail.bag.oncarlos.com") if err != nil { t.Fatalf("GetDomain: %v", err) } want := dom.VerifiedConfigSetARNs() if len(want) == 0 { t.Fatal("the fixture bound no configuration set, so this test proves nothing") } policy := p.Users[StandaloneUserName("bag", "harbourmail", "smoke")].Policy for _, arn := range want { if !strings.Contains(policy, arn) { t.Errorf("standalone user's policy does not grant %s\npolicy = %s", arn, policy) } } } func TestEnableIsIdempotent(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("first Enable: %v", err) } second, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("second Enable: %v", err) } if first.Provisioned.KeyID != second.Provisioned.KeyID { t.Fatal("a re-run minted a fresh key") } } // Enable must refuse an app with no VERIFIED domain. A credential with no // identity is a principal that can send nothing while looking provisioned. func TestEnableRefusesWithoutAVerifiedDomain(t *testing.T) { ctx := context.Background() s, p := release.NewDirStore(t.TempDir()), NewMemProvisioner("1") now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC) _, pubB64, _ := appconfig.GenerateKey() pub, _ := appconfig.ParsePublicKey(pubB64, "test") Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", "n@x.com", "owner@example.test", now) EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), testDeployment(), "bag", "harbourmail", "harbourmail.bag.oncarlos.com", now) // left pending if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err == nil { t.Fatal("Enable succeeded with only a pending domain") } } func TestEnableRefusesAnUndeclaredApp(t *testing.T) { ctx := context.Background() s, p := release.NewDirStore(t.TempDir()), NewMemProvisioner("1") now := time.Now().UTC() _, pubB64, _ := appconfig.GenerateKey() pub, _ := appconfig.ParsePublicKey(pubB64, "test") if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err != ErrNotDeclared { t.Fatalf("Enable on an undeclared app = %v, want ErrNotDeclared", err) } } // The From address must be at a granted domain. Otherwise the policy's // ses:FromAddress condition refuses every send at runtime — a failure that // surfaces as "email silently does not work" rather than as a refusal here. // // Declare itself accepts any From (a member may declare before any domain // is verified); Enable, which knows the granted domains, is what refuses // one outside every verified domain. func TestDeclareAcceptsAnyFromButEnableRefusesAnUngrantedOne(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) if _, err := Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", "noreply@somewhere-else.com", "owner@example.test", now); err != nil { t.Fatalf("Declare itself may accept any From: %v", err) } // but Enable, which knows the granted domains, must refuse it if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err == nil { t.Fatal("Enable accepted a From address outside every granted domain") } } // A pause must survive a re-walk. Task 13 adds the pause verb // (Provisioned.Active = false); nothing writes it yet, but Enable must // already carry an existing Active value forward rather than hardcoding // true, or the pause — whose whole purpose is stopping one tenant's bounce // rate from putting the shared SES account under review — would be undone // silently the next time delivery needed repair. func TestEnableCarriesAPausedActiveForwardThroughARewalk(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) if _, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now); err != nil { t.Fatalf("first Enable: %v", err) } // Simulate a pause directly on the record — the way the pause verb // (Task 13) will, once it exists. got, etag, err := Get(ctx, s, "bag", "harbourmail") if err != nil { t.Fatalf("Get: %v", err) } got.Provisioned.Active = false got.Provisioned.PauseKind = PauseKindCap if _, err := Put(ctx, s, "bag", "harbourmail", got, etag); err != nil { t.Fatalf("Put: %v", err) } // Remove a delivered var so the idempotence short-circuit cannot fire — // this forces Enable to actually re-walk rather than return early. if _, err := appconfig.Unset(ctx, s, "bag", "harbourmail", []string{"HARBOURMAIL_SMTP_HOST"}); err != nil { t.Fatalf("Unset: %v", err) } second, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("re-walk Enable: %v", err) } if second.Provisioned.Active { t.Fatal("a re-walk silently resumed a paused credential") } // R1: PauseKind must survive the rewrite alongside Active — dropping it // turns a CAP pause into a kind-less one, which nothing auto-resumes. if second.Provisioned.PauseKind != PauseKindCap { t.Fatalf("PauseKind = %q, want %q to survive the re-walk", second.Provisioned.PauseKind, PauseKindCap) } } // If the record ever names a key that is not what was actually delivered // — e.g. a failed final Put after a successful mint+deliver, or hand // edited bucket data — EnvDelivered must catch it via the _USER value // comparison, and Enable must re-walk rather than short-circuit on a // disagreement that would otherwise be permanent. func TestEnvDeliveredAndEnableCatchAStaleRecordedKeyID(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("first Enable: %v", err) } deliveredKeyID := first.Provisioned.KeyID got, etag, err := Get(ctx, s, "bag", "harbourmail") if err != nil { t.Fatalf("Get: %v", err) } got.Provisioned.KeyID = "AKIASTALE00000000" if _, err := Put(ctx, s, "bag", "harbourmail", got, etag); err != nil { t.Fatalf("Put: %v", err) } missing, err := EnvDelivered(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", "AKIASTALE00000000", "") if err != nil { t.Fatalf("EnvDelivered: %v", err) } if !containsString(missing, "HARBOURMAIL_SMTP_USER") { t.Fatalf("missing = %v, want HARBOURMAIL_SMTP_USER reported", missing) } second, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("second Enable: %v", err) } if second.Provisioned.KeyID == "AKIASTALE00000000" { t.Fatal("Enable short-circuited on a stale recorded key id instead of re-walking") } if second.Provisioned.KeyID == deliveredKeyID { t.Fatal("re-walk should mint a fresh key, not silently reuse the originally delivered one") } } // C2 (review follow-up): the idempotence short-circuit used to return // BEFORE EnsureUser — the only place the delivered credential's policy is // written — so a second domain added and verified after the first enable // never reached the policy on a plain re-`enable`. Spec §4 states the // opposite explicitly: "attaching a custom domain adds an ARN and rewrites // the policy without rotating a credential." func TestEnableRewritesThePolicyWhenASecondDomainIsAdded(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("first Enable: %v", err) } firstKeyID := first.Provisioned.KeyID second := "second.bag.oncarlos.com" if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), testDeployment(), "bag", "harbourmail", second, now); err != nil { t.Fatalf("EnsureDomain second: %v", err) } p.SetIdentityStatus("eu-west-1", second, Status{DKIM: "SUCCESS", MailFrom: "SUCCESS", Sending: "SUCCESS"}) if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", second, "eu-west-1", now); err != nil { t.Fatalf("VerifyDomain second: %v", err) } rewalked, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("re-enable: %v", err) } if rewalked.Provisioned.KeyID != firstKeyID { t.Fatal("re-enable minted a fresh key — the short-circuit must still govern the expensive half") } policy := p.Users[UserName("bag", "harbourmail")].Policy for _, want := range []string{ "harbourmail.bag.oncarlos.com", second, "*@harbourmail.bag.oncarlos.com", "*@" + second, } { if !strings.Contains(policy, want) { t.Fatalf("policy after re-enable missing %q:\n%s", want, policy) } } } // The symmetric shrink case: a domain that WAS granted and verified stops // being verified (e.g. SES reports its DKIM/MAIL FROM failed), narrowing // grantedDomains on the NEXT Enable call. The surviving policy must not go // on naming the departed ARN and From pattern — the same defect C2 fixes, // approached from the other direction. func TestEnableRewritesThePolicyWhenADomainStopsVerifying(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) second := "second.bag.oncarlos.com" if _, _, err := EnsureDomain(ctx, s, p, dnsrec.NewMemWriter(), testDeployment(), "bag", "harbourmail", second, now); err != nil { t.Fatalf("EnsureDomain second: %v", err) } p.SetIdentityStatus("eu-west-1", second, Status{DKIM: "SUCCESS", MailFrom: "SUCCESS", Sending: "SUCCESS"}) if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", second, "eu-west-1", now); err != nil { t.Fatalf("VerifyDomain second: %v", err) } first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("first Enable: %v", err) } policy := p.Users[UserName("bag", "harbourmail")].Policy if !strings.Contains(policy, second) { t.Fatalf("policy before the regression missing %q:\n%s", second, policy) } // second regresses: SES now reports it FAILED. p.SetIdentityStatus("eu-west-1", second, Status{DKIM: "FAILED", MailFrom: "FAILED", Sending: "PENDING"}) if _, err := VerifyDomain(ctx, s, p, testDeployment(), "bag", second, "eu-west-1", now); err != nil { t.Fatalf("VerifyDomain regression: %v", err) } rewalked, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("re-enable after regression: %v", err) } if rewalked.Provisioned.KeyID != first.Provisioned.KeyID { t.Fatal("re-enable minted a fresh key — the short-circuit must still govern the expensive half") } policy = p.Users[UserName("bag", "harbourmail")].Policy if strings.Contains(policy, second) { t.Fatalf("policy after the regression still names the departed domain %q:\n%s", second, policy) } if !strings.Contains(policy, "harbourmail.bag.oncarlos.com") { t.Fatalf("policy after the regression lost the SURVIVING domain too:\n%s", policy) } } // I1 (review follow-up): the exact failure class the _USER staleness check // was added to catch, applied to the OTHER unsealed variable it was never // extended to. Declare freely overwrites Declared.From at any time (only // the env prefix is locked once delivered); a re-declare followed by a // re-enable must re-deliver the new From, not leave the stale one standing // forever because nothing ever compared _FROM's VALUE. func TestEnableRedeliversAChangedFromAddress(t *testing.T) { ctx, s, p, pub, now := enableFixture(t) first, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("first Enable: %v", err) } newFrom := "admin@harbourmail.bag.oncarlos.com" if _, err := Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", newFrom, "owner@example.test", now); err != nil { t.Fatalf("re-declare: %v", err) } missing, err := EnvDelivered(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", first.Provisioned.KeyID, newFrom) if err != nil { t.Fatalf("EnvDelivered: %v", err) } if !containsString(missing, "HARBOURMAIL_SMTP_FROM") { t.Fatalf("missing = %v, want HARBOURMAIL_SMTP_FROM reported after a re-declare changed it", missing) } second, err := Enable(ctx, s, p, pub, testDeployment(), "bag", "harbourmail", now) if err != nil { t.Fatalf("re-enable: %v", err) } if second.Declared.From != newFrom { t.Fatalf("record's Declared.From = %q, want %q", second.Declared.From, newFrom) } man, err := appconfig.Get(ctx, s, "bag", "harbourmail") if err != nil { t.Fatalf("appconfig.Get: %v", err) } fromVar, ok := man.Vars["HARBOURMAIL_SMTP_FROM"] if !ok || fromVar.Value != newFrom { t.Fatalf("delivered HARBOURMAIL_SMTP_FROM = %+v, want value %q", fromVar, newFrom) } }