| index 3b778b001..594b4ce6f 100644 |
| --- a/internal/console/store_test.go |
| +++ b/internal/console/store_test.go |
| @@ -479,6 +479,49 @@ func TestTrashAppRowRoundTrip(t *testing.T) { |
| // TestAppCountCountsTrashedRows: account deletion's pre-flight must keep |
| // refusing while a trashed app's payload still exists, so the count is |
| // deliberately unfiltered by deleted_at. |
| +// TestPurgeAppRowRefusesLiveRow: purge is the one irreversible verb here, so a |
| +// live row must be structurally unpurgeable even if a caller gets confused — |
| +// the console row and a bucket marker can diverge (a restore that raced or |
| +// out-lived a failed marker delete elsewhere), and that must never be the |
| +// thing that frees a serving app's name. |
| +func TestPurgeAppRowRefusesLiveRow(t *testing.T) { |
| + s := openTestStore(t) |
| + now := time.Now() |
| + acct := testAccountID() |
| + if _, err := s.CreateAppIn(acct, "kass", "paul@keymail.dev", now); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := s.TrashAppRow(acct, "kass", now); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := s.RestoreAppRow(acct, "kass"); err != nil { |
| + t.Fatal(err) |
| + } |
| + // Live again: PurgeAppRow must refuse, not free the name. |
| + if err := s.PurgeAppRow(acct, "kass"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if has, err := s.HasAppIn(acct, "kass"); err != nil || !has { |
| + t.Fatalf("HasAppIn after purging a restored (live) row = %v %v, want true — the row must survive", has, err) |
| + } |
| + if created, err := s.CreateAppIn(acct, "kass", "bob@keymail.dev", now); err != nil || created { |
| + t.Fatalf("CreateAppIn after purging a live row = %v %v, want false — the name is still reserved", created, err) |
| + } |
| + // Trashed: PurgeAppRow now takes effect. |
| + if err := s.TrashAppRow(acct, "kass", now); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := s.PurgeAppRow(acct, "kass"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if has, err := s.HasTrashedAppIn(acct, "kass"); err != nil || has { |
| + t.Fatalf("HasTrashedAppIn after purging a trashed row = %v %v, want false — the row should be gone", has, err) |
| + } |
| + if created, err := s.CreateAppIn(acct, "kass", "bob@keymail.dev", now); err != nil || !created { |
| + t.Fatalf("CreateAppIn after purge = %v %v, want true — the name should be free", created, err) |
| + } |
| +} |
| + |
| func TestAppCountCountsTrashedRows(t *testing.T) { |
| s := openTestStore(t) |
| now := time.Now() |