| index d113db12e..24a48a30b 100644 |
| --- a/internal/console/apiretry_test.go |
| +++ b/internal/console/apiretry_test.go |
| @@ -3,7 +3,9 @@ package console |
| import ( |
| "context" |
| "encoding/json" |
| + "fmt" |
| "net/http" |
| + "strings" |
| "testing" |
| "time" |
| |
| @@ -39,8 +41,8 @@ func TestAPIInstanceRetryWritesARetryRequest(t *testing.T) { |
| token := cliToken(t, fx, "paul@keymail.dev", "code-host-retry") |
| u := fx.ts.URL + "/api/cli/apps/" + fx.sqid + "/kass/instances/" + host + "/retry" |
| |
| - if code, _, body := rawReq(t, http.MethodPost, u, token, `{}`); code != http.StatusConflict { |
| - t.Fatalf("no report = %d %s, want 409", code, body) |
| + if code, _, body := rawReq(t, http.MethodPost, u, token, `{}`); code != http.StatusConflict || !strings.Contains(body, fmt.Sprintf(apiRetryNotHeld, host)) { |
| + t.Fatalf("no report = %d %s, want 409 naming %s as not held", code, body, host) |
| } |
| writeHeldBox(t, fx, host, "kass", "v2") |
| code, _, body := rawReq(t, http.MethodPost, u, token, `{}`) |
| @@ -70,13 +72,16 @@ func TestAPIInstanceRetryIgnoresAnotherAppsReport(t *testing.T) { |
| writeHeldBox(t, fx, host, "otherapp", "z2") |
| token := cliToken(t, fx, "paul@keymail.dev", "code-host-retry-x") |
| u := fx.ts.URL + "/api/cli/apps/" + fx.sqid + "/kass/instances/" + host + "/retry" |
| - if code, _, body := rawReq(t, http.MethodPost, u, token, `{}`); code != http.StatusConflict { |
| - t.Fatalf("another app's hold = %d %s, want 409", code, body) |
| + if code, _, body := rawReq(t, http.MethodPost, u, token, `{}`); code != http.StatusConflict || !strings.Contains(body, fmt.Sprintf(apiRetryNotHeld, host)) { |
| + t.Fatalf("another app's hold = %d %s, want 409 naming %s as not held", code, body, host) |
| } |
| } |
| |
| -// A box that could not arm its scheduler reports no per-host state; the host is |
| -// not held, whatever else its view says. |
| +// A box that could not arm its scheduler reports no per-host state: its view is |
| +// built with Unavailable and no HeldBy, so the host is not held. This pins that |
| +// whole path to the 409. It cannot tell retryHostBuild's own Unavailable check |
| +// from the HeldBy check, because loadBoxJoin never builds a view carrying both; |
| +// the check is a guard for a join that someday does. |
| func TestAPIInstanceRetryRefusesAnUnavailableBox(t *testing.T) { |
| fx := newConsoleFixture(t) |
| const host = "web.kass.carlosapp.dev" |
| @@ -91,7 +96,32 @@ func TestAPIInstanceRetryRefusesAnUnavailableBox(t *testing.T) { |
| } |
| token := cliToken(t, fx, "paul@keymail.dev", "code-host-retry-u") |
| u := fx.ts.URL + "/api/cli/apps/" + fx.sqid + "/kass/instances/" + host + "/retry" |
| - if code, _, body := rawReq(t, http.MethodPost, u, token, `{}`); code != http.StatusConflict { |
| - t.Fatalf("unavailable box = %d %s, want 409", code, body) |
| + code, _, body := rawReq(t, http.MethodPost, u, token, `{}`) |
| + if code != http.StatusConflict || !strings.Contains(body, fmt.Sprintf(apiRetryNotHeld, host)) { |
| + t.Fatalf("unavailable box = %d %s, want 409 naming %s as not held", code, body, host) |
| + } |
| +} |
| + |
| +// A box that reports the host and holds nothing back: there is nothing to |
| +// retry, and no request may be written. |
| +func TestAPIInstanceRetryRefusesAHostThatIsNotHeld(t *testing.T) { |
| + fx := newConsoleFixture(t) |
| + const host = "web.kass.carlosapp.dev" |
| + sibling(t, fx, "kass", host, "stable") |
| + box := status.CollectBox(nil, status.Probes{MachineID: "box1"}, time.Now()) |
| + box.Metrics = &status.BoxMetrics{Rollout: &status.RolloutReport{Hosts: map[string]status.HostRollout{ |
| + host: {Account: fx.sqid, App: "kass", Installed: "v2", Serving: "v2", Good: "v2", Awake: true}, |
| + }}} |
| + if err := status.WriteBox(context.Background(), fx.rel, box); err != nil { |
| + t.Fatal(err) |
| + } |
| + token := cliToken(t, fx, "paul@keymail.dev", "code-host-retry-n") |
| + u := fx.ts.URL + "/api/cli/apps/" + fx.sqid + "/kass/instances/" + host + "/retry" |
| + code, _, body := rawReq(t, http.MethodPost, u, token, `{}`) |
| + if code != http.StatusConflict || !strings.Contains(body, fmt.Sprintf(apiRetryNotHeld, host)) { |
| + t.Fatalf("a healthy host = %d %s, want 409 naming %s as not held", code, body, host) |
| + } |
| + if g, found, _ := release.LoadRestartScopes(context.Background(), fx.rel, fx.sqid, "kass"); found && len(g.Requests) != 0 { |
| + t.Fatalf("a retry was written for a host that is not held: %+v", g.Requests) |
| } |
| } |