Files
chimera-Public/test/server/ch-0902-local-trophy-inventory.test.js

104 lines
5.5 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const initSqlJs = require('sql.js');
const migration = require('../../src/database/migrations/0013-create-trophy-local-snapshots');
const {
createSimulatedLocalTrophyProvider,
createSqliteLocalTrophyRepository,
createUnsupportedLocalTrophyProvider,
parseLocalTrophySnapshot,
} = require('../../src/services/local-trophy-inventory-service');
function sample(status = 'complete') {
return {
schemaVersion: 1,
provider: { id: 'simulated-local-reader', version: '1.0.0', firmware: '9.60', capabilities: ['profile-identity', 'list-title-sets', 'list-earned-trophies', 'earned-timestamps'] },
profile: { profileId: 'profile-12345678', localUserId: 'user-87654321', accountId: '1234567890123456' },
coverage: { status, completedScopes: status === 'inaccessible' ? [] : ['profile', 'NPWR12345_00'], failedScopes: status === 'complete' ? [] : ['NPWR54321_00'] },
observedAt: '2026-07-21T12:00:00.000Z',
titles: [{
npCommunicationId: 'NPWR12345_00', titleId: 'PPSA12345', platform: 'PS5', service: 'trophy2',
trophies: [
{ groupId: 'default', trophyId: 2, earned: false },
{ groupId: 'default', trophyId: 1, earned: true, earnedDateTime: '2026-01-02T03:04:05Z', definitionHash: 'a'.repeat(64) },
],
}],
};
}
async function setup() {
const SQL = await initSqlJs();
const db = new SQL.Database();
migration.up(db);
return { db, repository: createSqliteLocalTrophyRepository({ db }) };
}
test('complete local snapshot preserves exact IDs and alone authorizes absence conclusions', async t => {
const { db, repository } = await setup();
t.after(() => db.close());
const provider = createSimulatedLocalTrophyProvider({ repository, now: () => 1800000000000 });
const record = provider.fetchSnapshot(sample('complete'));
assert.equal(record.snapshot.profile.profileId, 'profile-12345678');
assert.equal(record.snapshot.titles[0].npCommunicationId, 'NPWR12345_00');
assert.deepEqual(record.snapshot.titles[0].trophies.map(item => item.trophyId), [1, 2]);
assert.equal(record.snapshot.coverage.canConcludeAbsence, true);
assert.equal(record.snapshot.provider.firmware, '9.60');
assert.deepEqual(record.snapshot.provider.capabilities, ['earned-timestamps', 'list-earned-trophies', 'list-title-sets', 'profile-identity']);
assert.match(record.rawSha256, /^[a-f0-9]{64}$/);
assert.throws(() => db.run('UPDATE trophy_local_snapshots SET firmware = ?', ['10.00']), /immutable/);
});
test('partial and inaccessible snapshots can never authorize trophy absence', async t => {
const { db, repository } = await setup();
t.after(() => db.close());
let now = 1800000000000;
const provider = createSimulatedLocalTrophyProvider({ repository, now: () => now++ });
const partial = provider.fetchSnapshot(sample('partial'));
const inaccessible = provider.fetchSnapshot(sample('inaccessible'));
assert.equal(partial.snapshot.coverage.canConcludeAbsence, false);
assert.equal(inaccessible.snapshot.coverage.canConcludeAbsence, false);
assert.deepEqual(partial.snapshot.coverage.failedScopes, ['NPWR54321_00']);
const invalid = sample('complete');
invalid.coverage.failedScopes = ['unreadable-title'];
assert.throws(() => parseLocalTrophySnapshot(invalid), error => error.code === 'LOCAL_TROPHY_SCHEMA');
});
test('unsupported live provider is explicit and exposes no mutation capability', () => {
const provider = createUnsupportedLocalTrophyProvider({ firmware: '10.00', reason: 'Pinned read-only adapter is unavailable for this firmware.' });
assert.deepEqual(Object.keys(provider).sort(), ['capabilities', 'fetchSnapshot', 'id', 'sanitizePublicModel', 'status']);
assert.deepEqual(provider.capabilities, []);
assert.equal(provider.status().available, false);
assert.equal(provider.status().readOnly, true);
assert.match(provider.status().reason, /unavailable/);
assert.throws(() => provider.fetchSnapshot(), error => error.code === 'LOCAL_TROPHY_UNSUPPORTED');
});
test('public local model masks profile identities and invalid evidence fails closed', async t => {
const { db, repository } = await setup();
t.after(() => db.close());
const provider = createSimulatedLocalTrophyProvider({ repository, now: () => 1800000000000 });
const publicModel = provider.sanitizePublicModel(provider.fetchSnapshot(sample()));
assert.equal(JSON.stringify(publicModel).includes('1234567890123456'), false);
assert.equal(JSON.stringify(publicModel).includes('profile-12345678'), false);
assert.equal(publicModel.coverage.canConcludeAbsence, true);
assert.equal(publicModel.readOnly, true);
const noTimestamp = sample();
delete noTimestamp.titles[0].trophies[1].earnedDateTime;
assert.throws(() => parseLocalTrophySnapshot(noTimestamp), error => error.code === 'LOCAL_TROPHY_SCHEMA');
const hostile = sample();
hostile.provider.capabilities.push('write-trophies');
assert.throws(() => parseLocalTrophySnapshot(hostile), error => error.code === 'LOCAL_TROPHY_SCHEMA');
});
test('schema and source contain only read-only local provider surfaces', () => {
const schema = require('../../schemas/trophy-local-inventory.schema.json');
assert.equal(schema.additionalProperties, false);
const source = fs.readFileSync(path.resolve(__dirname, '../../src/services/local-trophy-inventory-service.js'), 'utf8');
assert.doesNotMatch(source, /(?:unlock|reconstruct|applyTrophy|writeTrophy|deleteTrophy)/i);
assert.match(source, /canConcludeAbsence:\s*coverageStatus === 'complete'/);
});