| index 94732bb8d..8b60110f5 100644 |
| --- a/internal/console/economicspass_test.go |
| +++ b/internal/console/economicspass_test.go |
| @@ -3,9 +3,12 @@ package console |
| import ( |
| "context" |
| "encoding/json" |
| + "os" |
| + "path/filepath" |
| "testing" |
| "time" |
| |
| + "github.com/carlosframework/platform/internal/release" |
| "github.com/carlosframework/platform/internal/usage" |
| ) |
| |
| @@ -25,8 +28,13 @@ func TestEconomicsPassWritesCensusOnce(t *testing.T) { |
| if err := json.Unmarshal(raw, &day); err != nil { |
| t.Fatal(err) |
| } |
| - if len(day.Accounts) == 0 { |
| - t.Fatal("census counted nothing — fixture routes should appear") |
| + // buildFixtures (view_test.go) seeds three routes with no Account set — |
| + // release.RouteAccount normalizes all of them to release.OpsAccount — two |
| + // distinct apps (kass, otherapp) and three routes total (kass instance, |
| + // kass static docs, otherapp instance). |
| + want := usage.CensusAccount{Apps: 2, Routes: 3} |
| + if got := day.Accounts[release.OpsAccount]; got != want { |
| + t.Fatalf("day.Accounts[%q] = %+v, want %+v", release.OpsAccount, got, want) |
| } |
| // Idempotence: a second run the same day must not error or rewrite. |
| before := string(raw) |
| @@ -39,16 +47,73 @@ func TestEconomicsPassWritesCensusOnce(t *testing.T) { |
| } |
| } |
| |
| +// TestCensusPassRefusesMissingRegistry: a console booted with an unmounted |
| +// or misconfigured registry volume must refuse the census rather than read |
| +// registry.Open's freshly-created, empty sqlite DB as "zero routes" and |
| +// write a permanent, uncorrectable all-zero day (create-only). It must also |
| +// leave no stray file behind at the path it didn't open. |
| +func TestCensusPassRefusesMissingRegistry(t *testing.T) { |
| + fx := newConsoleFixture(t) |
| + ctx := context.Background() |
| + missing := filepath.Join(t.TempDir(), "nonexistent-registry.db") |
| + fx.srv.cfg.Registry = missing |
| + |
| + if err := fx.srv.censusPass(ctx, "2026-08-11"); err == nil { |
| + t.Fatal("censusPass with a missing registry succeeded, want an error") |
| + } |
| + if _, err := os.Stat(missing); err == nil { |
| + t.Fatal("censusPass created a file at the missing registry path") |
| + } |
| + if _, _, err := fx.rel.Get(ctx, usage.CensusDayKey("2026-08-11")); err == nil { |
| + t.Fatal("censusPass with a missing registry wrote a census object") |
| + } |
| +} |
| + |
| func TestStoreAccountsListsAll(t *testing.T) { |
| fx := newConsoleFixture(t) |
| accts, err := fx.st.Accounts() |
| if err != nil { |
| t.Fatal(err) |
| } |
| - // The fixture mints at least one account; every row must carry id+sqid+name. |
| - for _, a := range accts { |
| + if len(accts) == 0 { |
| + t.Fatal("Accounts() returned nothing — the fixture's ops account should appear") |
| + } |
| + var ops *Account |
| + for i := range accts { |
| + a := accts[i] |
| if a.ID == 0 || a.Sqid == "" || a.Name == "" { |
| t.Fatalf("incomplete account row: %+v", a) |
| } |
| + if a.Sqid == release.OpsAccount { |
| + ops = &accts[i] |
| + } |
| + } |
| + if ops == nil { |
| + t.Fatal("Accounts() is missing the fixture's built-in ops account") |
| + } |
| + if ops.Name != "ops" { |
| + t.Fatalf("ops account name = %q, want %q", ops.Name, "ops") |
| + } |
| + |
| + // A soft-deleted account, tombstoned the way the store's own member-facing |
| + // delete path does it (DeleteAccount sets deleted_at), must disappear |
| + // from Accounts() — pinning the deviation from the brief's literal query. |
| + id := testAccountID() |
| + sqid := "test-deleted-1" |
| + now := time.Now() |
| + if err := fx.st.CreateAccount(id, sqid, "Deadco", "paul@keymail.dev", now); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := fx.st.DeleteAccount(id, now); err != nil { |
| + t.Fatal(err) |
| + } |
| + accts, err = fx.st.Accounts() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + for _, a := range accts { |
| + if a.Sqid == sqid { |
| + t.Fatalf("Accounts() returned a soft-deleted account: %+v", a) |
| + } |
| } |
| } |