'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'/); });