'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/ [<TITLE_ID>]' }, 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); }));