import { isDeepStrictEqual } from 'node:util' import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const [report, inventory] = await Promise.all([ readFile(resolve(root, 'config/freecad-property-fileincluded-mutation.json'), 'utf8').then(JSON.parse), readFile(resolve(root, 'config/freecad-property-fileincluded-inventory.json'), 'utf8').then(JSON.parse), ]) const fail = (message) => { throw new Error(`FreeCAD PropertyFileIncluded mutation check failed: ${message}`) } if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-fileincluded-mutation' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyFileIncluded' || report.caseCount !== 20 || report.cases?.length !== 20) fail('baseline or case count is invalid') const keys = report.cases.map(({ objectTypeId, propertyName }) => `${objectTypeId}.${propertyName}`) if (!isDeepStrictEqual(keys, inventory.records.map(({ objectTypeId, propertyName }) => `${objectTypeId}.${propertyName}`))) fail('mutation cases do not cover the locked inventory') for (const [index, entry] of report.cases.entries()) { const record = inventory.records[index] const expectedStatus = record.status.map(String) const expectedEditor = isDeepStrictEqual(record.status, ['ReadOnly']) ? ['ReadOnly'] : [] const snapshots = [entry.before, entry.edited, entry.editedRecompute?.snapshot, entry.restored, entry.restoredRecompute?.snapshot] if (!entry.pathStable || !entry.objectSetStable || entry.beforeSource.sha256 === entry.editedSource.sha256 || entry.beforeSource.bytes === entry.editedSource.bytes && entry.beforeSource.sha256 === entry.editedSource.sha256) fail(`${keys[index]} did not produce a distinct edited resource`) if (entry.beforeSource.sha256 !== entry.before.sha256 || entry.editedSource.sha256 !== entry.edited.sha256 || entry.beforeSource.sha256 !== entry.restored.sha256 || entry.beforeSource.bytes !== entry.before.bytes || entry.editedSource.bytes !== entry.edited.bytes || entry.restored.bytes !== entry.beforeSource.bytes) fail(`${keys[index]} byte/hash mutation or restoration changed`) if (entry.before.baseName !== entry.archiveName || entry.edited.baseName !== entry.archiveName || entry.restored.baseName !== entry.archiveName || snapshots.some((snapshot) => snapshot.propertyTypeId !== 'App::PropertyFileIncluded' || snapshot.writeBits !== 0 || snapshot.underTransientDir !== true || !isDeepStrictEqual(snapshot.propertyStatus, expectedStatus) || !isDeepStrictEqual(snapshot.editorMode, expectedEditor) || snapshot.exists !== true)) fail(`${keys[index]} path/permission/status metadata changed`) if (entry.editedRecompute.result !== !isDeepStrictEqual(expectedStatus, ['27']) || entry.restoredRecompute.result !== !isDeepStrictEqual(expectedStatus, ['27'])) fail(`${keys[index]} recompute result changed`) if (isDeepStrictEqual(expectedStatus, ['27'])) { if (!isDeepStrictEqual(entry.before.objectState, ['Up-to-date']) || !isDeepStrictEqual(entry.edited.objectState, ['Up-to-date']) || !isDeepStrictEqual(entry.restored.objectState, ['Up-to-date']) || entry.edited.statusString !== 'Valid' || entry.restored.statusString !== 'Valid') fail(`${keys[index]} PropOutput touch suppression changed`) } else { if (!isDeepStrictEqual(entry.before.objectState, ['Up-to-date']) || entry.before.statusString !== 'Valid' || !isDeepStrictEqual(entry.edited.objectState, ['Touched']) || !isDeepStrictEqual(entry.restored.objectState, ['Touched']) || entry.edited.statusString !== 'Touched' || entry.restored.statusString !== 'Touched') fail(`${keys[index]} setter touch sequence changed`) if (!isDeepStrictEqual(entry.editedRecompute.snapshot.objectState, ['Up-to-date']) || entry.editedRecompute.snapshot.statusString !== 'Valid' || !isDeepStrictEqual(entry.restoredRecompute.snapshot.objectState, ['Up-to-date']) || entry.restoredRecompute.snapshot.statusString !== 'Valid') fail(`${keys[index]} recompute recovery changed`) } const shapeExpected = entry.objectTypeId === 'Sketcher::SketchObjectSF' if (entry.restored.shape?.applicable !== shapeExpected || shapeExpected && (entry.before.shape.isNull !== true || entry.edited.shape.isNull !== true || entry.restored.shape.isNull !== true || entry.restored.shape.edges !== 0)) fail(`${keys[index]} Shape boundary changed`) } console.log(JSON.stringify({ status: 'freecad-property-fileincluded-mutation-pass', propertyType: report.propertyType, caseCount: report.caseCount, editedCases: report.cases.filter(({ before, edited }) => before.sha256 !== edited.sha256).length, restoredCases: report.cases.filter(({ before, restored }) => before.sha256 === restored.sha256).length, stablePaths: report.cases.filter(({ pathStable }) => pathStable).length, stableObjects: report.cases.filter(({ objectSetStable }) => objectSetStable).length, outputTouchSuppressed: report.cases.find(({ propertyName, objectTypeId }) => objectTypeId === 'TechDraw::DrawSVGTemplate' && propertyName === 'PageResult')?.editedRecompute.result === false }, null, 2))