| index 61a2a0ab0..d36654fc0 100644 |
| --- a/internal/console/mailcounts_test.go |
| +++ b/internal/console/mailcounts_test.go |
| @@ -719,3 +719,111 @@ func TestResumeStaleCapPauseNeverTouchesAPerInstanceSender(t *testing.T) { |
| t.Fatal("a PerInstance sender's cap pause from yesterday was lifted") |
| } |
| } |
| + |
| +// mailAuditSubject is the naming rule directly: every app has exactly one |
| +// sender in this release (the app sender), so naming it would add "/app" to |
| +// a member-facing audit line for no information a member could use — and |
| +// that "/app" form never went through this repo's copy-review gate. Only a |
| +// sender that is NOT the app sender earns the "/sender" suffix. |
| +func TestMailAuditSubjectNamesOnlyANonAppSender(t *testing.T) { |
| + if got := mailAuditSubject("harbourmail", mail.SenderAppID); got != "harbourmail" { |
| + t.Fatalf("mailAuditSubject(app sender) = %q, want the bare app name", got) |
| + } |
| + if got := mailAuditSubject("harbourmail", "book.example.com"); got != "harbourmail/book.example.com" { |
| + t.Fatalf("mailAuditSubject(non-app sender) = %q, want app/sender", got) |
| + } |
| +} |
| + |
| +// The app sender's pause line must be byte-identical to what every live app |
| +// already prints today (pre-dating per-sender senders): "harbourmail: daily |
| +// cap 50 reached (50 sent)". Naming the sender here would be a member-facing |
| +// copy change this repo's copy-review gate never saw. |
| +func TestEnforceAuditDetailIsByteIdenticalForTheAppSender(t *testing.T) { |
| + fx, now := mailFixture(t) |
| + seedCounts(t, fx, now.UTC().Format("2006-01-02"), MailCounts{Sends: 50}) |
| + if _, _, err := fx.srv.enforceMailLimits(context.Background(), release.OpsAccount, "harbourmail", mail.SenderAppID, now); err != nil { |
| + t.Fatal(err) |
| + } |
| + |
| + entries, err := fx.st.Events(OpsAccountID, "harbourmail", 10) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + want := "harbourmail: daily cap 50 reached (50 sent)" |
| + for _, e := range entries { |
| + if e.Action == "mail.paused" { |
| + if e.Detail != want { |
| + t.Fatalf("pause detail = %q, want byte-identical %q", e.Detail, want) |
| + } |
| + return |
| + } |
| + } |
| + t.Fatalf("no mail.paused audit entry found: %+v", entries) |
| +} |
| + |
| +// A non-app sender's pause line DOES carry the sender, since "harbourmail" |
| +// alone would not tell a member which of two senders paused. |
| +func TestEnforceAuditDetailNamesANonAppSender(t *testing.T) { |
| + fx, now := mailFixture(t) |
| + ctx := context.Background() |
| + day := now.UTC().Format("2006-01-02") |
| + |
| + sibling := &mail.Sender{ |
| + ID: "book.example.com", Scope: mail.ScopeInstance, Source: mail.SourceDomain, |
| + Provisioned: &mail.Provisioned{IAMUser: "book-u", KeyID: "AKIA2", Active: true, At: now}, |
| + } |
| + if _, err := mail.PutSender(ctx, fx.rel, release.OpsAccount, "harbourmail", sibling, ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + for i := 0; i < 50; i++ { |
| + if err := fx.st.RecordMailEvent(ctx, release.OpsAccount, "harbourmail", "book.example.com", day, mail.EventSend); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + if _, _, err := fx.srv.enforceMailLimits(ctx, release.OpsAccount, "harbourmail", "book.example.com", now); err != nil { |
| + t.Fatal(err) |
| + } |
| + |
| + entries, err := fx.st.Events(OpsAccountID, "harbourmail", 10) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + want := "harbourmail/book.example.com: daily cap 50 reached (50 sent)" |
| + for _, e := range entries { |
| + if e.Action == "mail.paused" { |
| + if e.Detail != want { |
| + t.Fatalf("pause detail = %q, want %q", e.Detail, want) |
| + } |
| + return |
| + } |
| + } |
| + t.Fatalf("no mail.paused audit entry found: %+v", entries) |
| +} |
| + |
| +// The resume line follows the same rule: byte-identical for the app sender. |
| +func TestResumeAuditDetailIsByteIdenticalForTheAppSender(t *testing.T) { |
| + fx, now := mailFixture(t) |
| + pauseDirectly(t, fx, "harbourmail", mail.PauseKindCap, now.AddDate(0, 0, -1)) |
| + sn, _, err := mail.GetSender(context.Background(), fx.rel, release.OpsAccount, "harbourmail", mail.SenderAppID) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, resumed, err := fx.srv.resumeStaleCapPause(context.Background(), release.OpsAccount, "harbourmail", mail.SenderAppID, sn, now); err != nil || !resumed { |
| + t.Fatalf("resume = %v %v, want it to resume", resumed, err) |
| + } |
| + |
| + entries, err := fx.st.Events(OpsAccountID, "harbourmail", 10) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + want := "harbourmail: daily cap pause cleared on the UTC day roll" |
| + for _, e := range entries { |
| + if e.Action == "mail.resumed" { |
| + if e.Detail != want { |
| + t.Fatalf("resume detail = %q, want byte-identical %q", e.Detail, want) |
| + } |
| + return |
| + } |
| + } |
| + t.Fatalf("no mail.resumed audit entry found: %+v", entries) |
| +} |