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
11// An operator's deliberate cap must survive a tier recompute. Source is
12// what distinguishes "this account is on the free tier" from "someone
13// decided this account may send more", and a tier pass that flattened the
14// second into the first would silently revoke a decision (spec §10).
15func TestTierRecomputeDoesNotClobberAnOperatorGrant(t *testing.T) {
16 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
17 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
18 if _, err := Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP",
19 "n@x.com", "owner@example.test", now); err != nil {
20 t.Fatal(err)
21 }
22 if _, err := ApplyGrant(ctx, s, "bag", "harbourmail", []string{"x.com"}, 5000, "operator", "owner@example.test", now); err != nil {
23 t.Fatal(err)
24 }
25 rec, err := ApplyGrant(ctx, s, "bag", "harbourmail", []string{"x.com"}, 50, "tier:free", "tier", now)
26 if err != nil {
27 t.Fatal(err)
28 }
29 if rec.Grant.DailyCap != 5000 || rec.Grant.Source != "operator" {
30 t.Fatalf("tier recompute clobbered the operator grant: %+v", rec.Grant)
31 }
32}
33
34// A tier change DOES move a tier-sourced cap — that is the whole point of
35// recording the source.
36func TestTierRecomputeMovesATierGrant(t *testing.T) {
37 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
38 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
39 Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", "n@x.com", "owner@example.test", now)
40 ApplyGrant(ctx, s, "bag", "harbourmail", []string{"x.com"}, 50, "tier:free", "tier", now)
41 rec, err := ApplyGrant(ctx, s, "bag", "harbourmail", []string{"x.com"}, 1000, "tier:starter", "tier", now)
42 if err != nil {
43 t.Fatal(err)
44 }
45 if rec.Grant.DailyCap != 1000 {
46 t.Fatalf("DailyCap = %d, want 1000", rec.Grant.DailyCap)
47 }
48}
49
50func TestGrantRefusesAnUndeclaredApp(t *testing.T) {
51 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
52 if _, err := ApplyGrant(ctx, s, "bag", "harbourmail", []string{"x.com"}, 50, "tier:free", "tier", time.Now().UTC()); err != ErrNotDeclared {
53 t.Fatalf("= %v, want ErrNotDeclared", err)
54 }
55}
56
57// An operator must be able to override a tier grant. This is how an operator
58// raises a cap on a tiered account — the entire point of recording Source.
59func TestOperatorCanOverrideTierGrant(t *testing.T) {
60 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
61 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
62 Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", "n@x.com", "owner@example.test", now)
63 ApplyGrant(ctx, s, "bag", "harbourmail", []string{"x.com"}, 50, "tier:free", "tier", now)
64 rec, err := ApplyGrant(ctx, s, "bag", "harbourmail", []string{"x.com"}, 5000, "operator", "owner@example.test", now)
65 if err != nil {
66 t.Fatal(err)
67 }
68 if rec.Grant.DailyCap != 5000 || rec.Grant.Source != "operator" {
69 t.Fatalf("operator override failed: got DailyCap=%d Source=%q, want 5000/operator", rec.Grant.DailyCap, rec.Grant.Source)
70 }
71}
72
73// Domains are sorted before storage for consistency, so grant lookups are
74// stable regardless of the order they were supplied in.
75func TestGrantSortsDomains(t *testing.T) {
76 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
77 now := time.Date(2026, 8, 19, 12, 0, 0, 0, time.UTC)
78 Declare(ctx, s, "bag", "harbourmail", "HARBOURMAIL_SMTP", "n@x.com", "owner@example.test", now)
79 rec, err := ApplyGrant(ctx, s, "bag", "harbourmail", []string{"z.com", "a.com", "m.com"}, 50, "tier:free", "tier", now)
80 if err != nil {
81 t.Fatal(err)
82 }
83 if rec.Grant.Domains[0] != "a.com" || rec.Grant.Domains[1] != "m.com" || rec.Grant.Domains[2] != "z.com" {
84 t.Fatalf("domains not sorted: %v", rec.Grant.Domains)
85 }
86}
87