Revise user data cleanup logic to ensure proper cascading deletion of user-related database rows and S3 objects, including shared document and preview artifacts. Introduce explicit checks for last ownership before removing shared resources. Add TTS segment cache and audio cleanup to document and user deletion flows. Expand user data export to include TTS segment entries and audio, job events, document settings, and linked auth sessions. Update schema with ON DELETE CASCADE for userTtsChars and userJobEvents. Add new migration scripts and comprehensive unit tests for cleanup and export scenarios.
63 lines
2.4 KiB
TypeScript
63 lines
2.4 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
import type { Archiver } from 'archiver';
|
|
import { appendUserExportArchive } from '../../src/lib/server/user/data-export';
|
|
|
|
describe('user data export archive', () => {
|
|
test('includes user metadata and cached TTS audio without credential secrets', async () => {
|
|
const entries = new Map<string, unknown>();
|
|
const archive = {
|
|
append(value: unknown, options: { name: string }) {
|
|
entries.set(options.name, value);
|
|
return archive;
|
|
},
|
|
} as unknown as Archiver;
|
|
|
|
await appendUserExportArchive({
|
|
archive,
|
|
userId: 'user-1',
|
|
exportedAtMs: 123,
|
|
profileData: { user: { id: 'user-1', email: 'person@example.com' }, exportedAtMs: 123 },
|
|
preferences: { dataJson: '{}' },
|
|
readingHistory: [{ documentId: 'doc-1' }],
|
|
ttsUsage: [{ charCount: 10 }],
|
|
jobEvents: [{ action: 'pdf-layout' }],
|
|
documentSettings: [{ documentId: 'doc-1', dataJson: '{}' }],
|
|
authSessions: [{ id: 'session-1', ipAddress: null }],
|
|
linkedAccounts: [{ id: 'account-1', providerId: 'credential' }],
|
|
documents: [],
|
|
audiobooks: [],
|
|
audiobookChapters: [],
|
|
ttsSegmentEntries: [{ segmentEntryId: 'entry-1', documentId: 'doc-1' }],
|
|
ttsSegmentVariants: [{
|
|
segmentId: 'segment-1',
|
|
segmentEntryId: 'entry-1',
|
|
audioKey: 'private/audio/key',
|
|
audioFormat: 'mp3',
|
|
}],
|
|
getDocumentBlobStream: async () => new Uint8Array(),
|
|
listAudiobookObjects: async () => [],
|
|
getAudiobookObjectStream: async () => new Uint8Array(),
|
|
getTtsSegmentAudioStream: async () => new Uint8Array([1, 2, 3]),
|
|
});
|
|
|
|
expect(entries.has('document_settings.json')).toBe(true);
|
|
expect(entries.has('auth_sessions.json')).toBe(true);
|
|
expect(entries.has('linked_accounts.json')).toBe(true);
|
|
expect(entries.has('tts_segment_entries.json')).toBe(true);
|
|
expect(entries.has('tts_segment_variants.json')).toBe(true);
|
|
expect(entries.has('files/tts_segments/doc-1/segment-1.mp3')).toBe(true);
|
|
|
|
const manifest = JSON.parse(String(entries.get('export_manifest.json')));
|
|
expect(manifest).toMatchObject({
|
|
formatVersion: 3,
|
|
counts: {
|
|
ttsSegmentEntriesMetadata: 1,
|
|
ttsSegmentVariantsMetadata: 1,
|
|
ttsSegmentFiles: 1,
|
|
},
|
|
includes: {
|
|
credentialSecrets: false,
|
|
},
|
|
});
|
|
});
|
|
});
|