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,198 @@
|
||||
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/0016-create-trophy-reconstruction-journals');
|
||||
const {
|
||||
ACTION_CAPABILITIES,
|
||||
createSqliteTrophyActionRepository,
|
||||
createTrophyBackupService,
|
||||
createTrophyReconstructionActionService,
|
||||
createTrophyReconstructionSimulator,
|
||||
createUnsupportedLiveTrophyAdapter,
|
||||
} = require('../../src/services/trophy-reconstruction-action-service');
|
||||
|
||||
const FUTURE = '2099-01-01T00:00:00.000Z';
|
||||
|
||||
function item(trophyId, earnedDateTime = `2026-01-${String(trophyId).padStart(2, '0')}T03:04:05.000Z`) {
|
||||
return {
|
||||
npCommunicationId: 'NPWR12345_00',
|
||||
platform: 'PS5',
|
||||
service: 'trophy2',
|
||||
groupId: 'default',
|
||||
trophyId,
|
||||
sourceEarned: true,
|
||||
sourceEarnedDateTime: earnedDateTime,
|
||||
expectedLocalPreState: { earned: false, earnedDateTime: null },
|
||||
};
|
||||
}
|
||||
|
||||
function plan(items = [item(2)]) {
|
||||
return {
|
||||
id: `trophy-plan-${'a'.repeat(64)}`,
|
||||
planSha256: 'a'.repeat(64),
|
||||
idempotencyKey: 'b'.repeat(64),
|
||||
immutable: true,
|
||||
createdAt: '2026-07-22T00:00:00.000Z',
|
||||
expiresAt: FUTURE,
|
||||
adapter: {
|
||||
id: 'simulated-local-adapter',
|
||||
version: '1.0.0',
|
||||
timestampPolicy: 'preserve_exact',
|
||||
capabilities: [...ACTION_CAPABILITIES],
|
||||
firmwares: ['9.60'],
|
||||
services: ['trophy2'],
|
||||
},
|
||||
target: { firmware: '9.60', service: 'trophy2' },
|
||||
items,
|
||||
requiredBackup: { status: 'required', artifactRequired: true },
|
||||
};
|
||||
}
|
||||
|
||||
function initialState(items) {
|
||||
return items.map((entry) => ({
|
||||
npCommunicationId: entry.npCommunicationId,
|
||||
platform: entry.platform,
|
||||
service: entry.service,
|
||||
groupId: entry.groupId,
|
||||
trophyId: entry.trophyId,
|
||||
earned: false,
|
||||
earnedDateTime: null,
|
||||
}));
|
||||
}
|
||||
|
||||
async function harness(t, options = {}) {
|
||||
const SQL = await initSqlJs();
|
||||
const db = new SQL.Database();
|
||||
migration.up(db);
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ch0905-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
|
||||
let sequence = 0;
|
||||
const now = () => 1784721600000;
|
||||
const repository = createSqliteTrophyActionRepository({
|
||||
db,
|
||||
now,
|
||||
idFactory: () => `run-${++sequence}`,
|
||||
});
|
||||
const backupService = createTrophyBackupService({
|
||||
backupRoot: root,
|
||||
repository,
|
||||
now,
|
||||
idFactory: () => `backup-${++sequence}`,
|
||||
});
|
||||
const selectedPlan = options.plan || plan();
|
||||
const adapter = createTrophyReconstructionSimulator({
|
||||
initialItems: initialState(selectedPlan.items),
|
||||
...options.simulator,
|
||||
});
|
||||
const service = createTrophyReconstructionActionService({
|
||||
repository,
|
||||
backupService,
|
||||
now,
|
||||
planValidator: async () => ({ valid: true, reasons: [] }),
|
||||
});
|
||||
return { adapter, backupService, db, repository, selectedPlan, service };
|
||||
}
|
||||
|
||||
function confirmation(selectedPlan) {
|
||||
return `RECONSTRUCT LOCAL TROPHIES ${selectedPlan.items.length}`;
|
||||
}
|
||||
|
||||
test('verified backup is mandatory and backup failure prevents all mutation', async (t) => {
|
||||
const context = await harness(t, { simulator: { backupFailure: true } });
|
||||
await assert.rejects(
|
||||
context.service.execute({ plan: context.selectedPlan, adapter: context.adapter, confirmation: confirmation(context.selectedPlan) }),
|
||||
(error) => error.code === 'TROPHY_BACKUP_FAILED',
|
||||
);
|
||||
assert.equal(context.adapter.metrics().applyCalls, 0);
|
||||
assert.deepEqual(context.repository.listRuns(context.selectedPlan.id), []);
|
||||
});
|
||||
|
||||
test('successful apply uses frozen items and rerun is idempotent', async (t) => {
|
||||
const context = await harness(t);
|
||||
const first = await context.service.execute({
|
||||
plan: context.selectedPlan,
|
||||
adapter: context.adapter,
|
||||
confirmation: confirmation(context.selectedPlan),
|
||||
});
|
||||
assert.equal(first.status, 'completed');
|
||||
assert.equal(first.postVerification.verified, true);
|
||||
assert.equal(context.adapter.metrics().applyCalls, context.selectedPlan.items.length);
|
||||
|
||||
const storedBackup = await context.backupService.load(first.backupId);
|
||||
assert.equal(storedBackup.verified, true);
|
||||
assert.throws(
|
||||
() => context.db.run("UPDATE trophy_reconstruction_backups SET manifest_json = '{}'"),
|
||||
/immutable/i,
|
||||
);
|
||||
|
||||
const second = await context.service.execute({
|
||||
plan: context.selectedPlan,
|
||||
adapter: context.adapter,
|
||||
confirmation: confirmation(context.selectedPlan),
|
||||
});
|
||||
assert.equal(second.idempotent, true);
|
||||
assert.equal(second.runId, first.runId);
|
||||
assert.equal(context.adapter.metrics().applyCalls, context.selectedPlan.items.length);
|
||||
});
|
||||
|
||||
test('mid-action failure journals items and restores the verified backup', async (t) => {
|
||||
const selectedPlan = plan([item(2), item(3)]);
|
||||
const context = await harness(t, {
|
||||
plan: selectedPlan,
|
||||
simulator: { failAtItem: 'NPWR12345_00:PS5:trophy2:default:3' },
|
||||
});
|
||||
await assert.rejects(
|
||||
context.service.execute({ plan: selectedPlan, adapter: context.adapter, confirmation: confirmation(selectedPlan) }),
|
||||
(error) => error.code === 'TROPHY_ACTION_FAILED' && error.rollbackStatus === 'rolled_back',
|
||||
);
|
||||
assert.ok(context.adapter.state().every(([, entry]) => entry.earned === false));
|
||||
const [run] = context.repository.listRuns(selectedPlan.id);
|
||||
assert.equal(run.status, 'rolled_back');
|
||||
const journal = run.items;
|
||||
assert.equal(journal[0].status, 'verified');
|
||||
assert.equal(journal[1].status, 'failed');
|
||||
});
|
||||
|
||||
test('post-scan mismatch triggers rollback instead of reporting success', async (t) => {
|
||||
const context = await harness(t, { simulator: { postScanMismatch: true } });
|
||||
await assert.rejects(
|
||||
context.service.execute({ plan: context.selectedPlan, adapter: context.adapter, confirmation: confirmation(context.selectedPlan) }),
|
||||
(error) => error.code === 'TROPHY_ACTION_FAILED' && error.rollbackStatus === 'rolled_back',
|
||||
);
|
||||
assert.ok(context.adapter.state().every(([, entry]) => entry.earned === false));
|
||||
});
|
||||
|
||||
test('unsupported exact timestamp capability is rejected before backup or mutation', async (t) => {
|
||||
const context = await harness(t, { simulator: { timestampPolicy: 'current_time_only' } });
|
||||
await assert.rejects(
|
||||
context.service.execute({ plan: context.selectedPlan, adapter: context.adapter, confirmation: confirmation(context.selectedPlan) }),
|
||||
(error) => error.code === 'TROPHY_TIMESTAMP_UNSUPPORTED',
|
||||
);
|
||||
assert.equal(context.adapter.metrics().applyCalls, 0);
|
||||
assert.deepEqual(context.repository.listRuns(context.selectedPlan.id), []);
|
||||
});
|
||||
|
||||
test('action service has no network or PSN provider dependency and live binding is explicitly unavailable', () => {
|
||||
const source = fs.readFileSync(
|
||||
path.join(__dirname, '../../src/services/trophy-reconstruction-action-service.js'),
|
||||
'utf8',
|
||||
);
|
||||
assert.doesNotMatch(source, /node:https?|node-fetch|axios|\bfetch\s*\(/);
|
||||
assert.doesNotMatch(source, /psn-provider|psn-client/i);
|
||||
|
||||
const live = createUnsupportedLiveTrophyAdapter();
|
||||
assert.equal(live.status().available, false);
|
||||
assert.match(live.status().reason, /unsupported|version-pinned/i);
|
||||
|
||||
const status = fs.readFileSync(
|
||||
path.join(__dirname, '../../docs/autonomous/CH-0905_TROPHY_ADAPTER_STATUS.md'),
|
||||
'utf8',
|
||||
);
|
||||
assert.match(status, /unsupported/i);
|
||||
assert.match(status, /version-pinned/i);
|
||||
});
|
||||
Reference in new issue
Block a user