openreader/tests/unit/legacy-fs-claim-cleanup.vitest.spec.ts
Richard R e0cd9bc270 feat(user): remove legacy local file deletion and add cleanup for claimed FS sources
Eliminate the unused --delete-local option and related code from the
filesystem migration script to prevent accidental local data removal.
Introduce a cleanupClaimedLegacyFsSources utility for removing legacy
filesystem sources after claim, and integrate it into the user claim
flow. Add unit tests to verify the cleanup logic.
2026-06-03 14:35:27 -06:00

104 lines
3.9 KiB
TypeScript

import { createHash } from 'node:crypto';
import fs from 'node:fs';
import fsp from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
const hoisted = vi.hoisted(() => ({
roots: {
docstoreDir: '',
documentsDir: '',
audiobooksDir: '',
},
}));
vi.mock('@/lib/server/storage/docstore-legacy', () => ({
get DOCSTORE_DIR() {
return hoisted.roots.docstoreDir;
},
get DOCUMENTS_V1_DIR() {
return hoisted.roots.documentsDir;
},
get AUDIOBOOKS_V1_DIR() {
return hoisted.roots.audiobooksDir;
},
}));
describe('cleanupClaimedLegacyFsSources', () => {
let tempRoot = '';
beforeEach(async () => {
tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'openreader-legacy-cleanup-'));
hoisted.roots = {
docstoreDir: path.join(tempRoot, 'docstore'),
documentsDir: path.join(tempRoot, 'documents_v1'),
audiobooksDir: path.join(tempRoot, 'audiobooks_v1'),
};
await fsp.mkdir(hoisted.roots.docstoreDir, { recursive: true });
await fsp.mkdir(hoisted.roots.documentsDir, { recursive: true });
await fsp.mkdir(hoisted.roots.audiobooksDir, { recursive: true });
});
afterEach(async () => {
await fsp.rm(tempRoot, { recursive: true, force: true });
});
test('removes only claimed legacy document and audiobook sources', async () => {
const namespace = 'ns1';
const docsNsDir = path.join(hoisted.roots.documentsDir, namespace);
const docstoreNsDir = path.join(hoisted.roots.docstoreDir, namespace);
const audiobooksNsDir = path.join(hoisted.roots.audiobooksDir, namespace);
await fsp.mkdir(docsNsDir, { recursive: true });
await fsp.mkdir(docstoreNsDir, { recursive: true });
await fsp.mkdir(audiobooksNsDir, { recursive: true });
const doc1Bytes = Buffer.from('claimed-doc-1');
const doc1Id = createHash('sha256').update(doc1Bytes).digest('hex');
const doc1Path = path.join(docsNsDir, 'legacy-random-name.pdf');
await fsp.writeFile(doc1Path, doc1Bytes);
const doc2Bytes = Buffer.from('claimed-doc-2');
const doc2Id = createHash('sha256').update(doc2Bytes).digest('hex');
const doc2MetaPath = path.join(docstoreNsDir, 'legacy-doc-2.json');
const doc2ContentPath = path.join(docstoreNsDir, 'legacy-doc-2.pdf');
await fsp.writeFile(doc2MetaPath, JSON.stringify({
id: 'legacy-doc-2',
name: 'legacy-doc-2.pdf',
size: doc2Bytes.length,
lastModified: Date.now(),
type: 'pdf',
}));
await fsp.writeFile(doc2ContentPath, doc2Bytes);
const keepBytes = Buffer.from('keep-doc');
const keepId = createHash('sha256').update(keepBytes).digest('hex');
const keepPath = path.join(docsNsDir, `${keepId}__keep.pdf`);
await fsp.writeFile(keepPath, keepBytes);
const claimedBookDir = path.join(audiobooksNsDir, 'book-1-audiobook');
const keepBookDir = path.join(docstoreNsDir, 'book-2-audiobook');
await fsp.mkdir(claimedBookDir, { recursive: true });
await fsp.mkdir(keepBookDir, { recursive: true });
await fsp.writeFile(path.join(claimedBookDir, '0001__Chapter%201.mp3'), 'audio');
await fsp.writeFile(path.join(keepBookDir, '0001__Chapter%201.mp3'), 'audio');
const { cleanupClaimedLegacyFsSources } = await import('../../src/lib/server/user/legacy-fs-claim-cleanup');
const result = await cleanupClaimedLegacyFsSources({
documentIds: [doc1Id, doc2Id],
audiobookIds: ['book-1'],
namespace,
});
expect(result).toEqual({
deletedDocumentPaths: 3,
deletedAudiobookDirs: 1,
});
expect(fs.existsSync(doc1Path)).toBe(false);
expect(fs.existsSync(doc2MetaPath)).toBe(false);
expect(fs.existsSync(doc2ContentPath)).toBe(false);
expect(fs.existsSync(keepPath)).toBe(true);
expect(fs.existsSync(claimedBookDir)).toBe(false);
expect(fs.existsSync(keepBookDir)).toBe(true);
});
});