35 lines
3.7 KiB
JavaScript
35 lines
3.7 KiB
JavaScript
import { createHash } from 'node:crypto'
|
|
import { readFile } from 'node:fs/promises'
|
|
import { resolve } from 'node:path'
|
|
|
|
const root = resolve(new URL('..', import.meta.url).pathname)
|
|
const sourcePath = resolve(root, '.cache/freecad/reference-desktop.json')
|
|
const reportPath = resolve(root, 'config/freecad-property-force-inventory.json')
|
|
const fail = (message) => { throw new Error(`FreeCAD PropertyForce inventory check failed: ${message}`) }
|
|
const [sourceContent, reportContent] = await Promise.all([readFile(sourcePath), readFile(reportPath)])
|
|
const source = JSON.parse(sourceContent)
|
|
const report = JSON.parse(reportContent)
|
|
|
|
if (report.schemaVersion !== 1 || report.status !== 'pass' || report.propertyType !== 'App::PropertyForce' || report.classification !== 'opaque-fcstd-proxy') fail('report boundary is invalid')
|
|
if (report.baseline?.freecadVersion !== '1.1.1' || report.baseline?.commit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d') fail('baseline is not locked to FreeCAD 1.1.1')
|
|
if (report.source?.path !== '.cache/freecad/reference-desktop.json' || report.source.bytes !== sourceContent.length || report.source.sha256 !== createHash('sha256').update(sourceContent).digest('hex')) fail('source provenance is stale')
|
|
if (report.recordCount !== 4 || report.writableRecordCount !== 1 || report.outputRecordCount !== 3 || report.records?.length !== 4) fail('record cardinality is invalid')
|
|
const expected = [
|
|
['Fem::ConstraintForce', 'Force', '', '[]', true],
|
|
['Fem::ConstraintRigidBody', 'ForceX', 'ConstraintRigidBody', '[27]', false],
|
|
['Fem::ConstraintRigidBody', 'ForceY', 'ConstraintRigidBody', '[27]', false],
|
|
['Fem::ConstraintRigidBody', 'ForceZ', 'ConstraintRigidBody', '[27]', false],
|
|
]
|
|
for (const [objectTypeId, propertyName, group, status, writable] of expected) {
|
|
const record = report.records.find((candidate) => candidate.objectTypeId === objectTypeId && candidate.propertyName === propertyName)
|
|
if (!record || record.objectAvailable !== true || record.probeStatus !== 'available' || record.group !== group || JSON.stringify(record.status) !== status || record.defaultRaw !== '0.0 mm*kg/s^2') fail(`${objectTypeId}.${propertyName} inventory changed`)
|
|
const model = record.valueModel
|
|
if (model?.kind !== 'quantity' || model.dimension !== 'mass*length/time^2' || model.internalUnit !== 'mm*kg/s^2' || model.displayUnit !== 'N' || model.newtonScale !== 1000 || model.defaultValue !== 0 || model.writable !== writable || model.derivedOutput !== !writable) fail(`${objectTypeId}.${propertyName} value model is invalid`)
|
|
if (record.dependencies?.length !== 0 || record.applicability?.requiredObjectTypeId !== objectTypeId) fail(`${objectTypeId}.${propertyName} applicability is incomplete`)
|
|
}
|
|
const nativeMatches = []
|
|
for (const objectType of source.runtimeObjects?.types ?? []) for (const property of objectType.properties ?? []) if (property.typeId === 'App::PropertyForce') nativeMatches.push({ objectTypeId: objectType.typeId, propertyName: property.name, group: property.group, status: property.status, defaultRaw: property.default })
|
|
const reportMatches = report.records.map(({ objectTypeId, propertyName, group, status, defaultRaw }) => ({ objectTypeId, propertyName, group, status, defaultRaw }))
|
|
if (JSON.stringify(nativeMatches) !== JSON.stringify(reportMatches)) fail('report does not match the locked runtime oracle')
|
|
console.log(JSON.stringify({ status: 'freecad-property-force-inventory-pass', propertyType: report.propertyType, recordCount: report.recordCount, writableRecordCount: report.writableRecordCount, outputRecordCount: report.outputRecordCount, objectTypes: [...new Set(report.records.map(({ objectTypeId }) => objectTypeId))], classification: report.classification }, null, 2))
|