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.
104 lines
3.9 KiB
TypeScript
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);
|
|
});
|
|
});
|