test: clean up playwright anonymous users

This commit is contained in:
Richard R 2026-06-07 12:42:24 -06:00
parent 4c20e21a45
commit 91dde6931e
2 changed files with 58 additions and 34 deletions

View file

@ -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' },
},
},

View file

@ -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<T>(items: T[], size: number): T[][] {
if (items.length === 0) return [];
@ -66,46 +68,65 @@ function parseAudiobookScopeFromKey(
}
export default async function globalTeardown(): Promise<void> {
// 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<string, Set<string>>();
for (const key of audiobookKeys) {
const scope = parseAudiobookScopeFromKey(key, audiobooksNsRootPrefix);
if (!scope) continue;
let set = byUser.get(scope.userId);
if (!set) {
set = new Set<string>();
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<string, Set<string>>();
for (const key of audiobookKeys) {
const scope = parseAudiobookScopeFromKey(key, audiobooksNsRootPrefix);
if (!scope) continue;
let set = byUser.get(scope.userId);
if (!set) {
set = new Set<string>();
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);
}