Files
lms-be/scripts/add-week3-jetson-camera.test.cjs

79 lines
4.3 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const source = fs.readFileSync(path.join(__dirname, 'add-week3-jetson-camera.cjs'), 'utf8')
.replace(/main\(\)\.catch[\s\S]*$/, 'module.exports = { main };');
const ids = ['4eb01197-7605-4c3b-9a92-f9a3942050bd', '8e296f81-8445-455c-b1a2-47f2ce9099a4', '26d46589-31a5-4d94-b0e0-385a12c41721'];
const image = 'future-nav2-camera-test:humble-jetson_orin';
const release = { runtimeImages: [{ image, url: 'https://example.test/image', checksumSha256: 'a'.repeat(64) }],
prebuiltArtifacts: [{ architecture: 'arm64', url: 'https://example.test/prebuilt', checksumSha256: 'b'.repeat(64) }] };
function fixture() {
return ids.map((id, i) => ({ id, title: `Course ${i}`, slug: i ? `course-${i}` : 'futuredrive-autonomy-basic',
weeks: [{ id: `${i}-w1`, week: 1, modules: [] }, { id: `${i}-w2`, week: 2, modules: [] },
{ id: `${i}-w3`, week: 3, modules: [{ id: `${i}-mk`, name: 'NAV2-MKMini', folderName: 'mkmini', sortOrder: 1 }] }],
environmentProfiles: ['local', 'remote-jetson'].map(target => ({ id: `${i}-${target}`, week: 3,
config: { target, otherSetting: 'preserve', moduleSet: { includeUnlisted: false, modules: [{ id: `${i}-mk` }] },
runtimeImages: [{ image: 'future-nav2:humble-jetson_orin', url: 'existing' }] } })),
}));
}
function harness(apply, initial = fixture()) {
let state = structuredClone(initial);
const db = {
course: { findMany: async () => structuredClone(state) },
courseWeekModule: {
create: async ({ data }) => {
const week = state.flatMap(c => c.weeks).find(w => w.id === data.weekId);
const m = { id: `${week.id}-camera`, ...structuredClone(data) };
week.modules.push(m); return structuredClone(m);
},
update: async ({ where, data }) => {
const m = state.flatMap(c => c.weeks.flatMap(w => w.modules)).find(m => m.id === where.id);
Object.assign(m, structuredClone(data)); return structuredClone(m);
},
},
courseEnvironmentProfile: { update: async ({ where, data }) => {
const p = state.flatMap(c => c.environmentProfiles).find(p => p.id === where.id);
Object.assign(p, structuredClone(data)); return structuredClone(p);
} },
$transaction: async callback => {
const before = structuredClone(state);
try { return await callback(db); } catch (error) { state = before; throw error; }
},
};
const context = { module: { exports: {} }, URL, structuredClone, console: { log() {} },
fetch: async () => ({ ok: true }), AbortSignal,
process: { argv: apply ? ['--apply'] : [], env: { DATABASE_URL: 'postgresql://user:password@localhost/lms' } },
require: name => {
if (name === 'dotenv') return { config() {} };
if (name === '@prisma/client') return { PrismaClient: function () { return db; } };
if (name === 'node:fs') return { readFileSync: () => JSON.stringify(release), mkdirSync() {}, writeFileSync() {} };
return require(name);
},
};
vm.runInNewContext(source, context);
return { main: context.module.exports.main, state: () => structuredClone(state) };
}
(async () => {
const dry = harness(false);
await dry.main();
assert.deepEqual(dry.state(), fixture());
const applied = harness(true);
await applied.main();
for (const c of applied.state()) {
assert.equal(c.weeks.find(w => w.week === 3).modules.length, 2);
assert.equal(c.weeks.find(w => w.week === 3).modules.find(m => m.name === 'NAV2-Camera').category, 'Camera');
assert.equal(c.environmentProfiles.find(p => p.config.target === 'remote-jetson').config.moduleSet.modules.length, 2);
assert.equal(c.environmentProfiles.find(p => p.config.target === 'local').config.moduleSet.modules.length, 1);
}
const once = applied.state();
await applied.main();
assert.deepEqual(applied.state(), once);
const broken = fixture();
broken[0].environmentProfiles = broken[0].environmentProfiles.filter(p => p.config.target === 'local');
const rejected = harness(true, broken);
await assert.rejects(rejected.main());
assert.deepEqual(rejected.state(), broken);
console.log('Camera registration dry-run, preservation, idempotency and profile guard tests passed.');
})().catch(error => { console.error(error); process.exitCode = 1; });