Files
chimera-Public/test/server/ch-0403-recipe-editor.test.js

46 lines
4.3 KiB
JavaScript

'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 database = require('../../src/database');
const { createInstallRecipeRepository } = require('../../src/services/install-recipe-repository');
const { createV3Repositories } = require('../../src/services/v3-repositories');
const { draftFromPackageSet, editorError, moveItem } = require('../../src/renderer/recipe-editor');
function withDatabase(run) { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-recipe-editor-')); database.configure({ dataDir: root, logger: { info() {}, warn() {}, error() {} } }); return database.initialize().then(run).finally(() => { database.close(); fs.rmSync(root, { recursive: true, force: true }); }); }
function packageSet() { return { id: 1, titleId: 1, setKey: 'retail', label: 'Retail US', variantKey: 'retail-us', platform: 'ps4', region: 'US', components: [{ componentId: 'base', type: 'base', required: true, version: '1.00', contentId: 'UP0001-CUSA12345_00-BASEPACKAGE000001', region: 'US', metadata: { platform: 'ps4', titleId: { value: 'CUSA12345' } }, adapterHint: 'ps4-package' }, { componentId: 'update', type: 'update', required: true, version: '1.02', contentId: 'UP0001-CUSA12345_00-UPDATEPACKAGE001', region: 'US', metadata: { platform: 'ps4', titleId: { value: 'CUSA12345' } }, adapterHint: 'ps4-package' }], dependencies: [{ componentId: 'update', dependsOnComponentId: 'base', kind: 'requires' }] }; }
test('keyboard move alternative and actionable UI error projection are deterministic', () => {
assert.deepEqual(moveItem(['base', 'update'], 1, -1), ['update', 'base']);
assert.deepEqual(moveItem(['base', 'update'], 0, -1), ['base', 'update']);
assert.deepEqual(editorError(Object.assign(new Error('Cycle between base and update'), { code: 'RECIPE_CYCLE' })), { titleKey: 'recipeEditor.errorTitle', message: 'Cycle between base and update', code: 'RECIPE_CYCLE' });
const draft = draftFromPackageSet(packageSet());
assert.deepEqual(draft[1].dependsOn, ['step-1']);
});
test('saving creates immutable incrementing recipe versions and rejects stale editors', () => withDatabase(() => {
database.runMutatingSqlWithParams("INSERT INTO titles (id,platform,title_id,name,sort_name) VALUES (1,'ps4','CUSA12345','Recipe','Recipe')");
const set = packageSet();
createV3Repositories(database).packageSets.savePackageSet({ ...set, components: set.components.map((item, index) => ({ ...item, originalRelativePath: `${item.componentId}.pkg`, normalizedRelativePath: `${item.componentId}.pkg`, size: 4, sha256: String(index + 1).repeat(64), provenance: [{ source: 'test' }] })) });
const savedSet = createV3Repositories(database).packageSets.listByTitle(1)[0];
const repository = createInstallRecipeRepository({ db: database, now: () => 10 });
const steps = draftFromPackageSet(savedSet);
const first = repository.saveVersion({ packageSet: savedSet, recipeKey: 'safe', label: 'Safe', notes: 'Read first', steps, expectedVersion: 0 });
const second = repository.saveVersion({ packageSet: savedSet, recipeKey: 'safe', label: 'Safe', notes: 'Read again', steps, expectedVersion: 1 });
assert.equal(first.version, 1); assert.equal(second.version, 2); assert.notEqual(first.recipeSha256, second.recipeSha256);
assert.equal(repository.listByTitle(1).length, 2);
assert.throws(() => repository.saveVersion({ packageSet: savedSet, recipeKey: 'safe', notes: 'stale', steps, expectedVersion: 1 }), error => error.code === 'RECIPE_VERSION_CONFLICT');
assert.equal(database.runQuery('SELECT COUNT(*) AS c FROM install_recipe_steps')[0].c, 4);
}));
test('editor exposes drag and keyboard controls, live errors, version selection and expert disclosure', () => {
const root = path.resolve(__dirname, '../..');
const source = fs.readFileSync(path.join(root, 'src/renderer/recipe-editor.js'), 'utf8');
const css = fs.readFileSync(path.join(root, 'src/renderer/recipe-editor.css'), 'utf8');
assert.match(source, /draggable=\"true\"/); assert.match(source, /data-step-up/); assert.match(source, /aria-live=\"polite\"/); assert.match(source, /data-recipe-select/); assert.match(source, /data-recipe-version/); assert.match(source, /recipe-expert/);
assert.match(css, /body:not\(\.chimera-expert-mode\) \.recipe-expert/); assert.match(css, /@media\(max-width:650px\)/);
});