a6cbcc273e
Made-with: Cursor
120 lines
3.4 KiB
TypeScript
120 lines
3.4 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
|
|
import { PROJECT_SCHEMA_VERSION, type Project } from '../../shared/types';
|
|
import type { AssetId } from '../../shared/types/ids';
|
|
|
|
import { collectReferencedAssetIds, reconcileAssetFiles } from './assetPrune';
|
|
|
|
void test('collectReferencedAssetIds: превью, видео и аудио', () => {
|
|
const p = {
|
|
scenes: {
|
|
s1: {
|
|
previewAssetId: 'pr' as AssetId,
|
|
media: {
|
|
videos: ['v1' as AssetId],
|
|
audios: [{ assetId: 'a1' as AssetId, autoplay: true, loop: true }],
|
|
},
|
|
},
|
|
},
|
|
} as unknown as Project;
|
|
const s = collectReferencedAssetIds(p);
|
|
assert.deepEqual([...s].sort(), ['a1', 'pr', 'v1'].sort());
|
|
});
|
|
|
|
void test('reconcileAssetFiles: снимает осиротевшие assets и удаляет файлы', async () => {
|
|
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'dnd-asset-prune-'));
|
|
const relPath = 'assets/orphan.bin';
|
|
await fs.mkdir(path.join(tmp, 'assets'), { recursive: true });
|
|
await fs.writeFile(path.join(tmp, relPath), Buffer.from([1, 2, 3]));
|
|
|
|
const asset = {
|
|
id: 'orph' as AssetId,
|
|
type: 'audio' as const,
|
|
mime: 'audio/wav',
|
|
originalName: 'x.wav',
|
|
relPath,
|
|
sha256: 'a',
|
|
sizeBytes: 3,
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
|
|
const base = {
|
|
id: 'p1',
|
|
meta: {
|
|
name: 't',
|
|
fileBaseName: 't',
|
|
createdAt: '',
|
|
updatedAt: '',
|
|
createdWithAppVersion: '1',
|
|
appVersion: '1',
|
|
schemaVersion: PROJECT_SCHEMA_VERSION,
|
|
},
|
|
currentSceneId: null,
|
|
currentGraphNodeId: null,
|
|
sceneGraphNodes: [],
|
|
sceneGraphEdges: [],
|
|
} as unknown as Project;
|
|
|
|
const prev: Project = {
|
|
...base,
|
|
scenes: {},
|
|
assets: { orphan: asset } as Project['assets'],
|
|
};
|
|
const next: Project = {
|
|
...base,
|
|
scenes: {},
|
|
assets: { orphan: asset } as Project['assets'],
|
|
};
|
|
|
|
const out = await reconcileAssetFiles(prev, next, tmp);
|
|
assert.ok(!('orphan' in out.assets));
|
|
await assert.rejects(() => fs.stat(path.join(tmp, relPath)));
|
|
});
|
|
|
|
void test('reconcileAssetFiles: удаляет файл при исключении id из assets', async () => {
|
|
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'dnd-asset-prune-'));
|
|
const relPath = 'assets/gone.bin';
|
|
await fs.mkdir(path.join(tmp, 'assets'), { recursive: true });
|
|
await fs.writeFile(path.join(tmp, relPath), Buffer.from([9]));
|
|
|
|
const asset = {
|
|
id: 'gone' as AssetId,
|
|
type: 'audio' as const,
|
|
mime: 'audio/wav',
|
|
originalName: 'x.wav',
|
|
relPath,
|
|
sha256: 'b',
|
|
sizeBytes: 1,
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
|
|
const base = {
|
|
id: 'p1',
|
|
meta: {
|
|
name: 't',
|
|
fileBaseName: 't',
|
|
createdAt: '',
|
|
updatedAt: '',
|
|
createdWithAppVersion: '1',
|
|
appVersion: '1',
|
|
schemaVersion: PROJECT_SCHEMA_VERSION,
|
|
},
|
|
scenes: {},
|
|
currentSceneId: null,
|
|
currentGraphNodeId: null,
|
|
sceneGraphNodes: [],
|
|
sceneGraphEdges: [],
|
|
} as unknown as Project;
|
|
|
|
const prev: Project = { ...base, assets: { gone: asset } as Project['assets'] };
|
|
const next: Project = { ...base, assets: {} as Project['assets'] };
|
|
|
|
const out = await reconcileAssetFiles(prev, next, tmp);
|
|
assert.deepEqual(out.assets, {});
|
|
await assert.rejects(() => fs.stat(path.join(tmp, relPath)));
|
|
});
|