| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "fmt" |
| 6 | "net/http" |
| 7 | "net/url" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | "time" |
| 11 | |
| 12 | "github.com/carlosframework/platform/internal/registry" |
| 13 | "github.com/carlosframework/platform/internal/rolloutwords" |
| 14 | "github.com/carlosframework/platform/internal/status" |
| 15 | ) |
| 16 | |
| 17 | // Astra plan 6 R1 #8: the retry confirmation promises no fallback. A retry |
| 18 | // that fails in hold mode leaves the instance down, so "stays on its last |
| 19 | // good build" was false there; the host is held again either way. |
| 20 | func TestBrowserRetryConfirmationPromisesNoFallback(t *testing.T) { |
| 21 | fx := heldFixture(t) |
| 22 | c := signInPaul(t, fx) |
| 23 | csrf := sessionToken(t, fx, c) |
| 24 | code, _, body := postForm(t, c, fx.u("/apps/kass/instances/"+heldHost+"/retry"), url.Values{"csrf": {csrf}}) |
| 25 | want := "This clears the failure hold on " + heldHost + ". Its next attempt at v2 runs as a canary. If that fails too, " + heldHost + " is held again." |
| 26 | if code != http.StatusOK || !strings.Contains(body, want) { |
| 27 | t.Fatalf("confirm = %d, lacks %q", code, want) |
| 28 | } |
| 29 | if strings.Contains(body, "last good build") { |
| 30 | t.Fatal("the confirmation still promises a fallback to the last good build") |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | // Astra plan 6 R1 #8: a hold-mode failure leaves nothing serving |
| 35 | // (attempt down), so the page's hold row must not say the instance "stays |
| 36 | // on" a build there. Both renders: a held instance that serves its good |
| 37 | // build keeps the hold text; a down one gets the down text. |
| 38 | func TestInstancePageHoldTextTellsDownFromHeld(t *testing.T) { |
| 39 | fx := newConsoleFixture(t) |
| 40 | const down = "down.kass.carlosapp.dev" |
| 41 | for _, h := range []string{heldHost, down} { |
| 42 | fx.addRoute(t, registry.Route{Host: h, App: "kass", Account: fx.sqid, Kind: "instance", Status: "ready", Version: "v2", Channel: "stable"}) |
| 43 | sibling(t, fx, "kass", h, "stable") |
| 44 | } |
| 45 | box := status.CollectBox(nil, status.Probes{MachineID: "box1"}, time.Now()) |
| 46 | box.Metrics = &status.BoxMetrics{Rollout: &status.RolloutReport{Hosts: map[string]status.HostRollout{ |
| 47 | heldHost: {Account: fx.sqid, App: "kass", Installed: "v2", Serving: "v1", Good: "v1", Awake: true, HeldBy: "v2", Reason: status.ReasonHealth, |
| 48 | HeldEpoch: heldEpoch, HeldBook: heldBook, RetryV: status.HostRetryV}, |
| 49 | down: {Account: fx.sqid, App: "kass", Installed: "v2", Good: "v1", Attempt: status.AttemptDown, AttemptBuild: "v2", |
| 50 | HeldBy: "v2", Reason: status.ReasonExit, HeldEpoch: heldEpoch, HeldBook: heldBook, RetryV: status.HostRetryV}, |
| 51 | }}} |
| 52 | if err := status.WriteBox(context.Background(), fx.rel, box); err != nil { |
| 53 | t.Fatal(err) |
| 54 | } |
| 55 | c := signInPaul(t, fx) |
| 56 | _, _, held := get(t, c, fx.u("/apps/kass/instances/"+heldHost)) |
| 57 | if want := fmt.Sprintf(swapCopy.HoldText, "v2", rolloutwords.Reason(status.ReasonHealth), "v1"); !strings.Contains(held, want) { |
| 58 | t.Errorf("held page lacks %q", want) |
| 59 | } |
| 60 | _, _, body := get(t, c, fx.u("/apps/kass/instances/"+down)) |
| 61 | want := "Build v2 failed in hold mode on this box, and nothing is serving this instance. Roll back with carlos rollback, or retry it." |
| 62 | if !strings.Contains(body, want) { |
| 63 | t.Errorf("down page lacks %q", want) |
| 64 | } |
| 65 | if strings.Contains(body, "stays on") { |
| 66 | t.Error("the down page says the instance stays on a build") |
| 67 | } |
| 68 | if !strings.Contains(body, `/instances/`+down+`/retry"`) { |
| 69 | t.Error("the down page lost its Retry") |
| 70 | } |
| 71 | } |
| 72 | |