'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const test = require('node:test'); const initSqlJs = require('sql.js'); const migration = require('../../src/database/migrations/0017-create-ps5-system-backup-inventory'); const { createExperimentalBarAdapter, createSqliteSystemBackupRepository, createSystemBackupInventoryService } = require('../../src/services/system-backup-recovery-lab-service'); async function harness(t) { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ch0907-')); t.after(() => fs.rmSync(root, { recursive: true, force: true })); const SQL = await initSqlJs(); const db = new SQL.Database(); migration.up(db); let id = 0; const repository = createSqliteSystemBackupRepository({ db, now: () => 1784721600000 + id, idFactory: () => `artifact-${++id}` }); return { db, root, repository, service: createSystemBackupInventoryService({ repository, allowedRoots: [root] }) }; } function writeCandidate(root, name, byte = 0x5a) { const file = path.join(root, name); fs.writeFileSync(file, Buffer.alloc(4096, byte)); return file; } const readyMetadata = { label: 'Original PS5 backup', backupSystemSoftwareVersion: '9.60', targetSystemSoftwareVersion: '10.00', usbFilesystem: 'exFAT', accountBound: true, targetAccountConfirmed: true, destructiveRestoreAcknowledged: true }; test('register opens and hashes the original artifact read-only without changing bytes or metadata', async t => { const context = await harness(t); const file = writeCandidate(context.root, 'original.bar'); const before = fs.statSync(file); const bytes = fs.readFileSync(file); const record = await context.service.register({ filePath: file, metadata: readyMetadata }); const after = fs.statSync(file); assert.equal(record.readOnlyVerified, true); assert.equal(record.sizeBytes, bytes.length); assert.match(record.sha256, /^[a-f0-9]{64}$/); assert.deepEqual(fs.readFileSync(file), bytes); assert.equal(after.size, before.size); assert.equal(after.mtimeMs, before.mtimeMs); assert.equal(record.integrityStatus, 'candidate'); assert.equal(record.immutable, true); assert.throws(() => context.db.run("UPDATE ps5_system_backup_artifacts SET display_name = 'changed'"), /immutable/i); }); test('same content is registered as a duplicate without collapsing provenance', async t => { const context = await harness(t); const first = await context.service.register({ filePath: writeCandidate(context.root, 'first.bar'), metadata: readyMetadata }); const second = await context.service.register({ filePath: writeCandidate(context.root, 'copy.backup'), metadata: readyMetadata }); assert.notEqual(second.id, first.id); assert.equal(second.sha256, first.sha256); assert.equal(second.duplicateOf, first.id); assert.equal(context.service.list().length, 2); }); test('unsupported and corrupt artifacts are recorded but never reported restore-ready', async t => { const context = await harness(t); const unsupported = await context.service.register({ filePath: writeCandidate(context.root, 'notes.txt'), metadata: readyMetadata }); const corruptPath = path.join(context.root, 'empty.bar'); fs.writeFileSync(corruptPath, Buffer.alloc(0)); const corrupt = await context.service.register({ filePath: corruptPath, metadata: readyMetadata }); assert.equal(unsupported.integrityStatus, 'unsupported'); assert.equal(unsupported.officialChecklist.status, 'blocked'); assert.equal(corrupt.integrityStatus, 'corrupt'); assert.equal(corrupt.officialChecklist.status, 'blocked'); }); test('official checklist gates filesystem, firmware, account and destructive warning', async t => { const context = await harness(t); const ready = await context.service.register({ filePath: writeCandidate(context.root, 'ready.bar'), metadata: readyMetadata }); assert.equal(ready.officialChecklist.status, 'ready'); assert.match(ready.officialChecklist.officialRoute, /Restore Your PS5/); const blocked = await context.service.register({ filePath: writeCandidate(context.root, 'older-target.bar', 0x61), metadata: { ...readyMetadata, targetSystemSoftwareVersion: '9.00', usbFilesystem: 'ntfs', targetAccountConfirmed: false } }); assert.equal(blocked.officialChecklist.status, 'blocked'); assert.ok(blocked.officialChecklist.checks.some(check => check.id === 'system-software' && check.state === 'blocked')); assert.ok(blocked.officialChecklist.checks.some(check => check.id === 'usb-filesystem' && check.state === 'blocked')); assert.equal(ready.claims.trophyContents, 'excluded-by-official-guidance'); assert.equal(ready.claims.directTrophyRecovery, false); assert.equal(ready.claims.directSaveRecovery, false); }); test('managed root aliases are canonicalized before checking the resolved artifact', async t => { const context = await harness(t); const physical = path.join(context.root, 'physical'); fs.mkdirSync(physical); const alias = path.join(context.root, 'managed-alias'); fs.symlinkSync(physical, alias, 'junction'); const file = writeCandidate(physical, 'original.bar'); const before = fs.statSync(file); const service = createSystemBackupInventoryService({ repository: context.repository, allowedRoots: [alias, path.join(context.root, 'unavailable-root')] }); const record = await service.register({ filePath: path.join(alias, 'original.bar') }); assert.equal(record.sourcePath, await fs.promises.realpath(file)); assert.equal(record.sizeBytes, before.size); assert.equal(fs.statSync(file).mtimeMs, before.mtimeMs); assert.equal(record.readOnlyVerified, true); await assert.rejects(service.register({ filePath: file }), error => error.code === 'RECOVERY_LAB_PATH'); }); test('an internal directory link cannot escape its selected managed root', async t => { const context = await harness(t); const managed = path.join(context.root, 'managed'); fs.mkdirSync(managed); const outside = path.join(context.root, 'outside'); fs.mkdirSync(outside); writeCandidate(outside, 'outside.bar'); fs.symlinkSync(outside, path.join(managed, 'escape'), 'junction'); const service = createSystemBackupInventoryService({ repository: context.repository, allowedRoots: [managed, outside] }); await assert.rejects(service.register({ filePath: path.join(managed, 'escape', 'outside.bar') }), error => error.code === 'RECOVERY_LAB_PATH'); assert.equal(context.repository.list().length, 0); }); test('experimental BAR adapter is capability-based WIP and fails closed', async () => { const adapter = createExperimentalBarAdapter(); const status = adapter.status(); assert.equal(status.state, 'wip'); assert.equal(status.available, false); assert.deepEqual(status.capabilities, ['read-header', 'compare-sha256']); assert.ok(status.limitations.includes('no-restore')); assert.equal(adapter.assess({ integrityStatus: 'candidate' }).candidate, true); await assert.rejects(adapter.extract({}), error => error.code === 'RECOVERY_LAB_WIP'); await assert.rejects(adapter.restore({}), error => error.code === 'RECOVERY_LAB_WIP'); });