carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package domains
2
3import (
4 "context"
5 "errors"
6 "os"
7 "reflect"
8 "strings"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/release"
12)
13
14func TestAttachClaimCreates(t *testing.T) {
15 s := release.NewDirStore(t.TempDir())
16 ctx := context.Background()
17 created, changed, err := AttachClaim(ctx, s, Claim{
18 Host: "tickets.example.com", Account: "bab", App: "kass",
19 Route: "canary/live", CreatedAt: "2026-08-12T00:00:00Z", CreatedBy: "paul@tito.io",
20 })
21 if err != nil || !created || !changed {
22 t.Fatalf("AttachClaim: created=%v changed=%v err=%v, want true true nil", created, changed, err)
23 }
24 got, err := GetClaim(ctx, s, "tickets.example.com")
25 if err != nil {
26 t.Fatalf("GetClaim: %v", err)
27 }
28 if got.Schema != 1 || got.Host != "tickets.example.com" || got.Account != "bab" ||
29 got.App != "kass" || got.Route != "canary/live" || got.CreatedAt != "2026-08-12T00:00:00Z" ||
30 got.CreatedBy != "paul@tito.io" {
31 t.Fatalf("GetClaim round-trip mismatch: %+v", got)
32 }
33}
34
35func TestAttachClaimIdempotent(t *testing.T) {
36 s := release.NewDirStore(t.TempDir())
37 ctx := context.Background()
38 claim := Claim{Host: "tickets.example.com", Account: "bab", App: "kass", Route: "canary/live"}
39 if _, _, err := AttachClaim(ctx, s, claim); err != nil {
40 t.Fatal(err)
41 }
42 before, err := GetClaim(ctx, s, claim.Host)
43 if err != nil {
44 t.Fatal(err)
45 }
46 // changed=false is what makes this a NO-OP rather than a write: the door
47 // audits and bumps control/serial off that answer, and a re-attach on
48 // every router reload pass is the steady state, not the exception.
49 created, changed, err := AttachClaim(ctx, s, claim)
50 if err != nil || created || changed {
51 t.Fatalf("AttachClaim re-attach: created=%v changed=%v err=%v, want false false nil", created, changed, err)
52 }
53 after, err := GetClaim(ctx, s, claim.Host)
54 if err != nil {
55 t.Fatal(err)
56 }
57 if !reflect.DeepEqual(before, after) {
58 t.Fatalf("object changed on idempotent re-attach: before=%+v after=%+v", before, after)
59 }
60}
61
62func TestAttachClaimConvergesRoute(t *testing.T) {
63 s := release.NewDirStore(t.TempDir())
64 ctx := context.Background()
65 claim := Claim{Host: "tickets.example.com", Account: "bab", App: "kass", Route: "canary/live",
66 CreatedAt: "2026-08-12T00:00:00Z", Wildcard: true, Catchall: true}
67 if _, _, err := AttachClaim(ctx, s, claim); err != nil {
68 t.Fatal(err)
69 }
70 // A converge repoints Route and nothing else: the caller's claim here
71 // deliberately carries no CreatedAt and no Wildcard, and both stored
72 // values must survive — a route repoint must never strip another
73 // branch's TLS opt-in or restamp the claim's age. created=false but
74 // changed=TRUE: a converge IS a store write, so the door must audit it
75 // and announce it to the fleet.
76 created, changed, err := AttachClaim(ctx, s, Claim{Host: claim.Host, Account: "bab", App: "kass", Route: "canary/next"})
77 if err != nil || created || !changed {
78 t.Fatalf("AttachClaim route change: created=%v changed=%v err=%v, want false true nil", created, changed, err)
79 }
80 got, err := GetClaim(ctx, s, claim.Host)
81 if err != nil {
82 t.Fatal(err)
83 }
84 if got.Route != "canary/next" {
85 t.Fatalf("Route = %q, want canary/next", got.Route)
86 }
87 if !got.Wildcard || !got.Catchall || got.CreatedAt != "2026-08-12T00:00:00Z" {
88 t.Fatalf("converge must preserve Wildcard+Catchall+CreatedAt, got wildcard=%v catchall=%v created_at=%q",
89 got.Wildcard, got.Catchall, got.CreatedAt)
90 }
91}
92
93func TestAttachClaimOtherOwnerRefused(t *testing.T) {
94 s := release.NewDirStore(t.TempDir())
95 ctx := context.Background()
96 claim := Claim{Host: "tickets.example.com", Account: "bab", App: "kass", Route: "canary/live"}
97 if _, _, err := AttachClaim(ctx, s, claim); err != nil {
98 t.Fatal(err)
99 }
100 before, err := GetClaim(ctx, s, claim.Host)
101 if err != nil {
102 t.Fatal(err)
103 }
104
105 other := claim
106 other.Account = "bad"
107 if _, _, err := AttachClaim(ctx, s, other); !errors.Is(err, ErrHostUnavailable) {
108 t.Fatalf("other account: err = %v, want ErrHostUnavailable", err)
109 }
110
111 otherApp := claim
112 otherApp.App = "console"
113 if _, _, err := AttachClaim(ctx, s, otherApp); !errors.Is(err, ErrHostUnavailable) {
114 t.Fatalf("other app: err = %v, want ErrHostUnavailable", err)
115 }
116
117 after, err := GetClaim(ctx, s, claim.Host)
118 if err != nil {
119 t.Fatal(err)
120 }
121 if !reflect.DeepEqual(before, after) {
122 t.Fatalf("object changed on refused attach: before=%+v after=%+v", before, after)
123 }
124}
125
126func TestAttachClaimNewerSchemaRefused(t *testing.T) {
127 s := release.NewDirStore(t.TempDir())
128 ctx := context.Background()
129 body := []byte(`{"schema":2,"host":"tickets.example.com","account":"bab","app":"kass"}`)
130 if _, err := s.Put(ctx, ClaimKey("tickets.example.com"), body, ""); err != nil {
131 t.Fatal(err)
132 }
133 _, _, err := AttachClaim(ctx, s, Claim{Host: "tickets.example.com", Account: "bab", App: "kass"})
134 if err == nil || !strings.Contains(err.Error(), "schema") {
135 t.Fatalf("AttachClaim over newer schema: err = %v, want error mentioning schema", err)
136 }
137 got, _, gerr := s.Get(ctx, ClaimKey("tickets.example.com"))
138 if gerr != nil {
139 t.Fatal(gerr)
140 }
141 if string(got) != string(body) {
142 t.Fatalf("object changed on newer-schema refusal: got %s", got)
143 }
144}
145
146func TestDetachClaimRemoves(t *testing.T) {
147 s := release.NewDirStore(t.TempDir())
148 ctx := context.Background()
149 claim := Claim{Host: "tickets.example.com", Account: "bab", App: "kass"}
150 if _, _, err := AttachClaim(ctx, s, claim); err != nil {
151 t.Fatal(err)
152 }
153 removed, err := DetachClaim(ctx, s, claim.Host, claim.Account, claim.App)
154 if err != nil || !removed {
155 t.Fatalf("DetachClaim: removed=%v err=%v, want true nil", removed, err)
156 }
157 if _, err := GetClaim(ctx, s, claim.Host); !errors.Is(err, os.ErrNotExist) {
158 t.Fatalf("GetClaim after detach: err = %v, want os.ErrNotExist", err)
159 }
160}
161
162func TestDetachClaimUnknownQuiet(t *testing.T) {
163 s := release.NewDirStore(t.TempDir())
164 ctx := context.Background()
165 removed, err := DetachClaim(ctx, s, "nope.example.com", "bab", "kass")
166 if err != nil || removed {
167 t.Fatalf("DetachClaim unknown: removed=%v err=%v, want false nil", removed, err)
168 }
169}
170
171func TestDetachClaimOtherOwnerQuiet(t *testing.T) {
172 s := release.NewDirStore(t.TempDir())
173 ctx := context.Background()
174 claim := Claim{Host: "tickets.example.com", Account: "bab", App: "kass"}
175 if _, _, err := AttachClaim(ctx, s, claim); err != nil {
176 t.Fatal(err)
177 }
178 removed, err := DetachClaim(ctx, s, claim.Host, "bad", "console")
179 if err != nil || removed {
180 t.Fatalf("DetachClaim other owner: removed=%v err=%v, want false nil", removed, err)
181 }
182 if _, err := GetClaim(ctx, s, claim.Host); err != nil {
183 t.Fatalf("GetClaim after other-owner detach: %v, want claim still present", err)
184 }
185}
186
187func TestListClaimsSkipsGarbage(t *testing.T) {
188 s := release.NewDirStore(t.TempDir())
189 ctx := context.Background()
190 if _, _, err := AttachClaim(ctx, s, Claim{Host: "a.example.com", Account: "bab", App: "kass"}); err != nil {
191 t.Fatal(err)
192 }
193 if _, _, err := AttachClaim(ctx, s, Claim{Host: "b.example.com", Account: "bad", App: "console"}); err != nil {
194 t.Fatal(err)
195 }
196 if _, err := s.Put(ctx, ClaimKey("garbage.example.com"), []byte("not json"), ""); err != nil {
197 t.Fatal(err)
198 }
199 claims, err := ListClaims(ctx, s)
200 if err != nil {
201 t.Fatalf("ListClaims: %v", err)
202 }
203 if len(claims) != 2 {
204 t.Fatalf("ListClaims returned %d claims, want 2: %+v", len(claims), claims)
205 }
206 hosts := map[string]bool{}
207 for _, c := range claims {
208 hosts[c.Host] = true
209 }
210 if !hosts["a.example.com"] || !hosts["b.example.com"] {
211 t.Fatalf("ListClaims missing expected hosts: %+v", claims)
212 }
213}
214
215func TestNormalizeAliasHost(t *testing.T) {
216 cases := []struct {
217 raw string
218 want string
219 ok bool
220 }{
221 {"Tickets.Example.COM.", "tickets.example.com", true},
222 {"localhost", "", false},
223 {"a..b", "", false},
224 {"", "", false},
225 {strings.Repeat("a", 254), "", false},
226 }
227 for _, c := range cases {
228 got, ok := NormalizeAliasHost(c.raw)
229 if ok != c.ok {
230 t.Errorf("NormalizeAliasHost(%q) ok = %v, want %v", c.raw, ok, c.ok)
231 continue
232 }
233 if ok && got != c.want {
234 t.Errorf("NormalizeAliasHost(%q) = %q, want %q", c.raw, got, c.want)
235 }
236 }
237}
238
239func TestSetClaimCapabilities(t *testing.T) {
240 ctx := context.Background()
241 s := release.NewDirStore(t.TempDir())
242 if _, _, err := AttachClaim(ctx, s, Claim{Host: "woodstar.app", Account: "bab", App: "woodstar-relay", Route: "woodstar-relay.bab.oncarlos.com", CreatedAt: "2026-08-13T00:00:00Z"}); err != nil {
243 t.Fatal(err)
244 }
245
246 // Set both capabilities.
247 changed, err := SetClaimCapabilities(ctx, s, "woodstar.app", "bab", "woodstar-relay", true, true)
248 if err != nil || !changed {
249 t.Fatalf("set: changed=%v err=%v", changed, err)
250 }
251 got, err := GetClaim(ctx, s, "woodstar.app")
252 if err != nil || !got.Wildcard || !got.Catchall {
253 t.Fatalf("stored claim: %+v err=%v", got, err)
254 }
255 // Route, CreatedAt survive the capability write.
256 if got.Route != "woodstar-relay.bab.oncarlos.com" || got.CreatedAt != "2026-08-13T00:00:00Z" {
257 t.Fatalf("capability write must preserve Route+CreatedAt: %+v", got)
258 }
259
260 // Idempotent: same values = no write.
261 changed, err = SetClaimCapabilities(ctx, s, "woodstar.app", "bab", "woodstar-relay", true, true)
262 if err != nil || changed {
263 t.Fatalf("no-op set: changed=%v err=%v", changed, err)
264 }
265
266 // Turning both off works and reports changed.
267 changed, err = SetClaimCapabilities(ctx, s, "woodstar.app", "bab", "woodstar-relay", false, false)
268 if err != nil || !changed {
269 t.Fatalf("unset: changed=%v err=%v", changed, err)
270 }
271}
272
273func TestSetClaimCapabilitiesRefusals(t *testing.T) {
274 ctx := context.Background()
275 s := release.NewDirStore(t.TempDir())
276 if _, _, err := AttachClaim(ctx, s, Claim{Host: "woodstar.app", Account: "bab", App: "woodstar-relay", CreatedAt: "2026-08-13T00:00:00Z"}); err != nil {
277 t.Fatal(err)
278 }
279
280 // Catchall without wildcard: refused, never stored.
281 if _, err := SetClaimCapabilities(ctx, s, "woodstar.app", "bab", "woodstar-relay", false, true); !errors.Is(err, ErrCatchallNeedsWildcard) {
282 t.Fatalf("want ErrCatchallNeedsWildcard, got %v", err)
283 }
284 if got, _ := GetClaim(ctx, s, "woodstar.app"); got.Catchall {
285 t.Fatal("refused catchall must not be stored")
286 }
287
288 // Wrong owner: neutral refusal, same error the attach door uses.
289 if _, err := SetClaimCapabilities(ctx, s, "woodstar.app", "bad", "other-app", true, false); !errors.Is(err, ErrHostUnavailable) {
290 t.Fatalf("want ErrHostUnavailable, got %v", err)
291 }
292
293 // No claim at all: neutral, not a bare not-found (no existence oracle).
294 if _, err := SetClaimCapabilities(ctx, s, "unclaimed.example", "bab", "woodstar-relay", true, false); !errors.Is(err, ErrHostUnavailable) {
295 t.Fatalf("want ErrHostUnavailable for missing claim, got %v", err)
296 }
297}
298
299func TestHostDenied(t *testing.T) {
300 deny := []string{"oncarlos.com"}
301 cases := []struct {
302 host string
303 want bool
304 }{
305 {"x.oncarlos.com", true},
306 {"oncarlos.com", true},
307 {"xoncarlos.com", false},
308 }
309 for _, c := range cases {
310 if got := HostDenied(c.host, deny); got != c.want {
311 t.Errorf("HostDenied(%q, %v) = %v, want %v", c.host, deny, got, c.want)
312 }
313 }
314 if HostDenied("anything.example.com", []string{"", "oncarlos.com"}) {
315 t.Fatal("empty deny entry must never match")
316 }
317}
318