openreader/tests/unit/tts-segments-blobstore.vitest.spec.ts
Richard R 9a8bc060d9 feat(data): enhance anonymous claim to support document settings and TTS segment copy
Expand the anonymous data claim process to include document settings transfer and TTS segment S3 prefix copying. Remove foreign key constraints from user_tts_chars to allow non-user buckets. Update claim modal and onboarding flow to display claimed document settings. Refactor TTS char count transfer to merge all dates and fix upsert logic. Add S3 copy utility for TTS segments and corresponding tests. Update migrations and schema to reflect relaxed constraints.
2026-06-06 19:12:40 -06:00

76 lines
2.4 KiB
TypeScript

import { beforeEach, describe, expect, test, vi } from 'vitest';
const mocks = vi.hoisted(() => ({
send: vi.fn(),
}));
vi.mock('@/lib/server/storage/s3', () => ({
getS3Config: () => ({ bucket: 'test-bucket' }),
getS3Client: () => ({ send: mocks.send }),
getS3ProxyClient: () => ({ send: mocks.send }),
}));
import { copyTtsSegmentPrefix, deleteTtsSegmentPrefix } from '../../src/lib/server/tts/segments-blobstore';
describe('TTS segment blob cleanup', () => {
beforeEach(() => {
mocks.send.mockReset();
});
test('counts successful quiet deletes by requested object count', async () => {
mocks.send
.mockResolvedValueOnce({
Contents: [{ Key: 'prefix/a.mp3' }, { Key: 'prefix/b.mp3' }],
IsTruncated: false,
})
.mockResolvedValueOnce({});
await expect(deleteTtsSegmentPrefix('prefix/')).resolves.toBe(2);
});
test('fails cleanup when storage reports per-object deletion errors', async () => {
mocks.send
.mockResolvedValueOnce({
Contents: [{ Key: 'prefix/a.mp3' }],
IsTruncated: false,
})
.mockResolvedValueOnce({
Errors: [{ Key: 'prefix/a.mp3', Code: 'AccessDenied' }],
});
await expect(deleteTtsSegmentPrefix('prefix/')).rejects.toThrow(
'Failed deleting 1 TTS segment audio objects',
);
});
test('copies a user-scoped prefix without deleting its source objects', async () => {
mocks.send
.mockResolvedValueOnce({
Contents: [{ Key: 'source/doc/audio.mp3' }],
IsTruncated: false,
})
.mockResolvedValueOnce({});
await expect(copyTtsSegmentPrefix('source/doc/', 'destination/doc/')).resolves.toBe(1);
const copyCommand = mocks.send.mock.calls[1]?.[0];
expect(copyCommand.constructor.name).toBe('CopyObjectCommand');
expect(copyCommand.input).toMatchObject({
Key: 'destination/doc/audio.mp3',
CopySource: 'test-bucket/source/doc/audio.mp3',
});
expect(mocks.send).toHaveBeenCalledTimes(2);
});
test('does not delete source objects when a TTS prefix copy fails', async () => {
mocks.send
.mockResolvedValueOnce({
Contents: [{ Key: 'source/doc/audio.mp3' }],
IsTruncated: false,
})
.mockRejectedValueOnce(new Error('copy failed'));
await expect(copyTtsSegmentPrefix('source/doc/', 'destination/doc/')).rejects.toThrow('copy failed');
expect(mocks.send).toHaveBeenCalledTimes(2);
});
});