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,122 @@
|
||||
'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 migration12 = require('../../src/database/migrations/0012-create-trophy-psn-snapshots');
|
||||
const migration13 = require('../../src/database/migrations/0013-create-trophy-local-snapshots');
|
||||
const migration14 = require('../../src/database/migrations/0014-create-trophy-account-mappings');
|
||||
const { createJsonTrophySnapshotProvider, createSqliteTrophySnapshotRepository } = require('../../src/services/trophy-snapshot-service');
|
||||
const { createSimulatedLocalTrophyProvider, createSqliteLocalTrophyRepository } = require('../../src/services/local-trophy-inventory-service');
|
||||
const { DIFF_STATES, createSqliteTrophyMappingRepository, createTrophyDiffService } = require('../../src/services/trophy-mapping-diff-service');
|
||||
|
||||
const HASH_A = 'a'.repeat(64);
|
||||
const HASH_B = 'b'.repeat(64);
|
||||
const TIME = '2026-01-02T03:04:05.000Z';
|
||||
|
||||
function psnPayload(name = 'English title') {
|
||||
return {
|
||||
schemaVersion: 1, provider: { id: 'json-proof', version: '1' }, account: { accountId: '1234567890123456', onlineId: 'Player_One' }, context: { region: 'BE', language: 'en-BE' }, fetchedAt: '2026-07-20T12:00:00Z',
|
||||
titles: [{ npCommunicationId: 'NPWR12345_00', titleId: 'PPSA12345', name, platform: 'PS5', service: 'trophy2', groups: [{ groupId: 'default', name: 'Base' }], trophies: [
|
||||
{ groupId: 'default', trophyId: 1, name: 'Same', grade: 'gold', hidden: false, earned: true, earnedDateTime: TIME, progress: 100, definitionHash: HASH_A },
|
||||
{ groupId: 'default', trophyId: 2, name: 'Source earned', grade: 'silver', hidden: false, earned: true, earnedDateTime: TIME, progress: 100, definitionHash: HASH_A },
|
||||
{ groupId: 'default', trophyId: 3, name: 'Local earned', grade: 'bronze', hidden: false, earned: false, progress: 0, definitionHash: HASH_A },
|
||||
{ groupId: 'default', trophyId: 4, name: 'Metadata differs', grade: 'silver', hidden: false, earned: true, earnedDateTime: TIME, progress: 100, definitionHash: HASH_A },
|
||||
{ groupId: 'default', trophyId: 5, name: 'Neither', grade: 'bronze', hidden: false, earned: false, progress: 0, definitionHash: HASH_A },
|
||||
{ groupId: 'default', trophyId: 7, name: 'Missing exact local ID', grade: 'gold', hidden: false, earned: true, earnedDateTime: TIME, progress: 100, definitionHash: HASH_A },
|
||||
] }],
|
||||
};
|
||||
}
|
||||
|
||||
function localPayload(status = 'complete') {
|
||||
return {
|
||||
schemaVersion: 1, provider: { id: 'local-simulator', version: '1', firmware: '9.60', capabilities: ['profile-identity', 'list-title-sets', 'list-earned-trophies', 'earned-timestamps'] },
|
||||
profile: { profileId: 'profile-1234', localUserId: 'user-1234', accountId: '1234567890123456' },
|
||||
coverage: { status, completedScopes: ['NPWR12345_00'], failedScopes: status === 'complete' ? [] : ['NPWR99999_00'] }, observedAt: '2026-07-21T12:00:00Z',
|
||||
titles: [{ npCommunicationId: 'NPWR12345_00', titleId: 'PPSA12345', platform: 'PS5', service: 'trophy2', trophies: [
|
||||
{ groupId: 'default', trophyId: 1, earned: true, earnedDateTime: TIME, definitionHash: HASH_A },
|
||||
{ groupId: 'default', trophyId: 2, earned: false, definitionHash: HASH_A },
|
||||
{ groupId: 'default', trophyId: 3, earned: true, earnedDateTime: TIME, definitionHash: HASH_A },
|
||||
{ groupId: 'default', trophyId: 4, earned: true, earnedDateTime: '2026-01-03T03:04:05Z', definitionHash: HASH_B },
|
||||
{ groupId: 'default', trophyId: 5, earned: false, definitionHash: HASH_A },
|
||||
{ groupId: 'default', trophyId: 6, earned: true, earnedDateTime: TIME, definitionHash: HASH_A },
|
||||
] }],
|
||||
};
|
||||
}
|
||||
|
||||
async function setup() {
|
||||
const SQL = await initSqlJs();
|
||||
const db = new SQL.Database();
|
||||
migration12.up(db); migration13.up(db); migration14.up(db);
|
||||
let now = 1800000000000;
|
||||
const psnProvider = createJsonTrophySnapshotProvider({ repository: createSqliteTrophySnapshotRepository({ db }), now: () => now++ });
|
||||
const localProvider = createSimulatedLocalTrophyProvider({ repository: createSqliteLocalTrophyRepository({ db }), now: () => now++ });
|
||||
const mappings = createSqliteTrophyMappingRepository({ db, now: () => now++, idFactory: () => 'mapping-1' });
|
||||
const mapping = mappings.create({ psnAccountId: '1234567890123456', localProfileId: 'profile-1234', selectedByUser: true, confirmation: 'MAP TROPHY ACCOUNT', reason: 'User selected the matching local profile.' });
|
||||
return { db, psnProvider, localProvider, mappings, mapping, diff: createTrophyDiffService({ mappingRepository: mappings }) };
|
||||
}
|
||||
|
||||
test('exact account/title/trophy mapping produces every evidence diff state', async t => {
|
||||
const env = await setup(); t.after(() => env.db.close());
|
||||
const psn = env.psnProvider.authenticateOrImport(psnPayload());
|
||||
const local = env.localProvider.fetchSnapshot(localPayload('complete'));
|
||||
const result = env.diff.compare({ psnSnapshot: psn, localSnapshot: local, mappingId: env.mapping.id });
|
||||
const byId = Object.fromEntries(result.items.map(item => [item.trophyId, item.state]));
|
||||
assert.deepEqual(byId, { 1: 'both', 2: 'psn_only', 3: 'local_only', 4: 'metadata_mismatch', 5: 'both', 6: 'local_only', 7: 'unmappable' });
|
||||
assert.equal(result.mappingExact, true);
|
||||
assert.equal(result.readOnly, true);
|
||||
assert.deepEqual(new Set(Object.keys(result.counts)), new Set(DIFF_STATES));
|
||||
|
||||
const partial = env.localProvider.fetchSnapshot(localPayload('partial'));
|
||||
const partialResult = env.diff.compare({ psnSnapshot: psn, localSnapshot: partial, mappingId: env.mapping.id });
|
||||
assert.equal(partialResult.items.find(item => item.trophyId === 2).state, 'unknown');
|
||||
});
|
||||
|
||||
test('missing, mismatched and ambiguous exact identities never fall back to names or position', async t => {
|
||||
const env = await setup(); t.after(() => env.db.close());
|
||||
const psn = env.psnProvider.authenticateOrImport(psnPayload());
|
||||
const local = env.localProvider.fetchSnapshot(localPayload());
|
||||
const missing = env.diff.compare({ psnSnapshot: psn, localSnapshot: local, mappingId: 'missing' });
|
||||
assert.equal(missing.mappingReason, 'mapping_missing');
|
||||
assert.ok(missing.items.every(item => item.state === 'unmappable'));
|
||||
|
||||
const ambiguous = { ...psn, snapshot: JSON.parse(JSON.stringify(psn.snapshot)) };
|
||||
ambiguous.snapshot.titles[0].trophies.push({ ...ambiguous.snapshot.titles[0].trophies[0] });
|
||||
assert.throws(() => env.diff.compare({ psnSnapshot: ambiguous, localSnapshot: local, mappingId: env.mapping.id }), error => error.code === 'TROPHY_DIFF_AMBIGUOUS');
|
||||
});
|
||||
|
||||
test('localized names are diagnostic only and do not change exact diff states', async t => {
|
||||
const env = await setup(); t.after(() => env.db.close());
|
||||
const local = env.localProvider.fetchSnapshot(localPayload());
|
||||
const english = env.diff.compare({ psnSnapshot: env.psnProvider.authenticateOrImport(psnPayload('English title')), localSnapshot: local, mappingId: env.mapping.id });
|
||||
const localizedPayload = psnPayload('Titre francais');
|
||||
localizedPayload.titles[0].trophies.forEach(item => { item.name = `Nom ${item.trophyId}`; });
|
||||
const localized = env.diff.compare({ psnSnapshot: env.psnProvider.authenticateOrImport(localizedPayload), localSnapshot: local, mappingId: env.mapping.id });
|
||||
assert.deepEqual(english.items.map(item => [item.trophyId, item.state]), localized.items.map(item => [item.trophyId, item.state]));
|
||||
});
|
||||
|
||||
test('mapping revocation changes state fingerprint and invalidates exact comparison', async t => {
|
||||
const env = await setup(); t.after(() => env.db.close());
|
||||
const psn = env.psnProvider.authenticateOrImport(psnPayload());
|
||||
const local = env.localProvider.fetchSnapshot(localPayload());
|
||||
const before = env.diff.compare({ psnSnapshot: psn, localSnapshot: local, mappingId: env.mapping.id });
|
||||
assert.throws(() => env.mappings.revoke(env.mapping.id, { confirmation: 'wrong', reason: 'test' }), error => error.code === 'TROPHY_MAPPING_CONFIRMATION');
|
||||
const revoked = env.mappings.revoke(env.mapping.id, { confirmation: 'REVOKE TROPHY MAPPING', reason: 'Local profile selection changed.' });
|
||||
const after = env.diff.compare({ psnSnapshot: psn, localSnapshot: local, mappingId: env.mapping.id });
|
||||
assert.equal(revoked.active, false);
|
||||
assert.notEqual(before.mappingStateHash, after.mappingStateHash);
|
||||
assert.notEqual(before.contextHash, after.contextHash);
|
||||
assert.equal(after.mappingReason, 'mapping_revoked');
|
||||
assert.ok(after.items.every(item => item.state === 'unmappable'));
|
||||
});
|
||||
|
||||
test('mapping implementation compares exact IDs and keeps names diagnostic-only', () => {
|
||||
const source = fs.readFileSync(path.resolve(__dirname, '../../src/services/trophy-mapping-diff-service.js'), 'utf8');
|
||||
assert.match(source, /npCommunicationId/);
|
||||
assert.match(source, /groupId/);
|
||||
assert.match(source, /trophyId/);
|
||||
assert.doesNotMatch(source, /\.name\s*===|localeCompare\([^)]*\.name/);
|
||||
assert.match(source, /diagnosticName/);
|
||||
});
|
||||
Reference in new issue
Block a user