94 lines
3.4 KiB
JavaScript
94 lines
3.4 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const crypto = require('node:crypto');
|
|
const test = require('node:test');
|
|
|
|
const { CONSENT } = require('../../src/services/inactive-legacy-trophy-unlock-service');
|
|
const { createLegacyDetachedTrophyMutationAdapter } = require('../../src/services/legacy-detached-trophy-mutation-adapter');
|
|
|
|
function sha256(value) {
|
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
}
|
|
|
|
test('native inactive mutation adapter is fail-closed without artifact pins', () => {
|
|
assert.deepEqual(createLegacyDetachedTrophyMutationAdapter().status(), {
|
|
available: false,
|
|
reasonCode: 'NATIVE_MUTATION_ADAPTER_NOT_PINNED',
|
|
});
|
|
});
|
|
|
|
test('native inactive mutation adapter verifies both artifacts and forwards an exact-one request', async () => {
|
|
const cliPath = 'cli';
|
|
const elfPath = 'elf';
|
|
const cli = Buffer.from('cli');
|
|
const elf = Buffer.from('elf');
|
|
const files = new Map([[cliPath, cli], [elfPath, elf]]);
|
|
const catalogSha256 = 'a'.repeat(64);
|
|
const trophySha256 = 'b'.repeat(64);
|
|
const backupSha256 = 'c'.repeat(64);
|
|
let forwarded;
|
|
const adapter = createLegacyDetachedTrophyMutationAdapter({
|
|
cliPath,
|
|
elfPath,
|
|
expectedCliSha256: sha256(cli),
|
|
expectedElfSha256: sha256(elf),
|
|
fs: { existsSync: (candidate) => files.has(candidate), readFileSync: (candidate) => files.get(candidate) },
|
|
runAdapter: async (request) => {
|
|
forwarded = request;
|
|
return {
|
|
status: 'unlocked',
|
|
catalogSha256,
|
|
trophySha256,
|
|
targetInactive: true,
|
|
unlockCalls: 1,
|
|
psnRequests: 0,
|
|
contextDestroyed: true,
|
|
handleDestroyed: true,
|
|
filesPersisted: 0,
|
|
};
|
|
},
|
|
});
|
|
|
|
const result = await adapter.unlockOne({
|
|
consent: CONSENT,
|
|
catalogSha256,
|
|
trophySha256,
|
|
trophyId: 7,
|
|
backupSha256,
|
|
});
|
|
assert.equal(result.status, 'unlocked');
|
|
assert.equal(forwarded.trophyId, 7);
|
|
assert.equal(forwarded.expectedElfSha256, sha256(elf));
|
|
assert.equal(Object.hasOwn(forwarded, 'titleId'), false);
|
|
});
|
|
|
|
test('native inactive mutation adapter rejects a hash mismatch before execution', async () => {
|
|
let ran = false;
|
|
const files = new Map([['cli', Buffer.from('cli')], ['elf', Buffer.from('elf')]]);
|
|
const adapter = createLegacyDetachedTrophyMutationAdapter({
|
|
cliPath: 'cli',
|
|
elfPath: 'elf',
|
|
expectedCliSha256: '0'.repeat(64),
|
|
expectedElfSha256: sha256(files.get('elf')),
|
|
fs: { existsSync: (candidate) => files.has(candidate), readFileSync: (candidate) => files.get(candidate) },
|
|
runAdapter: async () => { ran = true; },
|
|
});
|
|
assert.equal(adapter.status().reasonCode, 'NATIVE_MUTATION_ADAPTER_HASH_MISMATCH');
|
|
await assert.rejects(adapter.unlockOne({}), /unavailable/i);
|
|
assert.equal(ran, false);
|
|
});
|
|
|
|
test('native inactive mutation adapter rejects caller-supplied title metadata', async () => {
|
|
const files = new Map([['cli', Buffer.from('cli')], ['elf', Buffer.from('elf')]]);
|
|
const adapter = createLegacyDetachedTrophyMutationAdapter({
|
|
cliPath: 'cli',
|
|
elfPath: 'elf',
|
|
expectedCliSha256: sha256(files.get('cli')),
|
|
expectedElfSha256: sha256(files.get('elf')),
|
|
fs: { existsSync: (candidate) => files.has(candidate), readFileSync: (candidate) => files.get(candidate) },
|
|
runAdapter: async () => { throw new Error('must not run'); },
|
|
});
|
|
await assert.rejects(adapter.unlockOne({ titleId: 'CUSA00000' }), /forbidden field/i);
|
|
});
|