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,409 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
|
||||
const modulePathCache = new Map();
|
||||
let activeTransferState = null;
|
||||
|
||||
function mockModule(resolvedPath, exportsValue) {
|
||||
const current = require.cache[resolvedPath];
|
||||
modulePathCache.set(resolvedPath, current);
|
||||
require.cache[resolvedPath] = {
|
||||
id: resolvedPath,
|
||||
filename: resolvedPath,
|
||||
loaded: true,
|
||||
exports: exportsValue,
|
||||
};
|
||||
}
|
||||
|
||||
function restoreModules() {
|
||||
for (const [resolvedPath, original] of modulePathCache) {
|
||||
if (original === undefined) delete require.cache[resolvedPath];
|
||||
else require.cache[resolvedPath] = original;
|
||||
}
|
||||
modulePathCache.clear();
|
||||
}
|
||||
|
||||
function installRegistryMocks(rootDir) {
|
||||
const createOperationResult = () => ({
|
||||
status: 'ok',
|
||||
services: [],
|
||||
metadata: {},
|
||||
scans: [],
|
||||
health: {},
|
||||
});
|
||||
|
||||
mockModule(path.resolve(rootDir, 'src/platform/data-paths.js'), {
|
||||
mountedRoots: () => [],
|
||||
normalizeAllowedRoots: (roots, fallback) => (Array.isArray(roots) && roots.length ? roots : [fallback]),
|
||||
resolveAllowedPath: (candidate, _roots, options = {}) => {
|
||||
if (!candidate) return options.fallbackRoot || '';
|
||||
return path.isAbsolute(candidate) ? candidate : path.join(options.fallbackRoot || '', candidate);
|
||||
},
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/hashing.js'), {
|
||||
hashFile: async () => Buffer.from('fake-hash'),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/elf-arsenal-client.js'), {
|
||||
createElfArsenalClient: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/local-library-scanner.js'), {
|
||||
scanLocalLibraries: async () => ({ games: [], added: 0, total: 0 }),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/transfer-service.js'), {
|
||||
createTransferService: ({ broadcast, getCredentials }) => {
|
||||
const executors = new Map();
|
||||
const state = {
|
||||
jobs: [],
|
||||
jobsById: new Map(),
|
||||
registerExecutor: (kind, factory) => {
|
||||
executors.set(kind, factory);
|
||||
},
|
||||
enqueue: (type, options = {}, runner) => {
|
||||
const jobId = `job-${state.jobs.length + 1}`;
|
||||
let status = 'queued';
|
||||
const progress = {};
|
||||
const throwIfCancelled = () => {
|
||||
if (status === 'cancelled') {
|
||||
throw new Error('Taak geannuleerd');
|
||||
}
|
||||
};
|
||||
const context = {
|
||||
progress: () => {},
|
||||
throwIfCancelled,
|
||||
client: {
|
||||
uploadFile: async () => ({ success: true }),
|
||||
uploadDirectory: async () => ({ success: true }),
|
||||
downloadFile: async () => ({ success: true }),
|
||||
},
|
||||
};
|
||||
const job = {
|
||||
jobId,
|
||||
id: jobId,
|
||||
type,
|
||||
progress,
|
||||
status,
|
||||
promise: Promise.resolve(),
|
||||
deduplicated: false,
|
||||
};
|
||||
const execute = async () => {
|
||||
status = 'active';
|
||||
if (runner) {
|
||||
await runner(context);
|
||||
} else {
|
||||
const executor = executors.get(String((options.descriptor || {}).kind || type));
|
||||
if (!executor) throw new Error(`No executor for ${options.descriptor?.kind || type}`);
|
||||
await executor(options.descriptor || {})(context);
|
||||
}
|
||||
return { success: true, remotePath: options.remotePath };
|
||||
};
|
||||
const credentials = getCredentials?.();
|
||||
if (credentials) {
|
||||
context.credentials = credentials;
|
||||
}
|
||||
const resolved = execute()
|
||||
.then(result => { status = 'done'; broadcast?.('transfer:update', state.state()); return result; })
|
||||
.catch(error => {
|
||||
status = 'failed';
|
||||
throw error;
|
||||
});
|
||||
job.promise = resolved;
|
||||
job.status = status;
|
||||
state.jobs.push(job);
|
||||
state.jobsById.set(jobId, job);
|
||||
broadcast?.('transfer:update', state.state());
|
||||
return job;
|
||||
},
|
||||
pause: () => {},
|
||||
resume: () => {},
|
||||
clearDone: () => {
|
||||
state.jobs = [];
|
||||
state.jobsById = new Map();
|
||||
},
|
||||
setConcurrent: () => 4,
|
||||
setPolicy: options => options,
|
||||
retry: () => false,
|
||||
cancel: () => false,
|
||||
state: () => ({ jobs: state.jobs.slice(), policy: { bandwidthLimitSupported: false } }),
|
||||
close: () => {},
|
||||
};
|
||||
activeTransferState = state;
|
||||
return state;
|
||||
},
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/conversion-service.js'), {
|
||||
createConversionService: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/operations-service.js'), {
|
||||
createOperationsService: () => new Proxy({}, {
|
||||
get: (_target, prop) => {
|
||||
if (prop === 'start' || prop === 'stop') return () => {};
|
||||
return () => createOperationResult();
|
||||
},
|
||||
}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/ecosystem-service.js'), {
|
||||
createEcosystemService: () => ({
|
||||
stop: () => {},
|
||||
payloads: {},
|
||||
listRemoteSources: () => [],
|
||||
classification: {},
|
||||
downloads: () => [],
|
||||
}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/ecosystem-intelligence-service.js'), {
|
||||
createEcosystemIntelligenceService: () => ({ latestHealth: () => ({}) }),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/game-update-service.js'), {
|
||||
createGameUpdateService: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/ps5debug-readonly-client.js'), {
|
||||
createPs5DebugReadonlyClient: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/remote-play-service.js'), {
|
||||
createRemotePlayService: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/backup-replication-service.js'), {
|
||||
createBackupReplicationService: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/payload-runtime-service.js'), {
|
||||
createPayloadRuntimeService: () => ({
|
||||
watchdog: () => ({}),
|
||||
updatePlan: () => ({}),
|
||||
refreshRepositories: () => ({}),
|
||||
installUpdate: () => ({}),
|
||||
}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/chimera-beacon-client.js'), {
|
||||
createChimeraBeaconClient: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/console-profile-service.js'), {
|
||||
createConsoleProfileService: () => {
|
||||
const secrets = new Map();
|
||||
return {
|
||||
activeId: () => 'console-1',
|
||||
getSecret: key => secrets.get(String(key)),
|
||||
setSecret: (key, value) => { secrets.set(String(key), value); },
|
||||
syncActiveFromLegacy: () => {},
|
||||
list: () => [],
|
||||
comparison: () => ({ local: [], remote: [] }),
|
||||
save: () => ({}),
|
||||
activate: () => ({}),
|
||||
remove: () => ({}),
|
||||
storageTopology: () => ({ roots: [], games: [] }),
|
||||
};
|
||||
},
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/companion-worker-service.js'), {
|
||||
createCompanionWorkerService: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/audit-log-service.js'), {
|
||||
createAuditLogService: () => ({
|
||||
query: () => [],
|
||||
health: () => ({ ok: true }),
|
||||
exportAudit: () => Buffer.from('[]'),
|
||||
}),
|
||||
normalizeEntry: () => ({}),
|
||||
LEVELS: {},
|
||||
CLASSIFICATIONS: {},
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/ps5-klog-service.js'), {
|
||||
createPs5KlogService: () => ({ start: () => {}, stop: () => {} }),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/security/totp.js'), {
|
||||
createTotpService: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/notification-service.js'), {
|
||||
createNotificationService: () => ({ emit: async () => ({}) }),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/source-classification-service.js'), {
|
||||
createSourceClassificationService: () => ({}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/game-metadata-view.js'), {
|
||||
sanitizeOverride: value => value,
|
||||
annotateRegionalVariants: value => value,
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/security/read-only-sql.js'), {
|
||||
assertReadOnlySql: value => value,
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/server/register-functional-modules.js'), {
|
||||
registerFunctionalModules: () => ({
|
||||
metadata: {
|
||||
queue: () => {},
|
||||
queueDue: () => [],
|
||||
status: () => ({}),
|
||||
sync: () => [],
|
||||
snapshot: () => ({}),
|
||||
},
|
||||
payloadCatalog: {
|
||||
sync: async () => ({ count: 0 }),
|
||||
seed: () => ({ count: 0 }),
|
||||
snapshot: () => ({}),
|
||||
},
|
||||
payloadManager: {},
|
||||
cheatsSync: async () => ({}),
|
||||
}),
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/ezremote-server-client.js'), {
|
||||
createEzRemoteServerClient: () => ({
|
||||
probe: () => ({ status: 'mock' }),
|
||||
downloads: () => [],
|
||||
waitFor: () => ({}),
|
||||
ensureDownload: async () => ({ completed: true, download: { transferred: 123, size: 123, path: '' } }),
|
||||
}),
|
||||
buildDownloadRequest: input => input,
|
||||
DEFAULT_DESTINATION_ROOTS: ['/games/ps4', '/games/ps5'],
|
||||
});
|
||||
mockModule(path.resolve(rootDir, 'src/services/console-scan-reconciler.js'), {
|
||||
reconcileConsoleScan: () => ({ applied: true, upserted: 0, cleared: 0 }),
|
||||
});
|
||||
}
|
||||
|
||||
function withMockedRegistry(callback) {
|
||||
const rootDir = path.resolve(__dirname, '../..');
|
||||
let originalCreateRegistry;
|
||||
const registryPath = path.resolve(rootDir, 'src/server/create-registry.js');
|
||||
let active = true;
|
||||
const cleanup = () => {
|
||||
if (!active) return;
|
||||
active = false;
|
||||
restoreModules();
|
||||
delete require.cache[registryPath];
|
||||
};
|
||||
|
||||
try {
|
||||
installRegistryMocks(rootDir);
|
||||
delete require.cache[registryPath];
|
||||
originalCreateRegistry = require(registryPath);
|
||||
const result = callback(originalCreateRegistry.createRegistry);
|
||||
if (result && typeof result.then === 'function') {
|
||||
return result.finally(cleanup);
|
||||
}
|
||||
cleanup();
|
||||
return result;
|
||||
} finally {
|
||||
if (active === false) return;
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
function createDbWithTracking() {
|
||||
const updates = [];
|
||||
const games = new Map();
|
||||
const db = new Proxy({
|
||||
__updates: updates,
|
||||
updateGame: (gameId, fields) => {
|
||||
updates.push({ gameId, fields });
|
||||
const current = games.get(String(gameId)) || { game_id: String(gameId), ftp_path: '' };
|
||||
games.set(String(gameId), { ...current, ...fields });
|
||||
},
|
||||
getGame: gameId => games.get(String(gameId)) || null,
|
||||
upsertGame: game => { if (game?.game_id) games.set(String(game.game_id), { ...(games.get(String(game.game_id)) || {}), ...game }); },
|
||||
setGamePorked: () => {},
|
||||
listBackporkFolders: () => [],
|
||||
listBackups: () => [],
|
||||
upsertGameMinimal: () => {},
|
||||
setGameFirmwareLabel: () => {},
|
||||
upsertBackup: () => {},
|
||||
pruneGameFirmwareForFolder: () => {},
|
||||
pruneOrphanScanGames: () => {},
|
||||
syncBackedUpFlags: () => {},
|
||||
beginBulkUpdate: () => {},
|
||||
endBulkUpdate: () => {},
|
||||
}, {
|
||||
get(target, property) {
|
||||
return Reflect.get(target, property) || (() => {});
|
||||
},
|
||||
});
|
||||
return { db, updates };
|
||||
}
|
||||
|
||||
function waitForJob(transferState, jobId) {
|
||||
const job = transferState.jobsById.get(jobId);
|
||||
if (!job) throw new Error(`Missing job ${jobId}`);
|
||||
return job.promise;
|
||||
}
|
||||
|
||||
test('FTP upload queue completion does not set installed', async () => {
|
||||
await withMockedRegistry(async (createRegistry) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ch0003-upload-'));
|
||||
try {
|
||||
const localFile = path.join(root, 'game.pkg');
|
||||
fs.writeFileSync(localFile, 'payload');
|
||||
const { db, updates } = createDbWithTracking();
|
||||
db.upsertGame({ game_id: 'PPSA12345', ftp_path: '/existing/path', installed: 1 });
|
||||
const settings = { get: () => '127.0.0.1', set: () => {} };
|
||||
const secrets = { get: () => '', has: () => false };
|
||||
const ftp = { isConnected: () => true, hasCredentials: () => true };
|
||||
const paths = { root, cache: root, backups: root, tmp: root, config: root };
|
||||
const logger = { info() {}, warn() {}, error() {}, audit() {} };
|
||||
const registry = createRegistry({
|
||||
db,
|
||||
ftp,
|
||||
settings,
|
||||
secrets,
|
||||
paths,
|
||||
logger,
|
||||
capabilities: { features: {} },
|
||||
broadcast: () => {},
|
||||
appRoot: root,
|
||||
});
|
||||
|
||||
const upload = registry.commands.get('ftp:upload');
|
||||
assert.ok(upload);
|
||||
const result = upload.handler({ localPath: localFile, remotePath: '/mnt/sandbox' });
|
||||
await waitForJob(activeTransferState, result.jobId);
|
||||
assert.equal(result.success, true);
|
||||
assert.equal(updates.length, 0, 'no db.updateGame calls expected after upload completion');
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
activeTransferState = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('Backpork queue completion writes ftp_path but never flips installed', async () => {
|
||||
await withMockedRegistry(async (createRegistry) => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-ch0003-pork-'));
|
||||
try {
|
||||
const backporkRoot = path.join(root, 'backporks');
|
||||
fs.mkdirSync(backporkRoot, { recursive: true });
|
||||
const gameFolder = path.join(backporkRoot, 'PPSA54321-pack');
|
||||
fs.mkdirSync(gameFolder, { recursive: true });
|
||||
const { db, updates } = createDbWithTracking();
|
||||
db.upsertGame({ game_id: 'PPSA54321', ftp_path: '/mnt/sandbox/pfsmnt/PPSA54321', installed: 1 });
|
||||
const settings = { get: () => '127.0.0.1', set: () => {} };
|
||||
const secrets = { get: () => '', has: () => false };
|
||||
const ftp = { isConnected: () => true, hasCredentials: () => true };
|
||||
const paths = { root, cache: root, backups: root, tmp: root, config: root };
|
||||
const logger = { info() {}, warn() {}, error() {}, audit() {} };
|
||||
const registry = createRegistry({
|
||||
db,
|
||||
ftp,
|
||||
settings,
|
||||
secrets,
|
||||
paths,
|
||||
logger,
|
||||
capabilities: { features: {} },
|
||||
broadcast: () => {},
|
||||
appRoot: root,
|
||||
});
|
||||
|
||||
const pork = registry.commands.get('backporks:pork');
|
||||
assert.ok(pork);
|
||||
const queued = pork.handler({ game_id: 'PPSA54321', folder_name: '11.50.0', folder_path: backporkRoot });
|
||||
await waitForJob(activeTransferState, queued.jobId);
|
||||
assert.equal(updates.length >= 1, true);
|
||||
assert.equal(updates.some(item => item.fields && Object.prototype.hasOwnProperty.call(item.fields, 'ftp_path')), true);
|
||||
const hasInstalled = updates.some(item => item.fields && Object.prototype.hasOwnProperty.call(item.fields, 'installed'));
|
||||
assert.equal(hasInstalled, false, 'backpork completion must not set installed');
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
activeTransferState = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user