'use strict'; const assert = require('node:assert/strict'); const crypto = require('crypto'); const fs = require('fs'); const os = require('os'); const path = require('path'); const { Readable } = require('stream'); const test = require('node:test'); const { createImportUploadService, sanitizeRelativePath } = require('../../src/services/import-upload-service'); const digest = value => crypto.createHash('sha256').update(value).digest('hex'); test('folder upload sanitizes paths, resumes exact offsets and survives service restart', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-upload-')); try { const service = createImportUploadService({ root, limits: { maxChunkBytes: 8 } }); const created = service.create({ owner: 'alice', files: [{ relativePath: 'Game/base.pkg', size: 6, sha256: digest('abcdef') }] }); assert.equal(Object.hasOwn(created, 'owner'), false); assert.equal(JSON.stringify(created).includes(root), false); const file = created.files[0]; const first = await service.writeChunk({ id: created.id, fileId: file.id, owner: 'alice', offset: 0, length: 3, chunkSha256: digest('abc'), stream: Readable.from('abc') }); assert.equal(first.files[0].offset, 3); const restarted = createImportUploadService({ root, limits: { maxChunkBytes: 8 } }); assert.equal(restarted.get(created.id, 'alice').files[0].offset, 3); await assert.rejects( restarted.writeChunk({ id: created.id, fileId: file.id, owner: 'alice', offset: 2, length: 3, chunkSha256: digest('def'), stream: Readable.from('def') }), error => error.code === 'UPLOAD_OFFSET' && error.expectedOffset === 3, ); const completed = await restarted.writeChunk({ id: created.id, fileId: file.id, owner: 'alice', offset: 3, length: 3, chunkSha256: digest('def'), stream: Readable.from('def') }); assert.equal(completed.state, 'uploaded'); assert.equal(completed.bytesReceived, 6); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); test('hash failures roll back, quotas are enforced and hostile paths are rejected', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-upload-policy-')); try { const service = createImportUploadService({ root, limits: { maxFiles: 2, maxFileBytes: 8, maxSessionBytes: 10, maxUserBytes: 12, maxGlobalBytes: 14, maxChunkBytes: 8 } }); for (const value of ['../escape.pkg', '/absolute.pkg', 'C:\\escape.pkg', 'folder//file.pkg', 'folder/CON']) assert.throws(() => sanitizeRelativePath(value)); const created = service.create({ owner: 'alice', files: [{ relativePath: 'safe/file.pkg', size: 4 }] }); const file = created.files[0]; await assert.rejects( service.writeChunk({ id: created.id, fileId: file.id, owner: 'alice', offset: 0, length: 4, chunkSha256: digest('nope'), stream: Readable.from('data') }), error => error.code === 'UPLOAD_HASH', ); assert.equal(service.get(created.id, 'alice').files[0].offset, 0); assert.throws(() => service.create({ owner: 'alice', files: [{ relativePath: 'too-large.pkg', size: 9 }] }), error => error.code === 'UPLOAD_QUOTA'); assert.throws(() => service.create({ owner: 'alice', files: [{ relativePath: 'a.pkg', size: 5 }, { relativePath: 'A.pkg', size: 5 }] }), error => error.code === 'UPLOAD_COLLISION'); service.create({ owner: 'alice', files: [{ relativePath: 'second.pkg', size: 8 }] }); assert.throws(() => service.create({ owner: 'alice', files: [{ relativePath: 'over-user.pkg', size: 1 }] }), error => error.code === 'UPLOAD_QUOTA'); assert.throws(() => service.get(created.id, 'bob'), error => error.code === 'UPLOAD_NOT_FOUND'); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); test('cancel and expiry remove partial content without exposing or mutating a library', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-upload-lifecycle-')); let clock = 1000; const events = []; try { const service = createImportUploadService({ root, now: () => clock, emit: (channel, payload) => events.push({ channel, payload }), limits: { ttlMs: 10, maxChunkBytes: 8 } }); const cancelled = service.create({ owner: 'alice', files: [{ relativePath: 'cancel.pkg', size: 4 }] }); await service.writeChunk({ id: cancelled.id, fileId: cancelled.files[0].id, owner: 'alice', offset: 0, length: 2, chunkSha256: digest('ab'), stream: Readable.from('ab') }); assert.equal(service.cancel(cancelled.id, 'alice').state, 'cancelled'); const expiring = service.create({ owner: 'alice', files: [{ relativePath: 'expire.pkg', size: 2 }] }); clock = 1011; assert.equal(service.expire(), 1); assert.equal(service.get(expiring.id, 'alice').state, 'expired'); assert.equal(fs.readdirSync(root).some(name => name === 'library'), false); assert.deepEqual(events.map(event => event.channel), ['import:upload:created', 'import:upload:progress', 'import:upload:cancelled', 'import:upload:created', 'import:upload:expired']); } finally { fs.rmSync(root, { recursive: true, force: true }); } });