| index 4cbb96f35..174049ca8 100644 |
| --- a/internal/host/retention_test.go |
| +++ b/internal/host/retention_test.go |
| @@ -87,6 +87,63 @@ func TestRetentionPassPrunesEligibleAndProtectsBoxPins(t *testing.T) { |
| } |
| } |
| |
| +// TestRetentionPassResolvesEmptyAccountRoutesViaRouteAccount covers the |
| +// review finding: a route row with an EMPTY Account column (unstamped by |
| +// the domains pass) must still resolve to release.OpsAccount ("bab"), the |
| +// same resolution every other box-side release path uses |
| +// (release.RouteAccount, domains/reconcile.go's resolveRouteAccount) — |
| +// never be silently skipped, and never key its pins under "". |
| +func TestRetentionPassResolvesEmptyAccountRoutesViaRouteAccount(t *testing.T) { |
| + ctx := context.Background() |
| + dir := t.TempDir() |
| + s := release.NewDirStore(dir) |
| + now := time.Now() |
| + base := now.Add(-30 * 24 * time.Hour) |
| + vname := func(i int) string { return "v" + string(rune('0'+i/10)) + string(rune('0'+i%10)) } |
| + for i := 1; i <= 25; i++ { |
| + stampKey(t, s, dir, release.ManifestKey(release.OpsAccount, "kass", vname(i)), base.Add(time.Duration(i)*time.Hour)) |
| + } |
| + _, pub := retentionKeys(t) |
| + if err := release.SetRetention(ctx, s, release.OpsAccount, "kass", 20, "test", now); err != nil { |
| + t.Fatal(err) |
| + } |
| + reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer reg.Close() |
| + // Account column left empty — the state every route is in until the |
| + // domains pass stamps it (reconcile.go: "A route with an empty account |
| + // column resolves to ops until 1b writes the account the BUCKET |
| + // asserts"). v03/v02 are this box's pins, both outside keep-20. |
| + if err := reg.Upsert(registry.Route{ |
| + Host: "kass.example.com", Kind: "instance", App: "kass", |
| + Version: "v03", PrevVersion: "v02", Status: "ready", |
| + }); err != nil { |
| + t.Fatal(err) |
| + } |
| + |
| + if err := retentionPass(ctx, s, pub, reg); err != nil { |
| + t.Fatal(err) |
| + } |
| + |
| + // The bab/kass policy must have been enforced — an empty Account |
| + // column must not cause the app to be silently skipped. |
| + if _, _, err := s.Get(ctx, release.ManifestKey(release.OpsAccount, "kass", "v01")); err == nil { |
| + t.Fatalf("v01 still present, want pruned (empty-account route was not resolved to %s)", release.OpsAccount) |
| + } |
| + if _, _, err := s.Get(ctx, release.ManifestKey(release.OpsAccount, "kass", "v04")); err == nil { |
| + t.Fatalf("v04 still present, want pruned") |
| + } |
| + // v02/v03 are the empty-account route's Version/PrevVersion pins — |
| + // must survive despite ranking outside keep-20. |
| + for _, kept := range []string{"v02", "v03"} { |
| + if _, _, err := s.Get(ctx, release.ManifestKey(release.OpsAccount, "kass", kept)); err != nil { |
| + t.Fatalf("%s pruned, want kept as an empty-account route's pin: %v", kept, err) |
| + } |
| + } |
| +} |
| + |
| func TestRetentionPassSkipsAppsWithoutAPolicy(t *testing.T) { |
| ctx := context.Background() |
| dir := t.TempDir() |