From 91dde6931eccbb0ce5bb71944022e630a9a49d0e Mon Sep 17 00:00:00 2001 From: Richard R Date: Sun, 7 Jun 2026 12:42:24 -0600 Subject: [PATCH] test: clean up playwright anonymous users --- playwright.config.ts | 3 ++ tests/global-teardown.ts | 89 +++++++++++++++++++++++++--------------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index d38c61e..1135007 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -45,6 +45,7 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'], + userAgent: `${devices['Desktop Chrome'].userAgent} OpenReader-Playwright/chromium`, extraHTTPHeaders: { 'x-openreader-test-namespace': 'chromium' }, }, }, @@ -53,6 +54,7 @@ export default defineConfig({ name: 'firefox', use: { ...devices['Desktop Firefox'], + userAgent: `${devices['Desktop Firefox'].userAgent} OpenReader-Playwright/firefox`, extraHTTPHeaders: { 'x-openreader-test-namespace': 'firefox' }, }, }, @@ -61,6 +63,7 @@ export default defineConfig({ name: 'webkit', use: { ...devices['Desktop Safari'], + userAgent: `${devices['Desktop Safari'].userAgent} OpenReader-Playwright/webkit`, extraHTTPHeaders: { 'x-openreader-test-namespace': 'webkit' }, }, }, diff --git a/tests/global-teardown.ts b/tests/global-teardown.ts index fa6356f..826a85e 100644 --- a/tests/global-teardown.ts +++ b/tests/global-teardown.ts @@ -1,11 +1,13 @@ import { ListObjectsV2Command } from '@aws-sdk/client-s3'; -import { and, eq, inArray, like } from 'drizzle-orm'; +import { and, eq, inArray, like, ne } from 'drizzle-orm'; import { db } from '../src/db'; -import { audiobooks, audiobookChapters, documents } from '../src/db/schema'; +import { audiobooks, audiobookChapters, documentPreviews, documents } from '../src/db/schema'; import { deleteDocumentPrefix } from '../src/lib/server/documents/blobstore'; import { deleteAudiobookPrefix } from '../src/lib/server/audiobooks/blobstore'; import { deleteTtsSegmentPrefix } from '../src/lib/server/tts/segments-blobstore'; import { getS3Client, getS3Config, isS3Configured } from '../src/lib/server/storage/s3'; +import * as authSchemaSqlite from '../src/db/schema_auth_sqlite'; +import * as authSchemaPostgres from '../src/db/schema_auth_postgres'; function chunk(items: T[], size: number): T[][] { if (items.length === 0) return []; @@ -66,46 +68,65 @@ function parseAudiobookScopeFromKey( } export default async function globalTeardown(): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const authSchema: any = process.env.POSTGRES_URL ? authSchemaPostgres : authSchemaSqlite; + const testSessions = await db + .select({ userId: authSchema.session.userId }) + .from(authSchema.session) + .where(like(authSchema.session.userAgent, '%OpenReader-Playwright/%')) as Array<{ userId: string }>; + const testUserIds = Array.from(new Set(testSessions.map((row) => row.userId))); + // Always clear namespaced unclaimed SQL rows from prior runs. await db.delete(audiobookChapters).where(like(audiobookChapters.userId, 'unclaimed::%')); await db.delete(audiobooks).where(like(audiobooks.userId, 'unclaimed::%')); await db.delete(documents).where(like(documents.userId, 'unclaimed::%')); + await db.delete(documentPreviews).where(ne(documentPreviews.namespace, '')); - if (!isS3Configured()) return; + if (isS3Configured()) { + const config = getS3Config(); + const docsNsRootPrefix = `${config.prefix}/documents_v1/ns/`; + const documentPreviewsNsRootPrefix = `${config.prefix}/document_previews_v1/ns/`; + const tempUploadsNsRootPrefix = `${config.prefix}/document_uploads_temp_v1/ns/`; + const audiobooksNsRootPrefix = `${config.prefix}/audiobooks_v1/ns/`; + const ttsSegmentsNsRootPrefixV1 = `${config.prefix}/tts_segments_v1/ns/`; + const ttsSegmentsNsRootPrefixV2 = `${config.prefix}/tts_segments_v2/ns/`; - const config = getS3Config(); - const docsNsRootPrefix = `${config.prefix}/documents_v1/ns/`; - const audiobooksNsRootPrefix = `${config.prefix}/audiobooks_v1/ns/`; - const ttsSegmentsNsRootPrefixV1 = `${config.prefix}/tts_segments_v1/ns/`; - const ttsSegmentsNsRootPrefixV2 = `${config.prefix}/tts_segments_v2/ns/`; - - // Remove SQL audiobook rows for namespaced objects (covers auth claim flows too). - const audiobookKeys = await listKeysByPrefix(audiobooksNsRootPrefix); - const byUser = new Map>(); - for (const key of audiobookKeys) { - const scope = parseAudiobookScopeFromKey(key, audiobooksNsRootPrefix); - if (!scope) continue; - let set = byUser.get(scope.userId); - if (!set) { - set = new Set(); - byUser.set(scope.userId, set); + // Remove SQL audiobook rows for namespaced objects (covers auth claim flows too). + const audiobookKeys = await listKeysByPrefix(audiobooksNsRootPrefix); + const byUser = new Map>(); + for (const key of audiobookKeys) { + const scope = parseAudiobookScopeFromKey(key, audiobooksNsRootPrefix); + if (!scope) continue; + let set = byUser.get(scope.userId); + if (!set) { + set = new Set(); + byUser.set(scope.userId, set); + } + set.add(scope.bookId); } - set.add(scope.bookId); + + for (const [userId, bookIds] of byUser) { + for (const ids of chunk(Array.from(bookIds), 200)) { + await db + .delete(audiobookChapters) + .where(and(eq(audiobookChapters.userId, userId), inArray(audiobookChapters.bookId, ids))); + await db + .delete(audiobooks) + .where(and(eq(audiobooks.userId, userId), inArray(audiobooks.id, ids))); + } + } + + await deleteDocumentPrefix(docsNsRootPrefix); + await deleteDocumentPrefix(documentPreviewsNsRootPrefix); + await deleteDocumentPrefix(tempUploadsNsRootPrefix); + await deleteAudiobookPrefix(audiobooksNsRootPrefix); + await deleteTtsSegmentPrefix(ttsSegmentsNsRootPrefixV1); + await deleteTtsSegmentPrefix(ttsSegmentsNsRootPrefixV2); } - for (const [userId, bookIds] of byUser) { - for (const ids of chunk(Array.from(bookIds), 200)) { - await db - .delete(audiobookChapters) - .where(and(eq(audiobookChapters.userId, userId), inArray(audiobookChapters.bookId, ids))); - await db - .delete(audiobooks) - .where(and(eq(audiobooks.userId, userId), inArray(audiobooks.id, ids))); - } + for (const ids of chunk(testUserIds, 200)) { + await db + .delete(authSchema.user) + .where(and(inArray(authSchema.user.id, ids), eq(authSchema.user.isAnonymous, true))); } - - await deleteDocumentPrefix(docsNsRootPrefix); - await deleteAudiobookPrefix(audiobooksNsRootPrefix); - await deleteTtsSegmentPrefix(ttsSegmentsNsRootPrefixV1); - await deleteTtsSegmentPrefix(ttsSegmentsNsRootPrefixV2); }