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,154 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
|
||||
const { canonicalCatalogDigest } = require('../../src/services/legacy-detached-trophy-preflight-service');
|
||||
const {
|
||||
CONSENT,
|
||||
canonicalTrophyDigest,
|
||||
confirmationPhrase,
|
||||
createInactiveLegacyTrophyUnlockService,
|
||||
} = require('../../src/services/inactive-legacy-trophy-unlock-service');
|
||||
|
||||
const game = {
|
||||
title: 'Inactive fixture game',
|
||||
trophies: [
|
||||
{ id: 0, grade: 'bronze', name: 'Locked', description: 'Target', hidden: false, earned: false },
|
||||
{ id: 1, grade: 'silver', name: 'Earned', description: 'Existing', hidden: false, earned: true },
|
||||
],
|
||||
};
|
||||
const catalogSha256 = canonicalCatalogDigest(game);
|
||||
const trophySha256 = canonicalTrophyDigest(catalogSha256, game.trophies[0]);
|
||||
const backupSha256 = 'b'.repeat(64);
|
||||
|
||||
function dependencies(overrides = {}) {
|
||||
const calls = [];
|
||||
return {
|
||||
calls,
|
||||
values: {
|
||||
installedCatalog: async () => ({ games: [game] }),
|
||||
mutationStatus: async () => ({ available: true }),
|
||||
detachedPreflight: async (input) => {
|
||||
calls.push(['preflight', input]);
|
||||
return { ready: true, cleanupComplete: true, targetInactive: true };
|
||||
},
|
||||
backup: async (input) => {
|
||||
calls.push(['backup', input]);
|
||||
return { complete: true, sha256: backupSha256 };
|
||||
},
|
||||
dryRun: async (input) => {
|
||||
calls.push(['dry-run', input]);
|
||||
return { ready: true, writes: 0, unlockCalls: 0 };
|
||||
},
|
||||
unlockAdapter: async (input) => {
|
||||
calls.push(['unlock', input]);
|
||||
return {
|
||||
status: 'unlocked',
|
||||
catalogSha256,
|
||||
trophySha256,
|
||||
targetInactive: true,
|
||||
unlockCalls: 1,
|
||||
psnRequests: 0,
|
||||
contextDestroyed: true,
|
||||
handleDestroyed: true,
|
||||
filesPersisted: 0,
|
||||
};
|
||||
},
|
||||
postScan: async (input) => {
|
||||
calls.push(['post-scan', input]);
|
||||
return { selectedUnlocked: true, changedTrophies: 1, otherChanges: 0 };
|
||||
},
|
||||
rollback: async (input) => {
|
||||
calls.push(['rollback', input]);
|
||||
return { verified: true };
|
||||
},
|
||||
audit: async (input) => calls.push(['audit', input]),
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function input() {
|
||||
return {
|
||||
catalogSha256,
|
||||
trophySha256,
|
||||
confirmation: confirmationPhrase(catalogSha256, trophySha256),
|
||||
};
|
||||
}
|
||||
|
||||
test('inactive unlock is unavailable until a native mutation adapter is proven', async () => {
|
||||
const service = createInactiveLegacyTrophyUnlockService({
|
||||
installedCatalog: async () => ({ games: [game] }),
|
||||
});
|
||||
assert.deepEqual(await service.status(), {
|
||||
available: false,
|
||||
exactOne: true,
|
||||
inactiveOnly: true,
|
||||
backupRequired: true,
|
||||
dryRunRequired: true,
|
||||
rollbackRequired: true,
|
||||
reasonCode: 'NATIVE_MUTATION_ADAPTER_UNAVAILABLE',
|
||||
});
|
||||
await assert.rejects(service.unlockOne(input()), /unavailable/i);
|
||||
});
|
||||
|
||||
test('inactive unlock resolves an opaque exact-one selection and verifies the full recovery flow', async () => {
|
||||
const fixture = dependencies();
|
||||
const service = createInactiveLegacyTrophyUnlockService(fixture.values);
|
||||
const result = await service.unlockOne(input());
|
||||
|
||||
assert.equal(result.status, 'unlocked');
|
||||
assert.equal(result.postScanVerified, true);
|
||||
assert.equal(result.backupSha256, backupSha256);
|
||||
assert.deepEqual(fixture.calls.map(([name]) => name), [
|
||||
'preflight', 'backup', 'dry-run', 'unlock', 'post-scan', 'audit',
|
||||
]);
|
||||
const adapterInput = fixture.calls.find(([name]) => name === 'unlock')[1];
|
||||
assert.equal(adapterInput.consent, CONSENT);
|
||||
assert.equal(adapterInput.trophyId, 0);
|
||||
assert.equal(Object.hasOwn(adapterInput, 'titleId'), false);
|
||||
assert.equal(Object.hasOwn(adapterInput, 'npCommunicationId'), false);
|
||||
});
|
||||
|
||||
test('inactive unlock rejects free ids, stale digests, earned trophies and wrong confirmation', async () => {
|
||||
const fixture = dependencies();
|
||||
const service = createInactiveLegacyTrophyUnlockService(fixture.values);
|
||||
await assert.rejects(service.unlockOne({ ...input(), trophyId: 99 }), /forbidden input field/i);
|
||||
await assert.rejects(service.unlockOne({ ...input(), trophySha256: '0'.repeat(64) }), /stale or ambiguous/i);
|
||||
await assert.rejects(service.unlockOne({ ...input(), confirmation: 'yes' }), /exact inactive trophy confirmation/i);
|
||||
|
||||
const earnedSha256 = canonicalTrophyDigest(catalogSha256, game.trophies[1]);
|
||||
await assert.rejects(service.unlockOne({
|
||||
catalogSha256,
|
||||
trophySha256: earnedSha256,
|
||||
confirmation: confirmationPhrase(catalogSha256, earnedSha256),
|
||||
}), /already unlocked/i);
|
||||
assert.equal(fixture.calls.length, 0);
|
||||
});
|
||||
|
||||
test('inactive unlock rolls back when exact-one post-scan proof fails', async () => {
|
||||
const fixture = dependencies({
|
||||
postScan: async () => ({ selectedUnlocked: true, changedTrophies: 2, otherChanges: 1 }),
|
||||
});
|
||||
const service = createInactiveLegacyTrophyUnlockService(fixture.values);
|
||||
await assert.rejects(service.unlockOne(input()), /exact-one trophy mutation/i);
|
||||
assert.equal(fixture.calls.some(([name]) => name === 'rollback'), true);
|
||||
const audit = fixture.calls.find(([name]) => name === 'audit')[1];
|
||||
assert.equal(audit.outcome, 'rolled-back');
|
||||
});
|
||||
|
||||
test('inactive unlock rejects an unverifiable backup or side-effecting dry-run before mutation', async () => {
|
||||
const noBackup = dependencies({ backup: async () => ({ complete: false }) });
|
||||
await assert.rejects(
|
||||
createInactiveLegacyTrophyUnlockService(noBackup.values).unlockOne(input()),
|
||||
/verified trophy backup/i,
|
||||
);
|
||||
|
||||
const dirtyDryRun = dependencies({ dryRun: async () => ({ ready: true, writes: 1, unlockCalls: 0 }) });
|
||||
await assert.rejects(
|
||||
createInactiveLegacyTrophyUnlockService(dirtyDryRun.values).unlockOne(input()),
|
||||
/dry-run did not pass/i,
|
||||
);
|
||||
assert.equal(dirtyDryRun.calls.some(([name]) => name === 'unlock'), false);
|
||||
});
|
||||
Reference in new issue
Block a user