Files
chimera-Public/test/server/ecosystem-intelligence-service.test.js
T

316 lines
18 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const crypto = require('crypto');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { SettingsStore } = require('../../src/persistence/settings-store');
const { createEcosystemIntelligenceService, repoSlug, sameVersion, compareVersionish, versionFromFilename, componentForPayload } = require('../../src/services/ecosystem-intelligence-service');
function fixture({ withWebhook = false, payloadManager = null } = {}) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ecosystem-intelligence-'));
const paths = Object.fromEntries(['backups', 'cache', 'tmp'].map(name => [name, path.join(root, name)]));
for (const directory of Object.values(paths)) fs.mkdirSync(directory, { recursive: true });
const settings = new SettingsStore(path.join(root, 'config', 'settings.json'));
settings.set('ftp.host', '127.0.0.1');
settings.set('ecosystem.componentVersions', { elfldr: 'v1.0.0', websrv: 'v1.0.0' });
const secretValues = new Map(withWebhook ? [['discord.webhookUrl', 'https://discord.com/api/webhooks/1/test']] : []);
const secrets = {
has: name => secretValues.has(name),
get: name => secretValues.get(name) || '',
};
const components = [
{ id: 'elfldr', name: 'ELF Loader', kind: 'daemon', platform: 'ps5', repo: 'ps5-payload-dev/elfldr', trust: 'official', risk: 'normal', dependencies: [], release: { tag: 'v1.0.0' }, compatibility: { status: 'compatible', reason: 'Testprofiel.' } },
{ id: 'websrv', name: 'PS5 websrv', kind: 'daemon', platform: 'ps5', repo: 'ps5-payload-dev/websrv', trust: 'official', risk: 'normal', dependencies: ['elfldr'], release: { tag: 'v2.0.0' }, compatibility: { status: 'compatible', reason: 'Testprofiel.' } },
];
const catalog = {
sources: [
{ id: 1, name: 'ELF Loader', github_url: 'https://github.com/ps5-payload-dev/elfldr', latest_tag: 'v1.0.0', category: 'loader', description: 'ELF loader', enabled: 1 },
{ id: 2, name: 'Web Server', github_url: 'https://github.com/ps5-payload-dev/websrv', latest_tag: 'v2.0.0', category: 'network', description: 'PS5 web server', enabled: 1 },
],
files: [
{ id: 10, source_id: 1, asset_name: 'elfldr.elf', asset_url: 'https://github.com/ps5-payload-dev/elfldr/releases/download/v1/elfldr.elf', asset_size: 100, version: 'v1.0.0', remote_hash: 'a'.repeat(64), local_path: null },
{ id: 20, source_id: 2, asset_name: 'websrv.elf', asset_url: 'https://github.com/ps5-payload-dev/websrv/releases/download/v2/websrv.elf', asset_size: 200, version: 'v2.0.0', remote_hash: 'b'.repeat(64), local_path: null },
],
stats: { updates: 1 },
};
const downloads = [];
let refreshes = 0;
let syncs = 0;
const base = {
listComponents: () => ({ checkedAt: Date.now(), components }),
refreshComponents: async () => { refreshes += 1; return { checkedAt: Date.now(), components }; },
serviceStatus: async () => ({ host: '127.0.0.1', services: [{ port: 9021, label: 'ELF loader', reachable: true }, { port: 8080, label: 'websrv', reachable: true }] }),
listPlugins: () => ({ items: [] }),
cheatIndex: () => ({ syncedAt: Date.now(), items: [], total: 0 }),
listSaveVault: () => [],
};
const db = {
getPayloadCatalog: () => catalog,
listGames: () => [{ game_id: 'PPSA00001', title: 'PPSA00001', prospero_name: null, prospero_icon_url: null }],
listBackups: () => [],
};
const payloadCatalog = {
sync: async () => { syncs += 1; return []; },
download: async input => { downloads.push(input); catalog.files.find(file => file.source_id === input.source_id && file.asset_name === input.asset_name).local_path = path.join(root, input.asset_name); return { localPath: path.join(root, input.asset_name), local_hash: 'b'.repeat(64), verified: true }; },
};
const service = createEcosystemIntelligenceService({
base, db, ftp: {}, settings, secrets, paths, payloadCatalog, payloadManager,
capabilities: { features: { https: { configured: true } } },
logger: { info() {}, warn() {}, error() {} },
});
return { root, paths, settings, service, components, catalog, downloads, counts: () => ({ refreshes, syncs }) };
}
test('repository and version normalization only accepts GitHub repositories', () => {
assert.equal(repoSlug('https://github.com/etaHEN/etaHEN/releases/latest'), 'etahen/etahen');
assert.equal(repoSlug('https://github.com/etaHEN/etaHEN.git'), 'etahen/etahen');
assert.equal(repoSlug('https://example.test/etaHEN/etaHEN'), '');
assert.equal(sameVersion('Release-v2.5B', '2.5b'), true);
assert.equal(sameVersion('1.0', '1.0.1'), false);
assert.equal(compareVersionish('2.6B', '2.5B'), 1);
assert.equal(compareVersionish('v0.23', '0.23'), 0);
assert.equal(compareVersionish('1.6beta16', '1.6beta17'), -1);
assert.equal(compareVersionish('1.0.0-beta1', '1.0.0'), null);
assert.equal(versionFromFilename('websrv_v0.33.elf'), '0.33');
assert.equal(componentForPayload('SystemStateManager.elf'), 'system-state');
});
test('health is unscored until a PS5 profile is configured', async () => {
const f = fixture();
try {
f.settings.set('ftp.host', '');
const report = await f.service.runHealth({ persist: true, notify: true });
assert.equal(report.hostConfigured, false);
assert.equal(report.score, null);
assert.equal(report.grade, 'not-configured');
assert.equal(report.notification.reason, 'not-configured');
assert.equal(report.alerts.find(item => item.id === 'ps5-host').severity, 'info');
assert.equal(f.service.healthHistory()[0].score, null);
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('update planner detects newer console payloads and blocks downgrades', () => {
const f = fixture();
try {
f.settings.set('ecosystem.componentVersions', { ...f.settings.get('ecosystem.componentVersions', {}), etahen: '2.6B' });
f.components.push({ id: 'etahen', name: 'etaHEN', kind: 'hen', platform: 'ps5', repo: 'etaHEN/etaHEN', trust: 'official', risk: 'normal', dependencies: [], release: { tag: '2.5B' }, compatibility: { status: 'compatible', reason: 'Testprofiel.' } });
f.catalog.sources.push({ id: 3, name: 'etaHEN', github_url: 'https://github.com/etaHEN/etaHEN', latest_tag: '2.5B', category: 'homebrew', enabled: 1 });
f.catalog.files.push({ id: 30, source_id: 3, asset_name: 'etaHEN-2.5B.bin', asset_url: 'https://github.com/etaHEN/etaHEN/releases/download/2.5B/etaHEN-2.5B.bin', version: '2.5B', remote_hash: 'c'.repeat(64) });
const inventory = f.service.componentInventory();
assert.equal(inventory.stats.ahead, 1);
assert.equal(inventory.components.find(item => item.id === 'etahen').state, 'ahead');
const plan = f.service.updatePlan({ selected: ['etahen'] });
assert.equal(plan.steps[0].stageable, false);
assert.match(plan.steps[0].blockers.join(' '), /downgrade/);
assert.throws(() => f.service.selectUpdateAsset({ componentId: 'etahen' }), /downgrade/);
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('Payload Manager inventory automatically supplies installed component versions', async () => {
const payloadManager = {
probe: async () => ({
version: '0.3.1',
autoload: { list: 'SystemStateManager.elf' },
inventory: { payloads: [{ filename: 'SystemStateManager.elf', path: '/data/pldmgr/payloads/SystemStateManager/SystemStateManager.elf', version: '1.0', checksumVerified: true }] },
sources: { items: [{ id: 'default', name: 'Official Repository', url: 'https://example.test/payloads.json' }] },
repository: { total: 20 },
}),
};
const f = fixture({ payloadManager });
try {
f.components.push({ id: 'system-state', name: 'System State Manager', kind: 'daemon', platform: 'ps5', repo: 'StonedModder/PS5-SystemStateManager', trust: 'official', risk: 'advanced', dependencies: ['elfldr'], release: { tag: 'v1.0' }, compatibility: { status: 'compatible', reason: 'Testprofiel.' } });
const snapshot = await f.service.refreshConsolePayloads();
assert.equal(snapshot.online, true);
assert.equal(snapshot.payloads[0].componentId, 'system-state');
const component = f.service.componentInventory().components.find(item => item.id === 'system-state');
assert.equal(component.installedVersion, '1.0');
assert.equal(component.installedSource, 'payload-manager');
assert.equal(component.state, 'current');
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('runtime evidence beats manual fallback and clears stale masking', async () => {
const payloadManager = {
probe: async () => ({
version: '0.4.0',
autoload: { list: 'websrv.elf' },
inventory: { payloads: [{ filename: 'websrv.elf', path: '/data/pldmgr/payloads/PS5-Plugins/websrv/websrv.elf', version: '2.0.0', checksumVerified: true }] },
sources: { items: [{ id: 'default', name: 'Official Repository', url: 'https://example.test/payloads.json' }] },
repository: { total: 20 },
}),
};
const f = fixture({ payloadManager });
try {
f.settings.set('ecosystem.componentVersions', { websrv: '1.0.0' });
await f.service.refreshConsolePayloads();
const component = f.service.componentInventory().components.find(item => item.id === 'websrv');
assert.equal(component.installedVersion, '2.0.0');
assert.equal(component.installedSource, 'payload-manager');
assert.equal(component.installedSourceConfidence, 'high');
assert.equal(component.installedVersionConflicts.length, 0);
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('manual override is explicitly stored with manual source and used as fallback', () => {
const f = fixture();
try {
const updated = f.service.setComponentVersion({ id: 'websrv', version: '2.0.0' });
const component = updated.components.find(item => item.id === 'websrv');
assert.equal(component.installedSource, 'manual');
assert.equal(component.installedSourceConfidence, 'manual');
assert.equal(component.installedVersion, '2.0.0');
assert.ok(Number.isFinite(component.installedSourceSeenAt) && component.installedSourceSeenAt > 0);
const stored = f.settings.get('ecosystem.componentVersions', {});
assert.equal(stored.websrv.version, '2.0.0');
assert.equal(stored.websrv.source, 'manual');
assert.equal(stored.websrv.confidence, 'manual');
assert.equal(stored.websrv.mode, 'override');
assert.ok(Number.isFinite(stored.websrv.seenAt) && stored.websrv.seenAt > 0);
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('explicit manual override exposes conflicts and follow automatic restores runtime priority', async () => {
const payloadManager = { probe: async () => ({
version: '0.4.0', autoload: { list: 'websrv.elf' },
inventory: { payloads: [{ filename: 'websrv.elf', path: '/data/pldmgr/payloads/websrv.elf', version: '2.0.0', checksumVerified: true }] },
sources: { items: [] }, repository: { total: 1 },
}) };
const f = fixture({ payloadManager });
try {
await f.service.refreshConsolePayloads();
f.service.setComponentVersion({ id: 'websrv', version: '1.5.0' });
let component = f.service.componentInventory().components.find(item => item.id === 'websrv');
assert.equal(component.installedVersion, '1.5.0');
assert.equal(component.trackingMode, 'manual-override');
assert.equal(component.evidenceState, 'conflict');
assert.ok(component.installedVersionConflicts.some(item => item.version === '2.0.0'));
f.service.followAutomatic({ id: 'websrv' });
component = f.service.componentInventory().components.find(item => item.id === 'websrv');
assert.equal(component.installedVersion, '2.0.0');
assert.equal(component.installedSource, 'payload-manager');
assert.equal(component.trackingMode, 'automatic');
assert.equal(component.manualEvidence, null);
assert.equal(f.settings.get('ecosystem.componentVersions', {}).websrv, undefined);
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('conflict and unknown evidence states are visible', () => {
const f = fixture();
try {
const websrvAsset = f.catalog.files.find(file => file.id === 20);
websrvAsset.local_path = path.join(f.root, 'websrv-local.elf');
websrvAsset.version = '1.0.0';
websrvAsset.asset_name = 'websrv_v1.1.elf';
fs.writeFileSync(websrvAsset.local_path, Buffer.from('websrv-payload'));
const component = f.service.componentInventory().components.find(entry => entry.id === 'websrv');
assert.equal(component.installedVersion, '1.0.0');
assert.equal(component.installedSource, 'file-hash');
assert.ok(component.installedVersionConflicts.length >= 1);
f.components.push({ id: 'ghost', name: 'Ghost payload', kind: 'daemon', platform: 'ps5', repo: 'invalid/ghost', trust: 'official', risk: 'normal', dependencies: [], release: {}, compatibility: { status: 'compatible', reason: 'Testprofiel.' } });
const ghost = f.service.componentInventory().components.find(entry => entry.id === 'ghost');
assert.equal(ghost.installedSource, null);
assert.equal(ghost.state, 'not-tracked');
assert.equal(ghost.installedVersion, '');
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('update planner orders dependencies and never declares automatic execution', async () => {
const f = fixture();
try {
const inventory = f.service.componentInventory();
assert.equal(inventory.stats.updates, 1);
assert.equal(inventory.components.find(item => item.id === 'websrv').state, 'update-available');
const plan = f.service.updatePlan();
assert.deepEqual(plan.steps.map(step => step.component.id), ['elfldr', 'websrv']);
assert.equal(plan.steps[0].dependencyOnly, true);
assert.equal(plan.steps[1].stageable, true);
assert.equal(plan.safety.autoExecute, false);
const refreshed = await f.service.refreshUpdates();
assert.equal(refreshed.safety.autoExecute, false);
assert.deepEqual(f.counts(), { refreshes: 1, syncs: 1 });
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('preparing an update only stages the selected catalog asset and records it', async () => {
const f = fixture();
try {
const result = await f.service.prepareUpdate({ componentId: 'websrv', assetName: 'websrv.elf' });
assert.equal(result.ok, true);
assert.equal(result.file.name, 'websrv.elf');
assert.equal(result.file.verified, true);
assert.deepEqual(f.downloads, [{ source_id: 2, asset_name: 'websrv.elf' }]);
assert.match(result.nextStep, /bewust/);
assert.equal(f.service.activityHistory()[0].type, 'updates');
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('Payload Manager repository exports only public upstream metadata and checksums', () => {
const f = fixture();
try {
const repository = f.service.payloadManagerRepository();
assert.equal(repository.name, 'Chimera Verified Payloads');
assert.equal(repository.payloads.length, 2);
assert.deepEqual(Object.keys(repository), ['name', 'payloads']);
const websrv = repository.payloads.find(item => item.filename === 'websrv.elf');
assert.equal(websrv.version, 'v2.0.0');
assert.equal(websrv.category, 'network');
assert.equal(websrv.checksum, 'b'.repeat(64));
assert.equal(Object.prototype.hasOwnProperty.call(websrv, 'localPath'), false);
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('Payload Supply-Chain Vault rehashes staged assets and blocks mismatches', async () => {
const f = fixture();
try {
const vault = path.join(f.root, 'payloads');
fs.mkdirSync(vault, { recursive: true });
f.settings.set('payload.localPath', vault);
const content = Buffer.from('trusted payload bytes');
const localPath = path.join(vault, 'websrv.elf');
fs.writeFileSync(localPath, content);
const asset = f.catalog.files.find(file => file.asset_name === 'websrv.elf');
const source = f.catalog.sources.find(item => item.id === asset.source_id);
source.trust = 'official';
asset.local_path = localPath;
asset.local_hash = crypto.createHash('sha256').update(content).digest('hex');
asset.remote_hash = asset.local_hash;
let report = await f.service.supplyChainAudit();
assert.equal(report.stats.verified, 1);
assert.equal(report.stats.blocked, 0);
fs.appendFileSync(localPath, 'tamper');
report = await f.service.supplyChainAudit();
assert.equal(report.stats.verified, 0);
assert.equal(report.stats.blocked, 1);
assert.match(report.assets.find(item => item.name === 'websrv.elf').issues.join(' '), /wijkt af/);
assert.equal(report.policy.automaticExecution, false);
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('console health persists actionable alerts, history and acknowledgements', async () => {
const f = fixture();
try {
const report = await f.service.runHealth({ persist: true, notify: false });
assert.ok(report.score < 100);
assert.ok(report.alerts.some(alert => alert.id === 'game-metadata'));
assert.ok(report.alerts.some(alert => alert.id === 'backup-coverage' && alert.severity === 'critical'));
assert.equal(f.service.healthHistory().length, 1);
const acknowledged = f.service.acknowledgeAlert({ id: 'game-metadata' });
assert.equal(acknowledged.alerts.find(alert => alert.id === 'game-metadata').acknowledged, true);
assert.equal(f.service.latestHealth().alerts.find(alert => alert.id === 'game-metadata').acknowledged, true);
assert.ok(f.service.activityHistory().some(item => item.type === 'health'));
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});
test('health notifications cannot be enabled without an encrypted webhook', () => {
const f = fixture();
try {
assert.throws(() => f.service.setHealthOptions({ notificationsEnabled: true }), /Discord-webhook/);
assert.equal(f.service.setHealthOptions({ notificationsEnabled: false }).notificationsEnabled, false);
} finally { fs.rmSync(f.root, { recursive: true, force: true }); }
});