Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974
This commit is contained in:
commit
c737a37f71
531 files changed
+119270
No files matched your search
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
const initSqlJs = require('sql.js');
|
||||
const migration = require('../../src/database/migrations/0018-create-save-recovery-plans');
|
||||
const { createSaveRecoveryPlanService, createSaveRecoverySimulator, createSqliteSaveRecoveryRepository, createUnsupportedDirectSaveExtractionProvider } = require('../../src/services/save-recovery-plan-service');
|
||||
const crypto = require('node:crypto');
|
||||
const sha256 = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
|
||||
const SOURCE = Buffer.from('encrypted-byte-verifiable-save');
|
||||
const TARGET = Buffer.from('existing-target-save');
|
||||
const SOURCE_ACCOUNT = '0011223344556677';
|
||||
const TARGET_ACCOUNT = '8899aabbccddeeff';
|
||||
|
||||
async function harness() {
|
||||
const SQL = await initSqlJs(); const db = new SQL.Database(); migration.up(db); let id = 0;
|
||||
const repository = createSqliteSaveRecoveryRepository({ db, now: () => Date.now(), idFactory: () => `test-${++id}` });
|
||||
const service = createSaveRecoveryPlanService({ repository, now: () => Date.now(), idFactory: () => `plan-${++id}` });
|
||||
const artifact = { id: 'vault-1', titleId: 'PPSA03016', accountId: SOURCE_ACCOUNT, sha256: sha256(SOURCE), sizeBytes: SOURCE.length, format: 'garlic-encrypted-ps5-title' };
|
||||
const adapter = createSaveRecoverySimulator({ sourceBytes: SOURCE, targetBytes: TARGET, sourceAccountId: SOURCE_ACCOUNT, targetAccountId: TARGET_ACCOUNT });
|
||||
return { adapter, artifact, db, repository, service };
|
||||
}
|
||||
|
||||
function makePlan(context, overrides = {}) { return context.service.createPlan({ sourceArtifact: context.artifact, sourceAccountId: SOURCE_ACCOUNT, targetAccountId: TARGET_ACCOUNT, adapterStatus: context.adapter.status(), ...overrides }); }
|
||||
|
||||
test('dry run freezes explicit accounts, hashes and separate backup-gated steps without executing', async () => {
|
||||
const context = await harness(); const plan = makePlan(context);
|
||||
assert.equal(plan.immutable, true); assert.match(plan.planSha256, /^[a-f0-9]{64}$/); assert.equal(plan.sourceAccountId, SOURCE_ACCOUNT); assert.equal(plan.targetAccountId, TARGET_ACCOUNT);
|
||||
assert.deepEqual(plan.steps.map(step => step.id), ['verify-source','backup-target','export-encrypted','resign','import-encrypted','export-target','verify-target']);
|
||||
assert.ok(plan.steps.filter(step => step.requiresBackup).every(step => step.backupStepId === 'backup-target'));
|
||||
assert.equal(plan.automaticExecution, false); assert.equal(plan.destructiveRestore, false); assert.equal(context.adapter.metrics().mutations, 0);
|
||||
await assert.rejects(context.service.execute({ planId: plan.id, adapter: context.adapter, confirmation: plan.confirmationPhrase }), error => error.code === 'SAVE_RECOVERY_CONFIRMATION');
|
||||
});
|
||||
|
||||
test('source hash mismatch blocks before backup or target mutation', async () => {
|
||||
const context = await harness(); const plan = makePlan(context);
|
||||
const adapter = createSaveRecoverySimulator({ sourceBytes: SOURCE, targetBytes: TARGET, sourceAccountId: SOURCE_ACCOUNT, targetAccountId: TARGET_ACCOUNT, sourceHashMismatch: true });
|
||||
await assert.rejects(context.service.execute({ planId: plan.id, adapter, confirmation: plan.confirmationPhrase, manualStart: true }), error => error.code === 'SAVE_RECOVERY_HASH_MISMATCH' && error.rollbackStatus === 'not-required');
|
||||
assert.equal(adapter.metrics().mutations, 0); assert.deepEqual(adapter.metrics().target, TARGET);
|
||||
});
|
||||
|
||||
test('explicit source and transformed target account mismatches are rejected', async () => {
|
||||
const context = await harness();
|
||||
assert.throws(() => makePlan(context, { sourceAccountId: TARGET_ACCOUNT }), error => error.code === 'SAVE_RECOVERY_ACCOUNT_MISMATCH');
|
||||
const plan = makePlan(context); const adapter = createSaveRecoverySimulator({ sourceBytes: SOURCE, targetBytes: TARGET, sourceAccountId: SOURCE_ACCOUNT, targetAccountId: TARGET_ACCOUNT, accountMismatch: true });
|
||||
await assert.rejects(context.service.execute({ planId: plan.id, adapter, confirmation: plan.confirmationPhrase, manualStart: true }), error => error.code === 'SAVE_RECOVERY_ACCOUNT_MISMATCH');
|
||||
assert.equal(adapter.metrics().mutations, 0);
|
||||
});
|
||||
|
||||
test('failure after import restores and byte-verifies the pre-action target backup', async () => {
|
||||
const context = await harness(); const plan = makePlan(context);
|
||||
const adapter = createSaveRecoverySimulator({ sourceBytes: SOURCE, targetBytes: TARGET, sourceAccountId: SOURCE_ACCOUNT, targetAccountId: TARGET_ACCOUNT, failAt: 'export-target' });
|
||||
await assert.rejects(context.service.execute({ planId: plan.id, adapter, confirmation: plan.confirmationPhrase, manualStart: true }), error => error.rollbackStatus === 'rolled-back');
|
||||
assert.equal(adapter.metrics().mutations, 1); assert.deepEqual(adapter.metrics().target, TARGET);
|
||||
});
|
||||
|
||||
test('supported flow journals before/after hashes and idempotent rerun does not mutate', async () => {
|
||||
const context = await harness(); const plan = makePlan(context);
|
||||
const first = await context.service.execute({ planId: plan.id, adapter: context.adapter, confirmation: plan.confirmationPhrase, manualStart: true });
|
||||
assert.equal(first.status, 'completed'); assert.equal(first.steps.length, 7); assert.ok(first.steps.every(step => step.status === 'completed'));
|
||||
assert.match(first.steps.find(step => step.id === 'backup-target').outputSha256, /^[a-f0-9]{64}$/); assert.match(first.steps.find(step => step.id === 'verify-target').outputSha256, /^[a-f0-9]{64}$/);
|
||||
const second = await context.service.execute({ planId: plan.id, adapter: context.adapter, confirmation: plan.confirmationPhrase, manualStart: true });
|
||||
assert.equal(second.idempotent, true); assert.equal(context.adapter.metrics().mutations, 1);
|
||||
assert.throws(() => context.db.run("UPDATE save_recovery_plans SET title_id='PPSA99999'"), /immutable/i);
|
||||
});
|
||||
|
||||
test('unsupported direct extraction provider remains unavailable and cannot create a plan', async () => {
|
||||
const context = await harness(); const provider = createUnsupportedDirectSaveExtractionProvider();
|
||||
assert.equal(provider.status().available, false); assert.deepEqual(provider.status().capabilities, []);
|
||||
await assert.rejects(provider.extract({}), error => error.code === 'SAVE_RECOVERY_PROVIDER_UNSUPPORTED');
|
||||
assert.throws(() => context.service.createPlan({ sourceArtifact: { ...context.artifact, format: 'opaque-ps5-backup-candidate' }, sourceAccountId: SOURCE_ACCOUNT, targetAccountId: TARGET_ACCOUNT, adapterStatus: context.adapter.status() }), error => error.code === 'SAVE_RECOVERY_PROVIDER_UNSUPPORTED');
|
||||
});
|
||||
Reference in new issue
Block a user