carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "os"
5 "strings"
6 "testing"
7 "time"
8
9 "github.com/carlosframework/platform/internal/controlname"
10 "github.com/carlosframework/platform/internal/provision"
11 "github.com/carlosframework/platform/internal/release"
12)
13
14// A canary name has to satisfy TWO alphabets at once: release.ValidChannel's
15// segment ([a-zA-Z0-9._-], 64) and a DNS label, because the same string is
16// both the channel's second segment and the leftmost label of the canary
17// host. The DNS label is the stricter of the two, so it is the one enforced
18// — lowercase, no dots (a dot would silently add a level to the host).
19func TestCanarySlugTurnsBranchesIntoDNSLabels(t *testing.T) {
20 for _, tc := range []struct{ branch, want string }{
21 {"new-queue", "new-queue"},
22 {"feature/new-queue", "feature-new-queue"},
23 {"PAUL/JAM-42", "paul-jam-42"},
24 {"paul@example.com/fix", "paul-example-com-fix"},
25 {"a//b", "a-b"},
26 {"--leading-and-trailing--", "leading-and-trailing"},
27 {"v1.2.3", "v1-2-3"},
28 {"", ""},
29 {"///", ""},
30 } {
31 if got := canarySlug(tc.branch); got != tc.want {
32 t.Errorf("canarySlug(%q) = %q, want %q", tc.branch, got, tc.want)
33 }
34 }
35}
36
37// 63 is the DNS label cap, and it is tighter than the channel segment's 64,
38// so it is the one that binds. A slug that overran it would mint a host the
39// edge could never serve.
40func TestCanarySlugTruncatesToTheDNSLabelCap(t *testing.T) {
41 got := canarySlug(strings.Repeat("x", 100))
42 if len(got) != 63 {
43 t.Fatalf("len = %d, want 63", len(got))
44 }
45 if !validCanaryName(got) {
46 t.Errorf("a truncated slug %q is not a valid canary name", got)
47 }
48}
49
50// Whatever the slugger produces must satisfy the platform's own channel
51// check too — the two alphabets are only useful if the tighter one is
52// genuinely inside the looser one.
53func TestCanarySlugIsAlwaysAValidChannel(t *testing.T) {
54 for _, branch := range []string{"feature/new-queue", "PAUL/JAM-42", "v1.2.3", strings.Repeat("ab/", 40)} {
55 name := canarySlug(branch)
56 if !release.ValidChannel(canaryChannel(name)) {
57 t.Errorf("canarySlug(%q) = %q, which makes an invalid channel", branch, name)
58 }
59 }
60}
61
62// Truncation must not leave a trailing separator: "canary/foo-" is a valid
63// segment but an invalid DNS label, and the host is built from the same
64// string.
65func TestCanarySlugTruncationDoesNotStrandASeparator(t *testing.T) {
66 got := canarySlug(strings.Repeat("ab/", 40))
67 if strings.HasSuffix(got, "-") {
68 t.Errorf("slug %q ends in a separator", got)
69 }
70}
71
72// A detached HEAD reports the literal "HEAD". Minting canary/head off that
73// would be a silent wrong answer, so it is refused and names the way out.
74func TestCanaryNameRefusesDetachedHead(t *testing.T) {
75 _, err := canaryNameFrom("", "HEAD")
76 if err == nil {
77 t.Fatal("a detached HEAD was accepted; want a refusal")
78 }
79 if !strings.Contains(err.Error(), "--name") {
80 t.Errorf("refusal %q does not name --name", err)
81 }
82}
83
84func TestCanaryNameFlagBeatsTheBranch(t *testing.T) {
85 got, err := canaryNameFrom("chosen", "some-branch")
86 if err != nil {
87 t.Fatalf("canaryNameFrom: %v", err)
88 }
89 if got != "chosen" {
90 t.Errorf("name = %q, want chosen — --name must win over the branch", got)
91 }
92}
93
94// An explicit --name is taken as typed, never slugified: silently rewriting
95// what someone deliberately asked for is how you end up promoting to a
96// channel you never named.
97func TestCanaryNameFlagIsRefusedNotRepaired(t *testing.T) {
98 _, err := canaryNameFrom("feature/new-queue", "")
99 if err == nil {
100 t.Fatal("--name feature/new-queue was accepted; want a refusal")
101 }
102 if !strings.Contains(err.Error(), "feature/new-queue") {
103 t.Errorf("refusal %q does not quote the name given", err)
104 }
105}
106
107// No --name and no usable branch: refuse, rather than invent one.
108func TestCanaryNameWithNothingToGoOnIsRefused(t *testing.T) {
109 _, err := canaryNameFrom("", "")
110 if err == nil {
111 t.Fatal("an empty name and empty branch were accepted; want a refusal")
112 }
113 if !strings.Contains(err.Error(), "--name") {
114 t.Errorf("refusal %q does not name --name", err)
115 }
116}
117
118// The host is <canary>-<app>.<account-sqid>.<apps-domain>: one label before
119// the domain, because the instance create door takes exactly one (discussion
120// 207). The manifest's domain is already "<sqid>.<apps-domain>", the same
121// value deploy's mintPlatformHost builds "<app>.<domain>" from.
122func TestCanaryHostIsOneLabelBeforeTheDomain(t *testing.T) {
123 const domain = "bab.oncarlos.com"
124 got := canaryHost("new-queue", "jam", domain)
125 want := "new-queue-jam.bab.oncarlos.com"
126 if got != want {
127 t.Errorf("canaryHost = %q, want %q", got, want)
128 }
129 // The create door's own predicate, not a restatement of it.
130 if !provision.ValidHost(got, domain) {
131 t.Errorf("provision.ValidHost(%q, %q) = false: the create door would refuse this canary", got, domain)
132 }
133 // The widest label that fits: name + "-" + app = 63.
134 name := strings.Repeat("n", 63-1-len("jam"))
135 if h := canaryHost(name, "jam", domain); !canaryLabelFits(name, "jam") || !provision.ValidHost(h, domain) {
136 t.Errorf("a 63-byte label was refused: fits=%v host=%q", canaryLabelFits(name, "jam"), h)
137 }
138}
139
140// name + "-" + app is one hostname label: 63 bytes, the dash counted. A name
141// validCanaryName accepts on its own can still be too long beside the app.
142func TestCanaryLabelFitsOneLabel(t *testing.T) {
143 for _, tc := range []struct {
144 name, app string
145 want bool
146 }{
147 {"new-queue", "jam", true},
148 {strings.Repeat("n", 59), "jam", true}, // 59 + 1 + 3 = 63
149 {strings.Repeat("n", 60), "jam", false}, // 64
150 {strings.Repeat("n", 63), "a", false}, // a valid name, no room for the app
151 {"a", strings.Repeat("p", 61), true}, // 1 + 1 + 61 = 63
152 {"a", strings.Repeat("p", 62), false},
153 } {
154 if got := canaryLabelFits(tc.name, tc.app); got != tc.want {
155 t.Errorf("canaryLabelFits(%d bytes, %d bytes) = %v, want %v", len(tc.name), len(tc.app), got, tc.want)
156 }
157 }
158 msg := canaryLabelTooLong(strings.Repeat("n", 60), "jam")
159 for _, want := range []string{"64 characters", "63", "--name"} {
160 if !strings.Contains(msg, want) {
161 t.Errorf("refusal %q does not say %q", msg, want)
162 }
163 }
164}
165
166func TestCanaryChannelIsNamespaced(t *testing.T) {
167 if got := canaryChannel("new-queue"); got != "canary/new-queue" {
168 t.Errorf("canaryChannel = %q, want canary/new-queue", got)
169 }
170}
171
172// Step 4 of the deploy: create, re-channel, or write nothing at all. The
173// no-write case matters — a redeploy of the same canary must not PATCH a
174// record that is already correct.
175func TestCanaryRecordPlan(t *testing.T) {
176 const host = "new-queue-jam.bab.oncarlos.com"
177 const ch = "canary/new-queue"
178 other := apiInstanceRecord{Host: "jam.bab.oncarlos.com", Channel: "stable"}
179
180 for _, tc := range []struct {
181 name string
182 recs []apiInstanceRecord
183 want canaryPlan
184 }{
185 {"no records at all", nil, canaryPlanCreate},
186 {"only the app's stable instance", []apiInstanceRecord{other}, canaryPlanCreate},
187 {"canary host already on the canary channel",
188 []apiInstanceRecord{other, {Host: host, Channel: ch}}, canaryPlanReady},
189 {"canary host parked on another channel",
190 []apiInstanceRecord{other, {Host: host, Channel: "canary/old"}}, canaryPlanRechannel},
191 // One label now: the canary's host can be an ordinary instance of
192 // the same app. Rechannelling is for canaries; an instance that is
193 // not on a canary/* channel is refused, whatever it follows.
194 {"an instance of the app on the canary's host",
195 []apiInstanceRecord{{Host: host, Channel: "stable"}}, canaryPlanTaken},
196 {"a bare canary channel is not a canary/* channel",
197 []apiInstanceRecord{{Host: host, Channel: "canary"}}, canaryPlanTaken},
198 } {
199 t.Run(tc.name, func(t *testing.T) {
200 if got := canaryRecordPlan(tc.recs, host, ch); got != tc.want {
201 t.Errorf("plan = %v, want %v", got, tc.want)
202 }
203 })
204 }
205}
206
207// ls reports what is left of the seven-day lease, and says plainly when one
208// has lapsed — that is the whole reason the TTL is visible at all.
209func TestCanaryLeaseLabel(t *testing.T) {
210 now := time.Date(2026, 9, 4, 12, 0, 0, 0, time.UTC)
211 rfc := func(d time.Duration) string { return now.Add(-d).Format(time.RFC3339) }
212
213 for _, tc := range []struct{ name, at, want string }{
214 {"promoted moments ago", rfc(time.Minute), "7d left"},
215 {"one day in", rfc(24 * time.Hour), "6d left"},
216 {"final hours", rfc(7*24*time.Hour - 3*time.Hour), "3h left"},
217 {"just lapsed", rfc(7*24*time.Hour + 30*time.Minute), "expired <1h"},
218 {"long lapsed", rfc(9 * 24 * time.Hour), "expired 2d"},
219 {"never promoted", "", "—"},
220 {"unparseable", "last tuesday", "—"},
221 } {
222 t.Run(tc.name, func(t *testing.T) {
223 if got := canaryLeaseLabel(tc.at, now); got != tc.want {
224 t.Errorf("canaryLeaseLabel(%q) = %q, want %q", tc.at, got, tc.want)
225 }
226 })
227 }
228}
229
230// rm must delete the record for the canary it was asked about and no other —
231// an app can have several canaries live at once.
232func TestCanaryRecordForNamePicksExactlyOne(t *testing.T) {
233 recs := []apiInstanceRecord{
234 {Host: "jam.bab.oncarlos.com", Channel: "stable"},
235 {Host: "old-jam.bab.oncarlos.com", Channel: "canary/old"},
236 {Host: "new-queue-jam.bab.oncarlos.com", Channel: "canary/new-queue"},
237 }
238 got, ok := canaryRecordForChannel(recs, "canary/new-queue")
239 if !ok {
240 t.Fatal("no record found for canary/new-queue")
241 }
242 if got.Host != "new-queue-jam.bab.oncarlos.com" {
243 t.Errorf("host = %q, want the new-queue record", got.Host)
244 }
245 if _, ok := canaryRecordForChannel(recs, "canary/nope"); ok {
246 t.Error("a canary that does not exist was reported found")
247 }
248}
249
250// ls lists canaries, not every instance the app has.
251func TestCanaryRecordsFiltersToCanaryChannels(t *testing.T) {
252 recs := []apiInstanceRecord{
253 {Host: "jam.bab.oncarlos.com", Channel: "stable"},
254 {Host: "old-jam.bab.oncarlos.com", Channel: "canary/old"},
255 {Host: "eu-jam.bab.oncarlos.com", Channel: "edge"},
256 {Host: "new-queue-jam.bab.oncarlos.com", Channel: "canary/new-queue"},
257 // A bare "canary" is NOT a canary/* channel — the same boundary
258 // internal/host/agent.go's isCanaryRoute draws.
259 {Host: "bare-jam.bab.oncarlos.com", Channel: "canary"},
260 }
261 got := canaryRecords(recs)
262 if len(got) != 2 {
263 t.Fatalf("got %d canary records, want 2: %+v", len(got), got)
264 }
265 if got[0].Channel != "canary/new-queue" && got[0].Channel != "canary/old" {
266 t.Errorf("unexpected first row %+v", got[0])
267 }
268}
269
270// The name is what a member types into rm, so ls has to be able to derive it
271// back out of the channel it lists.
272func TestCanaryNameOfChannel(t *testing.T) {
273 if got := canaryNameOf("canary/new-queue"); got != "new-queue" {
274 t.Errorf("canaryNameOf = %q, want new-queue", got)
275 }
276 if got := canaryNameOf("stable"); got != "" {
277 t.Errorf("canaryNameOf(stable) = %q, want empty", got)
278 }
279}
280
281// The whole point of the change: main.go's canary case dispatches instead of
282// exiting "not implemented". internal/docsite's TopLevelCommands scan already
283// insists the verb has a doc heading, but it cannot tell a real dispatch from
284// a stub — and a stub with a heading is exactly the state that made a session
285// conclude the platform could not canary at all.
286func TestCanaryIsDispatchedNotStubbed(t *testing.T) {
287 src, err := os.ReadFile("main.go")
288 if err != nil {
289 t.Fatalf("read main.go: %v", err)
290 }
291 _, after, ok := strings.Cut(string(src), "case \"canary\":")
292 if !ok {
293 t.Fatal("main.go no longer dispatches on \"canary\"")
294 }
295 body, _, _ := strings.Cut(after, "case \"")
296 if !strings.Contains(body, "runCanary(args)") {
297 t.Errorf("the canary case does not call runCanary: %q", body)
298 }
299 if strings.Contains(body, "not implemented") {
300 t.Error("the canary case still exits \"not implemented\"")
301 }
302}
303
304// canary is a member verb now, not a reserved word, so it takes the
305// once-a-day update hint like deploy and ship do.
306func TestCanaryTakesTheUpdateHint(t *testing.T) {
307 if noHintVerbs["canary"] {
308 t.Error("canary is still in noHintVerbs — it was listed there while it was a stub")
309 }
310}
311
312// The refusal names the channel the record on the host follows, from the
313// same source the plan was decided from: the direct read under --region, the
314// list otherwise.
315func TestCanaryHostChannelReadsWhatThePlanDecidedFrom(t *testing.T) {
316 const host = "probe-jam.bab.oncarlos.com"
317 recs := []apiInstanceRecord{{Host: host, Channel: "stable"}, {Host: "other-jam.bab.oncarlos.com", Channel: "edge"}}
318 if got := canaryHostChannel(canaryPreflight{}, recs, host); got != "stable" {
319 t.Errorf("from the list = %q, want stable", got)
320 }
321 pre := canaryPreflight{Host: host, Rec: apiInstanceRecord{Host: host, Channel: "beta"}, Found: true}
322 if got := canaryHostChannel(pre, recs, host); got != "beta" {
323 t.Errorf("from the direct read = %q, want beta", got)
324 }
325 if got := canaryHostChannel(canaryPreflight{Host: host}, recs, host); got != "" {
326 t.Errorf("direct read found nothing, got %q", got)
327 }
328 if msg := canaryHostTaken(host, "stable"); !strings.Contains(msg, host) || !strings.Contains(msg, "stable") {
329 t.Errorf("refusal %q names neither the host nor its channel", msg)
330 }
331}
332
333// Every app name the platform accepts has to map to a canary host that is
334// either valid or refused up front. App names allow a trailing dash
335// (controlname.ValidApp: ^[a-z][a-z0-9-]{0,31}$) and DNS labels do not, so
336// app "jam-" would make "probe-jam-" for EVERY canary name, and choosing
337// another --name cannot help. Exhaustive over a small alphabet that holds
338// every character class and the dash positions: when the refusal is empty
339// the host must pass the create door's own predicate.
340func TestCanaryHostIsValidOrRefusedForEveryAppName(t *testing.T) {
341 const domain = "bab.oncarlos.com"
342 var words func(n int) []string
343 words = func(n int) []string {
344 if n == 0 {
345 return []string{""}
346 }
347 var out []string
348 for _, w := range words(n - 1) {
349 for _, c := range "a1-" {
350 out = append(out, w+string(c))
351 }
352 }
353 return out
354 }
355 var names, apps []string
356 for n := 1; n <= 4; n++ {
357 for _, w := range words(n) {
358 if validCanaryName(w) {
359 names = append(names, w)
360 }
361 if controlname.ValidApp(w) {
362 apps = append(apps, w)
363 }
364 }
365 }
366 // A real-length app ending in a dash, and the one at the 32-byte limit.
367 apps = append(apps, "jam-", strings.Repeat("a", 31)+"-", strings.Repeat("a", 32))
368 refused, accepted := 0, 0
369 for _, app := range apps {
370 for _, name := range names {
371 why := canaryHostRefusal(name, app)
372 ok := provision.ValidHost(canaryHost(name, app, domain), domain)
373 switch {
374 case why == "" && !ok:
375 t.Fatalf("app %q, canary %q: host %q is not valid and nothing refused it", app, name, canaryHost(name, app, domain))
376 case why != "" && ok && canaryLabelFits(name, app):
377 t.Fatalf("app %q, canary %q: refused (%s) though %q is a valid host", app, name, why, canaryHost(name, app, domain))
378 }
379 if why == "" {
380 accepted++
381 } else {
382 refused++
383 }
384 }
385 }
386 if accepted == 0 || refused == 0 {
387 t.Fatalf("the sweep saw %d accepted and %d refused; it proves nothing", accepted, refused)
388 }
389}
390