plan: console conversion extracted as restartWire for a pure test
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 file changed,
+61
−1
docs/superpowers/plans/2026-09-07-named-unit-config-delivery.md+61 −1
diff --git a/docs/superpowers/plans/2026-09-07-named-unit-config-delivery.md b/docs/superpowers/plans/2026-09-07-named-unit-config-delivery.md| index 1d1d96459..eb4485101 100644 |
| --- a/docs/superpowers/plans/2026-09-07-named-unit-config-delivery.md |
| +++ b/docs/superpowers/plans/2026-09-07-named-unit-config-delivery.md |
| @@ -2731,7 +2731,67 @@ Leave `restartCopyUnit` and the `Unit` fields in place (spec §3.4: an older con |
| - [ ] **Step 5: Console-side test** |
| -Find the console test that exercises `configRestartNotice` or decodes the config door's `restart` field (`grep -rn 'restart' internal/console/apiconfig_test.go` or the nearest `*_test.go` naming `apiConfigRestart`). Add one case that seeds the test registry with an un-opted addr route and a stamped socket route for the app, performs the write the door exposes, and asserts the decoded JSON has `undelivered: ["<host>"]` and one `blocked` entry whose `reason` contains `socket`. Follow that file's existing setup verbatim (server construction, registry path, bearer token). |
| +No console test names `apiConfigRestart` today, so extract the conversion into a pure function and test that. In `internal/console/apiconfig.go`, replace the tail of `configRestartNotice` (from `p := appconfig.AwaitingRestart(...)`) with: |
| + |
| +```go |
| + return restartWire(appconfig.AwaitingRestart(ctx, s.cfg.Releases, routes, acct, app, envName, keys)) |
| +} |
| + |
| +// restartWire is the wire shape of one RestartPending, Known: this box |
| +// looked. Pure, so the field-by-field copy is testable without a server. |
| +func restartWire(p appconfig.RestartPending) apiConfigRestart { |
| + out := apiConfigRestart{Known: true, Unresolved: p.Unresolved, Undelivered: p.Undelivered} |
| + for _, h := range p.Hosts { |
| + out.Hosts = append(out.Hosts, apiConfigRestartHost{Host: h.Host, Unit: h.Unit}) |
| + } |
| + for _, b := range p.Blocked { |
| + out.Blocked = append(out.Blocked, apiConfigBlockedHost{Host: b.Host, Reason: b.Reason}) |
| + } |
| + return out |
| +} |
| +``` |
| + |
| +Create `internal/console/apiconfig_restart_test.go`: |
| + |
| +```go |
| +package console |
| + |
| +import ( |
| + "encoding/json" |
| + "testing" |
| + |
| + "github.com/carlosframework/platform/internal/appconfig" |
| +) |
| + |
| +func TestRestartWireCarriesEveryList(t *testing.T) { |
| + got := restartWire(appconfig.RestartPending{ |
| + Hosts: []appconfig.RestartHost{{Host: "h", Unit: "u.service"}}, |
| + Unresolved: []string{"x"}, |
| + Undelivered: []string{"plain"}, |
| + Blocked: []appconfig.BlockedHost{{Host: "a", Reason: "socket"}}, |
| + }) |
| + raw, err := json.Marshal(got) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + var back map[string]any |
| + json.Unmarshal(raw, &back) |
| + for _, k := range []string{"known", "hosts", "unresolved", "undelivered", "blocked"} { |
| + if _, ok := back[k]; !ok { |
| + t.Errorf("wire lacks %q: %s", k, raw) |
| + } |
| + } |
| + // Empty lists are omitted so an older CLI sees exactly what it saw. |
| + raw, _ = json.Marshal(restartWire(appconfig.RestartPending{})) |
| + back = nil |
| + json.Unmarshal(raw, &back) |
| + for _, k := range []string{"undelivered", "blocked"} { |
| + if _, ok := back[k]; ok { |
| + t.Errorf("empty %q must be omitted: %s", k, raw) |
| + } |
| + } |
| +} |
| +``` |
| - [ ] **Step 6: Run everything** |