import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { DuplicatiRepository } from '../src/db/duplicatiRepository.js'; import { openWritableSqlite } from '../src/db/sqlite.js'; import { createDuplicatiFixture } from './support/duplicatiFixture.js'; function createAppDatabasePath() { const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'duplicati-app-')); return { appDbPath: path.join(tempDirectory, 'app.sqlite'), cleanup() { fs.rmSync(tempDirectory, { recursive: true, force: true }); } }; } async function openAttachedRepository(sourceOverrides = {}) { const fixture = createDuplicatiFixture(sourceOverrides); const appDb = createAppDatabasePath(); const database = await openWritableSqlite(appDb.appDbPath); await database.attachDatabase('source', fixture.databasePath, { readOnly: true }); const sourceLayout = sourceOverrides.layout ?? 'file'; const repository = new DuplicatiRepository({ database, previewMaxBytes: 1024, source: { id: 'source-1', storagePath: fixture.databasePath, sha256: 'sha-demo', capabilities: { archiveEntryIndex: true, volumeCryptoCache: true } }, sourceLayout }); return { fixture, appDb, repository, database }; } test('listDirectory returns immediate directory and file children', async () => { const session = await openAttachedRepository(); try { const result = await session.repository.listDirectory({ apiPath: '/C:/media', snapshotId: 'latest' }); assert.equal(result.path, '/C:/media'); assert.equal(result.entries.length, 2); assert.deepEqual( result.entries.map((entry) => ({ type: entry.type, path: entry.path })), [ { type: 'dir', path: '/C:/media/movies' }, { type: 'file', path: '/C:/media/readme.txt' } ] ); } finally { await session.database.detachDatabase('source'); await session.database.close(); session.fixture.cleanup(); session.appDb.cleanup(); } }); test('getFileInfo expands ordered segments and required dblocks', async () => { const session = await openAttachedRepository(); try { const listing = await session.repository.listDirectory({ apiPath: '/C:/media/movies', snapshotId: 'latest' }); const fileId = listing.entries.find((entry) => entry.type === 'file')?.id; assert.ok(fileId); const result = await session.repository.getFileInfo(fileId); assert.equal(result.file.path, '/C:/media/movies/demo.mp4'); assert.equal(result.restorePlan.segmentCount, 3); assert.equal(result.restorePlan.blockSize, 102400); assert.equal(result.requiredDblocks.length, 1); assert.equal(result.requiredDblocks[0], 'duplicati-demo.dblock.zip.aes'); assert.equal(result.volumes[0].encryption.streamFormat, 'v2'); assert.equal(result.segments[0].logicalOffset, 0); assert.equal(result.segments[1].logicalOffset, 102400); assert.equal(result.segments[2].zip.compressionMethod, 'deflate'); } finally { await session.database.detachDatabase('source'); await session.database.close(); session.fixture.cleanup(); session.appDb.cleanup(); } }); test('listDirectory supports FileLookup plus PathPrefix based databases', async () => { const session = await openAttachedRepository({ layout: 'file_lookup' }); try { const result = await session.repository.listDirectory({ apiPath: '/source/media', snapshotId: 'latest' }); assert.equal(result.path, '/source/media'); assert.equal(result.entries.length, 2); assert.deepEqual( result.entries.map((entry) => ({ type: entry.type, path: entry.path })), [ { type: 'dir', path: '/source/media/movies' }, { type: 'file', path: '/source/media/readme.txt' } ] ); } finally { await session.database.detachDatabase('source'); await session.database.close(); session.fixture.cleanup(); session.appDb.cleanup(); } });