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 "testing"
6 "time"
7
8 "github.com/carlosframework/platform/internal/release"
9)
10
11func TestDomainKeyIsAccountScoped(t *testing.T) {
12 got := DomainKey("bag", "harbourmail.bag.oncarlos.com")
13 want := "accounts/bag/email/domains/harbourmail.bag.oncarlos.com.json"
14 if got != want {
15 t.Fatalf("DomainKey = %q, want %q", got, want)
16 }
17}
18
19func TestValidDomainRefusesWhatWouldEscapeTheKey(t *testing.T) {
20 for _, ok := range []string{"harbourmail.bag.oncarlos.com", "example.com", "a-b.example.co.uk"} {
21 if !ValidDomain(ok) {
22 t.Fatalf("ValidDomain(%q) = false, want true", ok)
23 }
24 }
25 // A domain becomes a bucket KEY and an IAM policy RESOURCE. Anything
26 // that could traverse or inject must be refused here, at the only
27 // place every caller passes through.
28 for _, bad := range []string{"", ".", "../etc", "a/b.com", "UPPER.com", "-lead.com", "trail-.com", "a..b.com",
29 // Wildcards are dangerous: the domain string becomes an IAM
30 // ses:FromAddress condition value, where * would match every
31 // address on earth.
32 "*", "*.example.com"} {
33 if ValidDomain(bad) {
34 t.Fatalf("ValidDomain(%q) = true, want false", bad)
35 }
36 }
37}
38
39// Regions is a MAP, and this test is the reason: a domain verified in two
40// regions holds two independent identities, ARNs and DKIM token sets, and
41// a scalar region field would have to be migrated the day a second region
42// appears (spec §6a, §8).
43func TestRegionsAreIndependent(t *testing.T) {
44 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
45 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
46 rec := &DomainRecord{
47 Domain: "harbourmail.bag.oncarlos.com",
48 Kind: KindPlatform,
49 Apps: []string{"harbourmail"},
50 Regions: map[string]*RegionState{
51 "eu-west-1": {IdentityARN: "arn:aws:ses:eu-west-1:1:identity/harbourmail.bag.oncarlos.com",
52 DKIMTokens: []string{"a", "b", "c"},
53 MailFrom: "mail-eu-west-1.harbourmail.bag.oncarlos.com",
54 Status: StatusVerified, CheckedAt: now},
55 "us-east-1": {IdentityARN: "arn:aws:ses:us-east-1:1:identity/harbourmail.bag.oncarlos.com",
56 DKIMTokens: []string{"d", "e", "f"},
57 MailFrom: "mail-us-east-1.harbourmail.bag.oncarlos.com",
58 Status: StatusPending, CheckedAt: now},
59 },
60 CreatedAt: now,
61 }
62 if _, err := PutDomain(ctx, s, "bag", rec, ""); err != nil {
63 t.Fatalf("PutDomain: %v", err)
64 }
65 out, _, err := GetDomain(ctx, s, "bag", "harbourmail.bag.oncarlos.com")
66 if err != nil {
67 t.Fatalf("GetDomain: %v", err)
68 }
69 if len(out.Regions) != 2 {
70 t.Fatalf("regions = %d, want 2", len(out.Regions))
71 }
72 if out.Regions["us-east-1"].DKIMTokens[0] != "d" {
73 t.Fatal("per-region DKIM tokens did not round-trip independently")
74 }
75}
76
77// Only a VERIFIED region contributes an ARN, so a pending domain grants
78// nothing (spec §6a).
79func TestVerifiedARNsSkipsPendingAndFailed(t *testing.T) {
80 rec := &DomainRecord{Regions: map[string]*RegionState{
81 "eu-west-1": {IdentityARN: "arn-ok", Status: StatusVerified},
82 "us-east-1": {IdentityARN: "arn-pending", Status: StatusPending},
83 "ap-south-1": {IdentityARN: "arn-failed", Status: StatusFailed},
84 }}
85 got := rec.VerifiedARNs()
86 if len(got) != 1 || got[0] != "arn-ok" {
87 t.Fatalf("VerifiedARNs = %v, want [arn-ok]", got)
88 }
89}
90
91// The apps set is what makes account-scoping worth its cost: teardown of
92// one app must never revoke a sibling's verified domain (spec §6a).
93func TestAppsSetGuardsSiblingTeardown(t *testing.T) {
94 rec := &DomainRecord{Apps: []string{"harbourmail"}}
95 if !rec.AddApp("marketing") {
96 t.Fatal("AddApp returned false for a new app")
97 }
98 if rec.AddApp("marketing") {
99 t.Fatal("AddApp returned true for an app already present")
100 }
101 if !rec.RemoveApp("harbourmail") {
102 t.Fatal("RemoveApp returned false for a present app")
103 }
104 if len(rec.Apps) != 1 || rec.Apps[0] != "marketing" {
105 t.Fatalf("Apps = %v, want [marketing] — the sibling must survive", rec.Apps)
106 }
107}
108