'use strict';
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const test = require('node:test');
const initSqlJs = require('sql.js');
const database = require('../../src/database');
const { createV3Repositories } = require('../../src/services/v3-repositories');
function withDatabase(run) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-package-sets-'));
database.configure({ dataDir: root, logger: { info() {}, warn() {}, error() {} } });
return database.initialize().then(run).finally(() => { database.close(); fs.rmSync(root, { recursive: true, force: true }); });
}
function seedTitle() {
database.runMutatingSqlWithParams("INSERT INTO titles (platform,title_id,name,sort_name) VALUES ('ps4','CUSA12345','Package Test','Package Test')");
return Number(database.runQuery('SELECT last_insert_rowid() AS id')[0].id);
}
const evidence = value => ({ value, source: 'pkg-param-sfo', confidence: 'high', observedAt: 1 });
function manifest(name, suffix) {
const base = `${suffix}`.repeat(24).slice(0, 24);
const update = `${suffix === 'a' ? 'b' : 'c'}`.repeat(24);
return { schemaVersion: 1, createdAt: 1, import: { jobId: `job-${suffix}`, source: 'managed-import', classificationStatus: 'classified' }, title: { platform: 'ps4', titleId: 'CUSA12345', name, provenance: evidence('CUSA12345') }, destination: { relativePath: `PS4-Games/${name} [CUSA12345]`, template: 'PS4-Games/
[]' }, components: [
{ id: base, type: 'base', required: true, originalRelativePath: `${name}/base.pkg`, originalName: 'base.pkg', normalizedRelativePath: 'base/base.pkg', size: 4, sha256: '1'.repeat(64), platform: 'ps4', titleId: evidence('CUSA12345'), contentId: evidence('UP0001-CUSA12345_00-BASEPACKAGE000001'), version: evidence('1.00'), provenance: [{ source: 'staging-sha256', confidence: 'verified', observedAt: 1 }] },
{ id: update, type: 'update', required: false, originalRelativePath: `${name}/update.pkg`, originalName: 'update.pkg', normalizedRelativePath: 'updates/v1.01/update.pkg', size: 5, sha256: '2'.repeat(64), platform: 'ps4', titleId: evidence('CUSA12345'), contentId: evidence('UP0001-CUSA12345_00-UPDATEPACKAGE001'), version: evidence('1.01'), provenance: [{ source: 'staging-sha256', confidence: 'verified', observedAt: 1 }] },
], notes: [], recipe: { schema: 1, execution: 'disabled-until-validated', installOrder: [{ order: 1, componentId: base, componentType: 'base', action: 'proposal-only' }, { order: 2, componentId: update, componentType: 'update', action: 'proposal-only' }], installNotes: '', attachments: { patches: [], artwork: [] }, arbitraryCommands: [], notesExecutable: false } };
}
test('migration creates constrained package set, component and dependency schemas', () => withDatabase(async () => {
const tables = database.runQuery("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'package_%'").map(row => row.name);
assert.deepEqual(tables.sort(), ['package_component_dependencies', 'package_components', 'package_sets']);
const SQL = await initSqlJs();
const snapshot = new SQL.Database(database.exportDatabaseBuffer());
const refs = table => snapshot.exec(`PRAGMA foreign_key_list('${table}')`)[0]?.values?.map(row => row[2]) || [];
const componentRefs = refs('package_components');
const dependencyRefs = refs('package_component_dependencies');
assert.ok(componentRefs.includes('package_sets'));
assert.ok(dependencyRefs.includes('package_components'));
snapshot.close();
assert.throws(() => database.runMutatingSqlWithParams("INSERT INTO package_components (package_set_id,component_key,component_type,original_relative_path,normalized_relative_path,size_bytes,sha256) VALUES (999,'x','shell','x','x',0,@hash)", { hash: '0'.repeat(64) }));
}));
test('multiple variants per title persist complete component evidence and dependencies', () => withDatabase(() => {
const titleId = seedTitle();
const repository = createV3Repositories(database).packageSets;
const firstManifest = manifest('Retail', 'a');
const secondManifest = manifest('Modded', 'd');
repository.importManifest(titleId, firstManifest, { setKey: 'retail-us', variantKey: 'retail', region: 'US', dependencies: [{ componentId: firstManifest.components[1].id, dependsOnComponentId: firstManifest.components[0].id, kind: 'requires' }] });
repository.importManifest(titleId, secondManifest, { setKey: 'modded-eu', variantKey: 'modded', region: 'EU' });
const sets = repository.listByTitle(titleId);
assert.equal(sets.length, 2);
assert.deepEqual(sets.map(item => item.variantKey), ['retail', 'modded']);
assert.equal(sets[0].components[0].version, '1.00');
assert.equal(sets[0].components[0].region, 'US');
assert.equal(sets[0].components[0].sha256, '1'.repeat(64));
assert.equal(sets[0].components[0].required, true);
assert.equal(sets[0].components[0].provenance[0].confidence, 'verified');
assert.deepEqual(sets[0].dependencies, [{ componentId: firstManifest.components[1].id, dependsOnComponentId: firstManifest.components[0].id, kind: 'requires' }]);
}));
test('portable manifest maps losslessly while normalized rows remain queryable', () => withDatabase(() => {
const titleId = seedTitle();
const repository = createV3Repositories(database).packageSets;
const source = manifest('Round Trip', 'e');
const saved = repository.importManifest(titleId, source, { setKey: 'round-trip', variantKey: 'disc' });
assert.deepEqual(repository.manifestRoundTrip(saved.id), source);
assert.deepEqual(saved.manifest, source);
assert.equal(saved.manifestSchemaVersion, 1);
assert.match(saved.manifestSha256, /^[0-9a-f]{64}$/);
assert.equal(saved.components[1].normalizedRelativePath, source.components[1].normalizedRelativePath);
assert.equal(saved.components[1].contentId, source.components[1].contentId.value);
}));