fix(tests): uuid-suffixed websocket collection self-deletes
TestWebSocketUserScopedBroadcast creates "Admin Collection <uuid8>" on the shared admin — deliberately NOT named with "test" or every run would 409 on UNIQUE(user_id, name) against stranded past-run rows (the comment in the test says exactly that), but it never deleted its row, so the same runs accrued ten "Admin Collection" forever-rows the name sweep can never touch. cleanupCollectionByID gives tests an owner-scoped RLS-bridge delete-at-backstory: same minimal contract as cleanupDeviceByIdentifier, for rows whose name must stay unique across runs. Deleted the ten standing rows (owner-scoped, exactly what the cleanup path does). What CANNOT be swept, audited safe: the dev admin's real hand-made collections (Saga reading-orders, Podcast 2, Smart Collection (Books)) carry no sweepable names; devices rely on identifier/token prefixes tests own exclusively; the rls-phone hand account persists because the transient-user sweep matches only @tests.bookhoard.internal / @example.com emails.
This commit is contained in:
1 parent
7d88ec5184
commit
06ec312863
2 files changed
+26
No files matched your search
@@ -111,6 +111,28 @@ func cleanupDeviceByIdentifier(t *testing.T, setup *TestServerSetup, identifier
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanupCollectionByID deletes one collections row at test end, scoped
|
||||||
|
// through the RLS bridge to its owner — collections RLS (2026-10-31)
|
||||||
|
// makes unscoped writes invisible (no-op). Use for a collection a test
|
||||||
|
// attaches to the shared admin whose name CANNOT carry the "test"
|
||||||
|
// marker the setup-time name sweep keys on (uuid-suffixed uniqueness
|
||||||
|
// against stale UNIQUE(user_id, name) rows from past runs). Rows that
|
||||||
|
// survive their test also get stranded forever — the 10 accumulated
|
||||||
|
// "Admin Collection <uuid>" rows were exactly this.
|
||||||
|
func cleanupCollectionByID(t *testing.T, setup *TestServerSetup, ownerID [16]byte, collectionID string) {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
cid, err := uuid.Parse(collectionID)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("cleanupCollectionByID: unparsable id %q — %v", collectionID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scope := rls.WithUser(context.Background(), pgtype.UUID{Bytes: ownerID, Valid: true})
|
||||||
|
if err := setup.DB.DeleteCollection(scope, pgtype.UUID{Bytes: cid, Valid: true}); err != nil {
|
||||||
|
t.Logf("cleanupCollectionByID: delete failed for %s — %v", collectionID, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Close cleans up all resources in the correct order
|
// Close cleans up all resources in the correct order
|
||||||
func (s *TestServerSetup) Close() error {
|
func (s *TestServerSetup) Close() error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
|||||||
@@ -241,6 +241,10 @@ func TestWebSocketUserScopedBroadcast(t *testing.T) {
|
|||||||
err = json.NewDecoder(collectionResp.Body).Decode(&collectionResult)
|
err = json.NewDecoder(collectionResp.Body).Decode(&collectionResult)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
collectionID := collectionResult["id"].(string)
|
collectionID := collectionResult["id"].(string)
|
||||||
|
// The uuid suffix keeps the name off every run's UNIQUE(user_id,name),
|
||||||
|
// which also keeps it out of the name-based cleanup sweep — self-delete
|
||||||
|
// instead (owner-scoped, exactly who created it).
|
||||||
|
cleanupCollectionByID(t, setup, getTestUserID(t, setup.DB), collectionID)
|
||||||
|
|
||||||
// Create a test book via API
|
// Create a test book via API
|
||||||
bookID := createTestMediaItemID(t, setup)
|
bookID := createTestMediaItemID(t, setup)
|
||||||
|
|||||||
Reference in new issue
Block a user