'use strict'; const assert = require('node:assert/strict'); const crypto = require('node:crypto'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const test = require('node:test'); const { createImportUploadService } = require('../../src/services/import-upload-service'); const { createMediaImportService, detectMedia, normalizeCrop } = require('../../src/services/media-import-service'); const { createAppServer } = require('../../src/server/app-server'); function png(width = 600, height = 900) { const bytes = Buffer.alloc(32); Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]).copy(bytes); bytes.write('IHDR', 12, 'ascii'); bytes.writeUInt32BE(width, 16); bytes.writeUInt32BE(height, 20); return bytes; } test('image/video validation and crop bounds fail closed', () => { assert.equal(detectMedia(png()).mime, 'image/png'); assert.equal(detectMedia(Buffer.from([0x1a, 0x45, 0xdf, 0xa3, 0, 0, 0, 0, 0, 0, 0, 0])).mime, 'video/webm'); assert.equal(detectMedia(Buffer.from('not media bytes')), null); assert.deepEqual(normalizeCrop({ x: 0.1, y: 0, width: 0.8, height: 1 }), { x: 0.1, y: 0, width: 0.8, height: 1 }); assert.throws(() => normalizeCrop({ x: 0.5, y: 0, width: 0.6, height: 1 }), /bounds/); }); test('streamed import preserves provenance, title activity data and recoverable provider cover', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-media-import-')); const rows = new Map([['PPSA12345', { game_id: 'PPSA12345', prospero_cover_url: 'https://provider.example/cover.jpg', metadata_override: null }]]); const db = { getGame: id => rows.get(id), updateGame: (id, fields) => rows.set(id, { ...rows.get(id), ...fields }) }; const uploads = createImportUploadService({ root: path.join(root, 'uploads'), limits: { maxFileBytes: 1024, maxSessionBytes: 1024 } }); const media = createMediaImportService({ root: path.join(root, 'media'), db, now: () => 1_800_000_000_000 }); try { const bytes = png(); const session = uploads.create({ owner: 'alice', files: [{ relativePath: 'custom-cover.png', size: bytes.length }] }); await uploads.writeChunk({ id: session.id, fileId: session.files[0].id, owner: 'alice', offset: 0, length: bytes.length, chunkSha256: crypto.createHash('sha256').update(bytes).digest('hex'), stream: require('stream').Readable.from(bytes) }); const asset = await media.finalizeFromUpload({ uploadStore: uploads, sessionId: session.id, fileId: session.files[0].id, owner: 'alice', titleId: 'PPSA12345', kind: 'cover', crop: { x: 0, y: 0, width: 1, height: 1 }, preferred: true }); assert.equal(asset.provenance.type, 'imported'); assert.equal(asset.provenance.importedBy, 'alice'); assert.equal(asset.preferred, true); assert.match(JSON.parse(rows.get('PPSA12345').metadata_override).coverUrl, /^\/api\/imported-media\//); const linked = media.list('PPSA12345'); assert.equal(linked.items[0].titleId, 'PPSA12345'); assert.equal(linked.providerArtwork.coverUrl, 'https://provider.example/cover.jpg'); assert.equal(linked.providerArtwork.recoverable, true); media.setPreferred({ titleId: 'PPSA12345', assetId: null }); assert.equal(rows.get('PPSA12345').metadata_override, null); assert.equal(fs.existsSync(media.resolvePublicPath(asset.url.replace('/api/imported-media/', ''))), true); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); test('upload ownership and quota apply before media finalization', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-media-quota-')); const uploads = createImportUploadService({ root: path.join(root, 'uploads'), limits: { maxFileBytes: 16, maxSessionBytes: 16 } }); const media = createMediaImportService({ root: path.join(root, 'media'), db: { getGame: () => ({ game_id: 'CUSA00001' }) } }); try { assert.throws(() => uploads.create({ owner: 'alice', files: [{ relativePath: 'large.png', size: 17 }] }), error => error.code === 'UPLOAD_QUOTA'); const session = uploads.create({ owner: 'alice', files: [{ relativePath: 'small.png', size: 12 }] }); assert.throws(() => uploads.openUploaded(session.id, 'bob'), error => error.code === 'UPLOAD_NOT_FOUND'); await assert.rejects(() => media.finalizeFromUpload({ uploadStore: uploads, sessionId: session.id, fileId: session.files[0].id, owner: 'alice', titleId: 'CUSA00001', kind: 'cover' }), error => error.code === 'UPLOAD_INCOMPLETE'); } finally { fs.rmSync(root, { recursive: true, force: true }); } }); test('media finalize and imported asset routes enforce session and CSRF boundaries', async () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-media-http-')); const cache = path.join(root, 'cache'); fs.mkdirSync(cache, { recursive: true }); const values = new Map(); const secretValues = new Map(); const settings = { get: (key, fallback) => values.has(key) ? values.get(key) : fallback, set: (key, value) => values.set(key, value) }; const secrets = { get: key => secretValues.get(key), set: (key, value) => secretValues.set(key, value), delete: key => secretValues.delete(key) }; const row = { game_id: 'PPSA12345', prospero_cover_url: 'https://provider.example/cover.jpg', metadata_override: null }; const db = { getGame: id => id === row.game_id ? row : null, updateGame: (_id, fields) => Object.assign(row, fields) }; const mediaImports = createMediaImportService({ root: path.join(root, 'media-imports'), db }); const registry = { mediaImports, describe: () => [], invoke: async () => ({}), isMutating: () => false, requestBodyLimit: () => 1024 }; const app = createAppServer({ appRoot: path.resolve(__dirname, '../..'), paths: { root, cache }, settings, secrets, registry, logger: { info() {}, warn() {}, error() {}, audit() {} }, ftp: { disconnect: async () => {} }, capabilities: { features: {} }, version: 'test' }); const address = await app.listen(0, '127.0.0.1'); const base = `http://127.0.0.1:${address.port}`; const common = { Host: `127.0.0.1:${address.port}`, Origin: base, 'Content-Type': 'application/json' }; try { const denied = await fetch(`${base}/api/media/imports/finalize`, { method: 'POST', headers: common, body: '{}' }); assert.equal(denied.status, 401); const setup = await fetch(`${base}/api/setup`, { method: 'POST', headers: common, body: JSON.stringify({ username: 'artist', password: 'secure-password-123', allowedRoots: [root], safetyConfirmed: true }) }); const cookie = setup.headers.get('set-cookie').split(';')[0]; const csrf = (await setup.json()).csrfToken; const bytes = png(); const createdResponse = await fetch(`${base}/api/import/uploads`, { method: 'POST', headers: { ...common, Cookie: cookie, 'X-CSRF-Token': csrf }, body: JSON.stringify({ files: [{ relativePath: 'cover.png', size: bytes.length }] }) }); const created = (await createdResponse.json()).result; const chunkHeaders = { Host: common.Host, Origin: base, Cookie: cookie, 'X-CSRF-Token': csrf, 'Upload-Offset': '0', 'X-Chunk-SHA256': crypto.createHash('sha256').update(bytes).digest('hex'), 'Content-Type': 'application/octet-stream' }; await fetch(`${base}/api/import/uploads/${created.id}/files/${created.files[0].id}`, { method: 'PUT', headers: chunkHeaders, body: bytes }); const body = JSON.stringify({ sessionId: created.id, fileId: created.files[0].id, titleId: 'PPSA12345', kind: 'cover', preferred: true, crop: { x: 0, y: 0, width: 1, height: 1 } }); const missingCsrf = await fetch(`${base}/api/media/imports/finalize`, { method: 'POST', headers: { ...common, Cookie: cookie }, body }); assert.equal(missingCsrf.status, 403); const finalized = await fetch(`${base}/api/media/imports/finalize`, { method: 'POST', headers: { ...common, Cookie: cookie, 'X-CSRF-Token': csrf }, body }); assert.equal(finalized.status, 201); const asset = (await finalized.json()).result; const unauthenticatedAsset = await fetch(`${base}${asset.url}`); assert.equal(unauthenticatedAsset.status, 401); const served = await fetch(`${base}${asset.url}`, { headers: { Cookie: cookie } }); assert.equal(served.status, 200); assert.deepEqual(Buffer.from(await served.arrayBuffer()), bytes); } finally { await app.close(); fs.rmSync(root, { recursive: true, force: true }); } });