| 1 | package console |
| 2 | |
| 3 | // The "give this canary a URL" advice the app page and the post-create flash |
| 4 | // both render. The property under test is not the wording — it is WHICH verb |
| 5 | // the console sends its reader at. `carlos add` writes the box's registry on |
| 6 | // local disk (cli.go's defaultRegistry), so a browser session anywhere else |
| 7 | // cannot run it; `carlos instances create` is the API-backed door a login |
| 8 | // credential can drive, and provision.Reconcile carries its channel onto the |
| 9 | // minted route. A dashboard advertising the box-local verb is the bug this |
| 10 | // file pins shut (2026-08-07). |
| 11 | |
| 12 | import ( |
| 13 | "context" |
| 14 | "strings" |
| 15 | "testing" |
| 16 | |
| 17 | "github.com/carlosframework/platform/internal/provision" |
| 18 | ) |
| 19 | |
| 20 | func TestAttachCommands(t *testing.T) { |
| 21 | for _, tc := range []struct { |
| 22 | name string |
| 23 | domain string |
| 24 | appsDomain, sqid string |
| 25 | absent bool |
| 26 | wantCreate string |
| 27 | wantEnable string |
| 28 | }{{ |
| 29 | // Enabled: the manifest's own domain is where hosts live, whether it |
| 30 | // is the platform pin or a custom domain, and no enable step is owed. |
| 31 | name: "enabled platform domain", domain: "bab.oncarlos.test", |
| 32 | appsDomain: "oncarlos.test", sqid: "bab", |
| 33 | wantCreate: "carlos instances create --app echo --host <name>.bab.oncarlos.test --channel canary/rc", |
| 34 | }, { |
| 35 | name: "enabled custom domain", domain: "go.tito.io", |
| 36 | appsDomain: "oncarlos.test", sqid: "bab", |
| 37 | wantCreate: "carlos instances create --app echo --host <name>.go.tito.io --channel canary/rc", |
| 38 | }, { |
| 39 | // Proven absent: lead with enable, and name the host shape the enable |
| 40 | // door will pin — the account's own platform subdomain. |
| 41 | name: "absent with apps domain", absent: true, |
| 42 | appsDomain: "oncarlos.test", sqid: "bab", |
| 43 | wantCreate: "carlos instances create --app echo --host <name>.bab.oncarlos.test --channel canary/rc", |
| 44 | wantEnable: "carlos instances enable --app echo", |
| 45 | }, { |
| 46 | // Absent and the deployment has no platform apps domain: the enable |
| 47 | // door itself refuses (503), so there is no host shape to promise. |
| 48 | name: "absent without apps domain", absent: true, sqid: "bab", |
| 49 | wantCreate: "carlos instances create --app echo --host <host> --channel canary/rc", |
| 50 | wantEnable: "carlos instances enable --app echo", |
| 51 | }, { |
| 52 | // Unreadable manifest: neither enabled nor proven absent. The enable |
| 53 | // step must NOT appear — that door is guarded and answers 409 over a |
| 54 | // standing manifest, so a transient bucket error must not send an |
| 55 | // operator at a refusal. |
| 56 | name: "unreadable", appsDomain: "oncarlos.test", sqid: "bab", |
| 57 | wantCreate: "carlos instances create --app echo --host <host> --channel canary/rc", |
| 58 | }} { |
| 59 | t.Run(tc.name, func(t *testing.T) { |
| 60 | create, enable := attachCommands("echo", "canary/rc", tc.domain, tc.appsDomain, tc.sqid, tc.absent) |
| 61 | if create != tc.wantCreate { |
| 62 | t.Errorf("create = %q, want %q", create, tc.wantCreate) |
| 63 | } |
| 64 | if enable != tc.wantEnable { |
| 65 | t.Errorf("enable = %q, want %q", enable, tc.wantEnable) |
| 66 | } |
| 67 | if strings.Contains(create, "carlos add") || strings.Contains(enable, "carlos add") { |
| 68 | t.Errorf("advice names the box-local verb: %q / %q", create, enable) |
| 69 | } |
| 70 | }) |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | // TestInstanceDomainForStates pins the three-way answer, because collapsing |
| 75 | // "could not read" into "absent" is exactly what would put a doomed |
| 76 | // `carlos instances enable` in front of an operator during a bucket hiccup. |
| 77 | func TestInstanceDomainForStates(t *testing.T) { |
| 78 | fx := newConsoleFixture(t) |
| 79 | ctx := context.Background() |
| 80 | |
| 81 | // No manifest written for the fixture's app: proven absent. |
| 82 | if domain, absent := fx.srv.instanceDomainFor(ctx, "bab", "kass"); domain != "" || !absent { |
| 83 | t.Fatalf("no manifest: domain=%q absent=%v, want \"\" true", domain, absent) |
| 84 | } |
| 85 | |
| 86 | // A manifest present: its domain, and not absent. |
| 87 | m := provision.Manifest{Schema: 1, App: "kass", Domain: "bab.oncarlos.test", Channel: "stable"} |
| 88 | if err := provision.PutManifest(ctx, fx.rel, "bab", "kass", m); err != nil { |
| 89 | t.Fatal(err) |
| 90 | } |
| 91 | if domain, absent := fx.srv.instanceDomainFor(ctx, "bab", "kass"); domain != "bab.oncarlos.test" || absent { |
| 92 | t.Fatalf("with manifest: domain=%q absent=%v, want bab.oncarlos.test false", domain, absent) |
| 93 | } |
| 94 | |
| 95 | // A read that fails for any reason other than not-found is NOT absent. |
| 96 | fx.srv.cfg.Releases = unreadableStore{fx.rel} |
| 97 | if domain, absent := fx.srv.instanceDomainFor(ctx, "bab", "kass"); domain != "" || absent { |
| 98 | t.Fatalf("unreadable: domain=%q absent=%v, want \"\" false", domain, absent) |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | // TestAppPageNamesAPIBackedAttach is the regression at the level the operator |
| 103 | // met it: the app page's canary form, rendered, must hand back a command the |
| 104 | // reader can run from where they are. |
| 105 | func TestAppPageNamesAPIBackedAttach(t *testing.T) { |
| 106 | fx := newConsoleFixture(t) |
| 107 | fx.srv.cfg.AppsDomain = "oncarlos.test" |
| 108 | if err := provision.PutManifest(context.Background(), fx.rel, "bab", "kass", |
| 109 | provision.Manifest{Schema: 1, App: "kass", Domain: "bab.oncarlos.test", Channel: "stable"}); err != nil { |
| 110 | t.Fatal(err) |
| 111 | } |
| 112 | c := signInPaul(t, fx) |
| 113 | // The releases tab: the canary form lives there since the console's tab |
| 114 | // split, and so does the advice beside it. |
| 115 | _, _, body := get(t, c, fx.u("/apps/kass/releases")) |
| 116 | |
| 117 | // html/template escapes the <name> placeholder, so assert around it. |
| 118 | for _, want := range []string{ |
| 119 | "carlos instances create --app kass --host ", "bab.oncarlos.test --channel canary/", |
| 120 | } { |
| 121 | if !strings.Contains(body, want) { |
| 122 | t.Fatalf("/apps/kass/releases missing %q in:\n%s", want, body) |
| 123 | } |
| 124 | } |
| 125 | // Enabled already: no enable step, and never the box-local verb. |
| 126 | if strings.Contains(body, "carlos instances enable") { |
| 127 | t.Error("app page offers `instances enable` for an app that already has a manifest") |
| 128 | } |
| 129 | if strings.Contains(body, "carlos add ") { |
| 130 | t.Error("app page still sends the reader at box-local `carlos add`") |
| 131 | } |
| 132 | } |
| 133 | |