'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const vm = require('node:vm'); const root = path.resolve(__dirname, '../..'); const read = file => fs.readFileSync(path.join(root, file), 'utf8'); test('PS5 product surface is an evidence-first fPKG backup and restore workflow', () => { const html = read('renderer/index.html'); assert.match(html, /PS5 fPKG Backup & Restore/); for (const phase of ['Capture', 'Analyze', 'Build fPKG', 'Verify', 'Restore', 'Launch verify']) assert.match(html, new RegExp(`>${phase}<`)); assert.match(html, /PS5 fPKG means a finalized FIH \.pkg/); assert.match(html, /\.ffpkg remains a separate UFS mount image and is never a fallback/); assert.doesNotMatch(html, /PS5 Native PKG Lab|Build genuine debug PKG set|Guarded experimental restore/); }); test('backup safety readiness and restore qualification are separate accessible live evidence', () => { const html = read('renderer/index.html'); for (const id of ['ps5-native-preservation-evidence', 'ps5-native-fpkg-readiness-evidence', 'ps5-native-restore-evidence']) assert.match(html, new RegExp(`id="${id}"`)); assert.match(html, /class="ps5-fpkg-evidence-grid"[^>]*aria-label=/); assert.match(html, /id="ps5-native-package-readiness"[^>]*role="status"[^>]*aria-live="polite"/); assert.match(html, /id="ps5-native-package-launch"[^>]*disabled/); assert.match(html, /data-i18n-aria-label="ps5NativePackages\.phase\.aria"/); }); test('renderer presents installed pending failed and process-qualified states without conflation', () => { const modal = read('renderer/modules/modal.js'); for (const state of ['installed_observed', 'launching', 'launch_failed', 'launch_verified', 'outcome_unknown']) assert.match(modal, new RegExp(state)); assert.match(modal, /Installed · launch verification pending/); assert.match(modal, /Installed — launch failed\. Not qualified for restore/); assert.match(modal, /Installed · Launch verified · Qualified for restore/); assert.match(modal, /ps5NativePackageRestoreLaunchPlan/); assert.match(modal, /showExactConfirmation[\s\S]*plan\.confirmation/); assert.match(modal, /ps5NativePackageRestoreLaunchStatus/); assert.doesNotMatch(modal, /operation\.status === 'completed'[^\n]*ps5NativePackages\.verify\.completed/); }); test('launch qualification selects durable launch evidence instead of the newest install attempt', () => { const modal = read('renderer/modules/modal.js'); assert.match(modal, /function selectPs5RestoreQualificationOperation\(installs, titleId\)/); assert.match(modal, /\['installed_observed', 'launch_test_pending', 'launching', 'launch_verified', 'launch_failed'\]\.includes\(item\.status\)/); assert.match(modal, /item\.status === 'interrupted' && item\.launchOutcome === 'outcome_unknown'/); assert.match(modal, /Number\(b\.updatedAt \|\| b\.createdAt \|\| 0\) - Number\(a\.updatedAt \|\| a\.createdAt \|\| 0\)/); assert.match(modal, /renderPs5RestoreQualification\(qualificationOperation\)/); assert.doesNotMatch(modal, /renderPs5RestoreQualification\(titleInstalls\[0\]/); const source = modal.match(/function selectPs5RestoreQualificationOperation\(installs, titleId\) \{[\s\S]*?\n\}/)?.[0]; assert.ok(source); const select = vm.runInNewContext(`(${source})`); const selected = select([ { id: 'newer-install', titleId: 'FAKE00000', status: 'completed', createdAt: 300, updatedAt: 310 }, { id: 'launch-ready', titleId: 'FAKE00000', status: 'installed_observed', createdAt: 200, updatedAt: 400 }, { id: 'other-title', titleId: 'PPSA99099', status: 'launch_verified', createdAt: 500, updatedAt: 500 }, ], 'fake00000'); assert.equal(selected.id, 'launch-ready'); }); test('the exact pinned FAKE00000 launch reference can reach the guarded native workspace', () => { const modal = read('renderer/modules/modal.js'); assert.match(modal, /function isPs5NativeWorkspaceTitle\(gameId\)/); assert.match(modal, /=== 'FAKE00000'/); assert.match(modal, /btn-modal-ps5-native-packages'\)\.hidden = !isPs5NativeWorkspaceTitle\(game_id\)/); assert.match(modal, /refreshPs5NativePackageWorkspace\(\)[\s\S]*!isPs5NativeWorkspaceTitle\(game\.game_id\)/); assert.match(modal, /openPs5NativePackageWorkspace\(\)[\s\S]*!isPs5NativeWorkspaceTitle\(game\.game_id\)/); assert.doesNotMatch(modal, /\^\(PPSA\|FAKE\)/, 'do not widen arbitrary PS5 title validation'); }); test('Electron and web bridges expose only semantic launch qualification calls', () => { const preload = read('preload.js'); const web = read('renderer/pork-web-shim.js'); const main = read('main.js'); for (const source of [preload, web]) { assert.match(source, /ps5NativePackageRestoreLaunchPlan/); assert.match(source, /ps5NativePackageRestoreLaunchStatus/); assert.match(source, /ps5-native-packages:restore-launch/); } for (const command of ['restore-launch-plan', 'restore-launch', 'restore-launch-status']) assert.match(main, new RegExp(`ps5-native-packages:${command}`)); assert.doesNotMatch(modalUnsafeSurface(web), /callerHost|remotePath|shellCommand/); }); function modalUnsafeSurface(source) { const start = source.indexOf('ps5NativePackageRestoreLaunchPlan'); return start < 0 ? '' : source.slice(start, start + 700); } test('new visible fPKG copy has English and Dutch catalog parity', () => { const en = JSON.parse(read('build/lang/en.json')).strings; const nl = JSON.parse(read('build/lang/nl.json')).strings; const keys = [ 'ps5NativePackages.title', 'ps5NativePackages.phase.capture', 'ps5NativePackages.phase.analyze', 'ps5NativePackages.phase.launch', 'ps5NativePackages.evidence.backup', 'ps5NativePackages.evidence.readiness', 'ps5NativePackages.evidence.restore', 'ps5NativePackages.evidence.backupSafe', 'ps5NativePackages.evidence.buildBlocked', 'ps5NativePackages.launch.action', 'ps5NativePackages.launch.pending', 'ps5NativePackages.launch.failed', 'ps5NativePackages.launch.qualified', 'ps5NativePackages.launch.boundary', ]; for (const key of keys) { assert.equal(typeof en[key], 'string', `missing English ${key}`); assert.equal(typeof nl[key], 'string', `missing Dutch ${key}`); assert.notEqual(en[key], key); assert.notEqual(nl[key], key); } }); test('clean upstream UI no longer exposes legacy V16 builder tuning', () => { const settings = read('renderer/modules/settings.js'); assert.doesNotMatch(settings, /ps5-native-package-compression-workers|ps5-native-package-fast-store-threshold/); });