diff --git a/cmd/server/tests/test_helpers_test.go b/cmd/server/tests/test_helpers_test.go index a1179906..87ac26c9 100644 --- a/cmd/server/tests/test_helpers_test.go +++ b/cmd/server/tests/test_helpers_test.go @@ -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 " 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 func (s *TestServerSetup) Close() error { s.mu.Lock() diff --git a/cmd/server/tests/websocket_test.go b/cmd/server/tests/websocket_test.go index 451c4d8c..0839c091 100644 --- a/cmd/server/tests/websocket_test.go +++ b/cmd/server/tests/websocket_test.go @@ -241,6 +241,10 @@ func TestWebSocketUserScopedBroadcast(t *testing.T) { err = json.NewDecoder(collectionResp.Body).Decode(&collectionResult) require.NoError(t, err) 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 bookID := createTestMediaItemID(t, setup)