import assert from "node:assert/strict" import { ModelStatusFeed, ModelManagerFeed, ModelsPage, modelActionAllowed, fitsModelProfile } from "../web/js/models.js" const flush = async () => { for (let i = 0; i < 8; i++) await Promise.resolve() } function fixture() { const requests = [], states = [], timers = new Map() let now = 0, nextTimer = 0, unauthorized = 0 const feed = new ModelStatusFeed({ publish: (state) => states.push(state), unauthorized: () => { unauthorized++ }, fetcher: (url, options) => new Promise((resolve) => requests.push({ url, signal: options.signal, resolve })), later: (fn, ms) => { timers.set(++nextTimer, { fn, due: now + ms }); return nextTimer }, cancel: (id) => timers.delete(id) }) const reply = async (index, body, status = 200) => { requests[index].resolve({ ok: status === 200, status, json: async () => body }) await flush() } const advance = (ms) => { const end = now + ms while (true) { const due = [...timers.entries()].filter(([, timer]) => timer.due <= end).sort((a, b) => a[1].due - b[1].due)[0] if (!due) break now = due[1].due timers.delete(due[0]) due[1].fn() } now = end } return { feed, requests, states, timers, reply, advance, get unauthorized() { return unauthorized } } } const payload = { schemaVersion: 1, catalog: [{ id: "bundled-current", name: "Bundled driving model", selectable: true }], requestedId: "bundled-current", loadedId: "bundled-current", variant: "small", health: "active", artifactSha256: "a".repeat(64), fallbackReason: null, pendingNextStart: false } const normal = fixture() normal.feed.start() assert.equal(normal.requests[0].url, "./api/models/status") await normal.reply(0, payload) assert.equal(normal.states.at(-1).data.health, "active") normal.advance(1000) assert.equal(normal.requests.length, 2) assert.equal(normal.states.at(-1).data.health, "active") normal.advance(500) // stalled next request cannot preserve old active status assert.equal(normal.states.at(-1).status, "stale") assert.equal(normal.states.at(-1).data, null) normal.feed.stop() await normal.reply(1, payload) assert.equal(normal.states.at(-1).status, "idle") assert.equal(normal.timers.size, 0) const late = fixture() late.feed.start() late.feed.stop() late.feed.start() await late.reply(0, {}, 401) assert.equal(late.unauthorized, 0) await late.reply(1, payload) assert.equal(late.states.at(-1).data.health, "active") const body = fixture() body.feed.start() let finishBody body.requests[0].resolve({ ok: true, status: 200, json: () => new Promise((resolve) => { finishBody = resolve }) }) await flush() body.feed.stop() body.feed.start() finishBody(payload) await flush() assert.equal(body.states.at(-1).data, null) await body.reply(1, { ...payload, loadedId: null, variant: null, artifactSha256: null, health: "identity-unavailable" }) assert.equal(body.states.at(-1).data.health, "identity-unavailable") const malformed = fixture() malformed.feed.start() await malformed.reply(0, { ...payload, loadedId: null }) assert.equal(malformed.states.at(-1).status, "unavailable") assert.equal(malformed.states.at(-1).data, null) const priorDocument = globalThis.document const listeners = new Map() globalThis.document = { hidden: false, addEventListener: (name, fn) => listeners.set(name, fn), removeEventListener: (name, fn) => { if (listeners.get(name) === fn) listeners.delete(name) } } const page = { mode: "local", finishDialog() {}, manager: { start() {}, stop() {} }, feed: { starts: 0, stops: 0, start() { this.starts++ }, stop() { this.stops++ } } } ModelsPage.mounted.call(page) assert.equal(page.feed.starts, 1) document.hidden = true listeners.get("visibilitychange")() assert.equal(page.feed.stops, 1) document.hidden = false listeners.get("visibilitychange")() assert.equal(page.feed.starts, 2) ModelsPage.beforeUnmount.call(page) assert.equal(page.feed.stops, 2) assert.equal(listeners.size, 0) globalThis.document = priorDocument assert.match(ModelsPage.template, /offline preview/) assert.match(ModelsPage.template, /Active Small/) assert.match(ModelsPage.template, /None — always use Active Small/) console.log("Model status client lifecycle checks passed") const expanded = fixture() expanded.feed.start() await expanded.reply(0, { ...payload, catalog: [...payload.catalog, { id: "rdf43", name: "RDF V4", selectable: true }], requestedId: "rdf43", pendingNextStart: true, fallbackReason: "selected-load-failed" }) assert.equal(expanded.states.at(-1).status, "ready") expanded.feed.load() await expanded.reply(1, { ...payload, loadedId: "unknown" }) assert.equal(expanded.states.at(-1).status, "unavailable") expanded.feed.stop() const stalledRuntime = fixture() stalledRuntime.feed.start() await stalledRuntime.reply(0, { ...payload, fallbackReason: "chestnut-run-stalled" }) assert.equal(stalledRuntime.states.at(-1).status, "ready") assert.equal(stalledRuntime.states.at(-1).data.fallbackReason, "chestnut-run-stalled") stalledRuntime.feed.load() await stalledRuntime.reply(1, { ...payload, fallbackReason: "invented-reason" }) assert.equal(stalledRuntime.states.at(-1).status, "unavailable") stalledRuntime.feed.stop() const small = { value: "small", label: "Small", requiresGpu: false, installed: true, selectable: true, userFavorite: false } const big = { value: "big", label: "Big", requiresGpu: true, installed: true, selectable: true, userFavorite: true } const pending = { value: "pending", label: "Pending rebuild", requiresGpu: false, installed: false, selectable: false, downloadAvailable: false } const missing = { value: "missing", label: "Missing", requiresGpu: true, installed: false, selectable: false, downloadAvailable: true } const managerPayload = { schemaVersion: 1, models: [small, big, pending, missing], isOnroad: false, currentModel: "small", activeSmallModel: "small", activeBigModel: "big", downloading: false, capabilities: { select: true, favorites: true, download: true, downloadAll: true, cancel: true, delete: true, refresh: true } } assert.equal(modelActionAllowed(managerPayload, "select-small", small), true) assert.equal(modelActionAllowed(managerPayload, "select-big", small), false) assert.equal(modelActionAllowed(managerPayload, "select-small", { ...small, selectable: false }), false) assert.equal(modelActionAllowed(managerPayload, "select-small", { ...small, installed: false }), false) assert.equal(modelActionAllowed(managerPayload, "select-big"), true) assert.equal(modelActionAllowed(managerPayload, "download", pending), false) assert.equal(modelActionAllowed(managerPayload, "download", missing), true) assert.equal(modelActionAllowed(managerPayload, "delete", big), false) assert.equal(modelActionAllowed({ ...managerPayload, activeBigModel: "" }, "delete", big), true) assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true }, "favorite", small), true) assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true }, "select-small", small), false) assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true, downloading: true }, "cancel"), true) assert.equal(modelActionAllowed({ ...managerPayload, capabilities: {} }, "favorite", small), false) assert.equal(fitsModelProfile({ ...small, profiles: ["small", "big"] }, "big"), true) function managerFixture() { const requests = [], updates = [], timers = new Map() let timer = 0, unauthorized = 0 const manager = new ModelManagerFeed({ publish: update => updates.push(update), unauthorized: () => unauthorized++, fetcher: (url, options) => new Promise(resolve => requests.push({ url, options, resolve })), later: (fn, ms) => { timers.set(++timer, { fn, ms }); return timer }, cancel: id => timers.delete(id) }) const reply = async (i, body, status = 200) => { requests[i].resolve({ ok: status === 200, status, json: async () => body }); await flush() } return { manager, requests, updates, timers, reply, get unauthorized() { return unauthorized } } } const management = managerFixture() management.manager.start() await management.reply(0, managerPayload) assert.equal(management.requests[0].url, "./api/models/manager") management.manager.load() const selection = management.manager.action("select-big") assert.equal(management.requests[1].options.signal.aborted, true) assert.deepEqual(JSON.parse(management.requests[2].options.body), { profile: "big", model: "" }) assert.equal(management.requests[2].options.method, "POST") assert.equal(management.requests[2].url, "./api/models/active") await management.reply(1, { ...managerPayload, activeBigModel: "old" }) assert.notEqual(management.manager.data?.activeBigModel, "old") await management.reply(2, { message: "Selected" }) await management.reply(3, { ...managerPayload, activeBigModel: "" }) await selection assert.equal(management.manager.data.activeBigModel, "") const favorite = management.manager.action("favorite", small) assert.deepEqual(JSON.parse(management.requests[4].options.body), { userFavorites: ["big", "small"] }) await management.reply(4, { error: "Favorites were not saved" }, 409) await management.reply(5, managerPayload) await favorite assert.equal(management.updates.at(-1).error, "Favorites were not saved") management.manager.data = { ...managerPayload, models: managerPayload.models.map(m => m.value === "missing" ? { ...m, downloadAvailable: false } : m) } assert.equal(await management.manager.action("download", missing), null) assert.equal(management.requests.length, 6) management.manager.data = managerPayload const download = management.manager.action("download", missing, { allowGpuWithoutGpu: true }) assert.deepEqual(JSON.parse(management.requests[6].options.body), { model: "missing", allowGpuWithoutGpu: true }) management.manager.stop() await management.reply(6, {}, 401) await download assert.equal(management.unauthorized, 0) assert.equal(management.timers.size, 0) const access = managerFixture() access.manager.start() await access.reply(0, { code: "access_unavailable" }, 503) assert.equal(access.unauthorized, 1) assert.equal(access.timers.size, 0) const expiry = managerFixture() expiry.manager.start() await expiry.reply(0, managerPayload) const timedAction = expiry.manager.action("select-small", small) ;[...expiry.timers.values()].find(t => t.ms === 10000).fn() assert.equal(expiry.manager.data, null) assert.match(expiry.updates.at(-1).error, /result is unknown/) await expiry.reply(1, { message: "late success" }) await flush() if (expiry.requests[2]) await expiry.reply(2, managerPayload) await timedAction expiry.manager.stop() console.log("Model manager guarded actions and source retirement checks passed") const randomPayload = { ...managerPayload, randomizer: true, blacklistedModels: ['big'], capabilities: { ...managerPayload.capabilities, randomizer: true, exclusions: true } } assert.equal(modelActionAllowed(randomPayload, 'select-small', small), false) assert.equal(modelActionAllowed(randomPayload, 'select-big'), false) assert.equal(modelActionAllowed(randomPayload, 'disable-randomizer'), true) assert.equal(modelActionAllowed({ ...randomPayload, isOnroad: true }, 'exclude', small), false) assert.equal(modelActionAllowed({ ...randomPayload, isOnroad: true }, 'enable-randomizer'), false) assert.equal(modelActionAllowed({ ...randomPayload, isOnroad: true }, 'favorite', small), true) assert.equal(modelActionAllowed(randomPayload, 'exclude', pending), true) assert.match(ModelsPage.template, /Model Randomizer/) assert.match(ModelsPage.template, /Include in Randomizer/) assert.match(ModelsPage.template, /An external GPU is not detected/) const randomManagement = managerFixture() randomManagement.manager.start() await randomManagement.reply(0, randomPayload) const excluded = randomManagement.manager.action('exclude', small) assert.equal(randomManagement.requests[1].url, './api/models/preferences') assert.deepEqual(JSON.parse(randomManagement.requests[1].options.body), { blacklistedModels: ['big', 'small'] }) await randomManagement.reply(1, { message: 'Saved' }) await randomManagement.reply(2, { ...randomPayload, blacklistedModels: ['big', 'small'] }) await excluded const disabledRandomizer = randomManagement.manager.action('disable-randomizer') assert.deepEqual(JSON.parse(randomManagement.requests[3].options.body), { randomizer: false }) await randomManagement.reply(3, { message: 'Saved' }) await randomManagement.reply(4, { ...randomPayload, randomizer: false }) await disabledRandomizer randomManagement.manager.stop() console.log('Randomizer and exclusion interactions passed') assert.match(ModelsPage.template, /Park the vehicle before changing or downloading models/) assert.doesNotMatch(ModelsPage.template, /Onroad: switching/) assert.equal((ModelsPage.template.match(/class="gx-model-control"/g) || []).length, 2) assert.equal(ModelsPage.data().hardwareFilter, 'both') const hardware = { hardwareFilter: 'both', setHardwareFilter(value) { this.hardwareFilter = value } } ModelsPage.methods.toggleHardware.call(hardware, 'gpu', false) assert.equal(hardware.hardwareFilter, 'comma') ModelsPage.methods.toggleHardware.call(hardware, 'comma', false) assert.equal(hardware.hardwareFilter, 'none') ModelsPage.methods.toggleHardware.call(hardware, 'gpu', true) assert.equal(hardware.hardwareFilter, 'gpu') ModelsPage.methods.toggleHardware.call(hardware, 'comma', true) assert.equal(hardware.hardwareFilter, 'both') const randomSwitch = { target: { checked: true } } let randomAction = '' ModelsPage.methods.toggleRandomizer.call({ status: { randomizer: false }, runAction(action) { randomAction = action } }, randomSwitch) assert.equal(randomAction, 'enable-randomizer') assert.equal(randomSwitch.target.checked, false) const chargeSwitch = { target: { checked: true } } let chargeChoice ModelsPage.methods.toggleJetlinkCharge.call({ status: { jetlink: { mode: 'ios', chargePhone: false } }, configureJetlink(mode, enabled) { chargeChoice = [mode, enabled] } }, chargeSwitch) assert.deepEqual(chargeChoice, ['ios', true]) assert.equal(chargeSwitch.target.checked, false) const feedbackPage = { ...ModelsPage.data(), unauthorized() {}, canAction() { return true } } ModelsPage.created.call(feedbackPage) const publishManager = feedbackPage.manager.publish feedbackPage.manager.action = async () => { publishManager({ loading: false, error: '', data: { ...managerPayload, downloading: true, progress: 'Downloading 20%' } }) return { message: 'Starting download' } } await ModelsPage.methods.runAction.call(feedbackPage, 'download', { ...missing, requiresGpu: false }) assert.equal(feedbackPage.message, 'Downloading 20%') assert.equal(feedbackPage.trackingProgress, true) publishManager({ loading: false, error: '', data: { ...managerPayload, downloading: false, progress: 'Downloaded!', summary: { installed: 2, missing: 98, total: 100 } } }) assert.equal(feedbackPage.message, 'Downloaded!') assert.equal(feedbackPage.trackingProgress, false) assert.equal(feedbackPage.summary.installed, 2) console.log('Narrow model controls and completed operation feedback checks passed') // Connection settings share the parked/authenticated action path, not catalog selection. const jetlinkView = { isOnroad: false, capabilities: { jetlink: true }, jetlink: { mode: "off" } } assert.equal(modelActionAllowed(jetlinkView, "jetlink"), true) assert.equal(modelActionAllowed({ ...jetlinkView, isOnroad: true }, "jetlink"), false) assert.equal(modelActionAllowed({ ...jetlinkView, capabilities: {} }, "jetlink"), false) assert.match(ModelsPage.template, /Prepared means the remote model is built, not active/) const linkManagement = managerFixture() linkManagement.manager.start() await linkManagement.reply(0, { ...managerPayload, ...jetlinkView }) const linkSelection = linkManagement.manager.action("jetlink", null, { mode: "ios", chargePhone: false }) assert.equal(linkManagement.requests[1].url, "./api/models/jetlink") assert.deepEqual(JSON.parse(linkManagement.requests[1].options.body), { mode: "ios", chargePhone: false }) await linkManagement.reply(1, { message: "Saved" }) await linkManagement.reply(2, { ...managerPayload, ...jetlinkView, jetlink: { mode: "ios" } }) await linkSelection assert.equal(linkManagement.manager.data.jetlink.mode, "ios") linkManagement.manager.stop() const liveLink = managerFixture() liveLink.manager.start() await liveLink.reply(0, { ...managerPayload, ...jetlinkView, jetlink: { mode: "usb", active: true, state: "active" } }) const executionExpiry = [...liveLink.timers.values()].find(timer => timer.ms === 1500) assert.ok(executionExpiry) executionExpiry.fn() assert.equal(liveLink.manager.data.jetlink.active, false) assert.equal(liveLink.manager.data.jetlink.state, "checking execution") liveLink.manager.stop() assert.ok(ModelsPage.template.includes('href="https://github.com/StarPilotHQ/StarPilot/blob/SecretGoodStarPilot/openpilot/starpilot/models/JETLINK.md"'), "Jetlink setup guide points at the branch that contains it")