import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { after, before, test } from "node:test"; import { createServer } from "vite"; let server; let parseDevicePluginManifest; let createDevicePluginRegistry; let xgridsK1Manifest; let xgridsK1Actions; let xgridsK1Api; let ApiError; let localizeRuntimeMessage; let lifecycle; let projectName; let automaticSourceStart; let presentation; let operatorIntentGeneration; let configuration; before(async () => { server = await createServer({ appType: "custom", logLevel: "silent", server: { middlewareMode: true }, }); ({ parseDevicePluginManifest } = await server.ssrLoadModule( "/src/core/device-plugins/manifestParser.ts", )); ({ createDevicePluginRegistry } = await server.ssrLoadModule( "/src/core/device-plugins/registry.ts", )); ({ xgridsK1Manifest, xgridsK1Actions } = await server.ssrLoadModule( "@xgrids-k1/frontend/manifest.ts", )); lifecycle = await server.ssrLoadModule( "@xgrids-k1/frontend/lifecycle.ts", ); projectName = await server.ssrLoadModule( "@xgrids-k1/frontend/projectName.ts", ); automaticSourceStart = await server.ssrLoadModule( "@xgrids-k1/frontend/automaticSourceStart.ts", ); presentation = await server.ssrLoadModule( "@xgrids-k1/frontend/presentation.ts", ); operatorIntentGeneration = await server.ssrLoadModule( "@xgrids-k1/frontend/operatorIntentGeneration.ts", ); configuration = await server.ssrLoadModule( "@xgrids-k1/frontend/configuration.ts", ); ({ xgridsK1Api, ApiError } = await server.ssrLoadModule( "@xgrids-k1/frontend/api.ts", )); ({ localizeRuntimeMessage } = await server.ssrLoadModule( "@xgrids-k1/frontend/messages.ts", )); }); after(async () => { await server?.close(); }); function manifestDocument({ apiVersion = "missioncore.nodedc/v1alpha1", pluginId = "test.device.plugin", modelId = "test.device.model", } = {}) { const v1alpha2 = apiVersion === "missioncore.nodedc/v1alpha2"; return { apiVersion, kind: "DevicePlugin", metadata: { id: pluginId, version: "1.0.0", displayName: "Test device", }, spec: { hostApiRange: v1alpha2 ? "v1alpha2" : "v1alpha1", runtime: { backendEntrypoint: "test.plugin:build", isolation: "transitional-in-process", }, ...(v1alpha2 ? { compatibilityProfiles: [ { profileId: `${modelId}.fw-1.v1`, path: "profiles/fw-1/profile.v1.json", modelId, }, ], } : {}), permissions: ["device.read"], actions: [{ id: "state.read", mutating: false, secretFields: [] }], models: [ { id: modelId, vendor: "Test", displayName: "Test model", category: "Sensor", description: "Fixture model", verified: true, capabilities: [{ id: "device.read", label: "Read" }], ui: { slot: "device.connection", componentKey: "test.connection" }, }, ], }, }; } function uiPlugin(manifest) { return { manifest, RuntimeProvider: ({ children }) => children, connectionViews: { "test.connection": () => null }, }; } test("parser accepts reviewed v1alpha1 and v1alpha2 shapes", () => { const legacy = parseDevicePluginManifest(manifestDocument()); const current = parseDevicePluginManifest( manifestDocument({ apiVersion: "missioncore.nodedc/v1alpha2" }), ); assert.equal(legacy.apiVersion, "missioncore.nodedc/v1alpha1"); assert.equal(legacy.spec.hostApiRange, "v1alpha1"); assert.equal(current.apiVersion, "missioncore.nodedc/v1alpha2"); assert.equal(current.spec.hostApiRange, "v1alpha2"); assert.deepEqual(current.spec.compatibilityProfiles, [ { profileId: "test.device.model.fw-1.v1", path: "profiles/fw-1/profile.v1.json", modelId: "test.device.model", }, ]); }); test("v1alpha1 keeps the original nonblank identifier compatibility", () => { const legacyDocument = manifestDocument({ pluginId: "legacy plugin id", modelId: "legacy model id", }); legacyDocument.spec.permissions = ["legacy permission"]; legacyDocument.spec.actions[0].secretFields = ["legacy secret field"]; legacyDocument.spec.models[0].capabilities = [ { id: "legacy capability", label: "Legacy" }, ]; const legacy = parseDevicePluginManifest(legacyDocument); assert.equal(legacy.metadata.id, "legacy plugin id"); assert.equal(legacy.spec.models[0].id, "legacy model id"); assert.deepEqual(legacy.spec.permissions, ["legacy permission"]); }); test("v1alpha2 applies strict identifiers without redefining v1alpha1", () => { const current = manifestDocument({ apiVersion: "missioncore.nodedc/v1alpha2", pluginId: "current plugin id", }); assert.throws( () => parseDevicePluginManifest(current), /не является идентификатором/, ); }); test("v1alpha2 parser and registry accept multiple independently profiled models", () => { const document = manifestDocument({ apiVersion: "missioncore.nodedc/v1alpha2", pluginId: "test.family", modelId: "test.family.model-a", }); document.spec.models.push({ ...document.spec.models[0], id: "test.family.model-b", displayName: "Test model B", }); document.spec.compatibilityProfiles.push({ profileId: "test.family.model-b.fw-1.v1", path: "profiles/fw-1/model-b.v1.json", modelId: "test.family.model-b", }); const manifest = parseDevicePluginManifest(document); const plugin = { ...uiPlugin(manifest), connectionViews: { "test.connection": () => null }, }; const registry = createDevicePluginRegistry([plugin]); assert.deepEqual(registry.models.map(({ model }) => model.id), [ "test.family.model-a", "test.family.model-b", ]); }); test("installed XGRIDS frontend manifest exposes the semantic v1alpha2 actions", () => { assert.equal(xgridsK1Manifest.apiVersion, "missioncore.nodedc/v1alpha2"); assert.deepEqual(xgridsK1Manifest.spec.compatibilityProfiles, [ { profileId: "xgrids.lixelkity-k1.fw-3.0.2.direct-lan.v1", path: "profiles/fw-3.0.2/direct-lan.v1.json", modelId: "xgrids.lixelkity-k1", }, ]); assert.equal(xgridsK1Actions.acquisitionPrepare, "acquisition.prepare"); assert.equal(xgridsK1Actions.acquisitionStart, "acquisition.start"); assert.equal(xgridsK1Actions.acquisitionStop, "acquisition.stop"); }); test("one operator action can open at most one K1 control session", () => { assert.equal(lifecycle.controlSessionEntryPlan("idle", false, true), "open"); assert.equal(lifecycle.controlSessionEntryPlan("failed", false, true), "open"); assert.equal(lifecycle.controlSessionEntryPlan("failed", false, false), "failed"); assert.equal(lifecycle.controlSessionEntryPlan("failed", true, true), "failed"); assert.equal( lifecycle.controlSessionEntryPlan("idle", true, true), "duplicate-open", ); assert.equal( lifecycle.controlSessionEntryPlan("connection-ready", true, false), "continue", ); }); test("K1 operator intent stays invalid after runtime deactivate and reactivate", () => { const generation = new operatorIntentGeneration.OperatorIntentGeneration(); generation.activateRuntime(); const oldIntent = generation.beginOperatorIntent(); assert.ok(oldIntent); assert.equal(generation.isOperatorIntentCurrent(oldIntent), true); generation.deactivateRuntime(); generation.activateRuntime(); const freshIntent = generation.beginOperatorIntent(); assert.ok(freshIntent); assert.notEqual(freshIntent.runtimeGeneration, oldIntent.runtimeGeneration); assert.equal(generation.isOperatorIntentCurrent(oldIntent), false); assert.equal(generation.isOperatorIntentCurrent(freshIntent), true); }); test("stale K1 intent cannot continue past an await after runtime reactivation", async () => { const generation = new operatorIntentGeneration.OperatorIntentGeneration(); generation.activateRuntime(); const oldIntent = generation.beginOperatorIntent(); assert.ok(oldIntent); let resolveOldRead; const oldRead = new Promise((resolve) => { resolveOldRead = resolve; }); const writes = []; const assertOldIntent = () => { if (!generation.isOperatorIntentCurrent(oldIntent)) { throw new Error("stale operator intent"); } }; const oldContinuation = operatorIntentGeneration.awaitWhileIntentCurrent( assertOldIntent, () => oldRead, ).then(() => writes.push("old-checkpoint")); generation.deactivateRuntime(); generation.activateRuntime(); const freshIntent = generation.beginOperatorIntent(); assert.ok(freshIntent); resolveOldRead({ phase: "connection-ready" }); await assert.rejects(oldContinuation, /stale operator intent/); assert.deepEqual(writes, []); const assertFreshIntent = () => { if (!generation.isOperatorIntentCurrent(freshIntent)) { throw new Error("stale fresh intent"); } }; await operatorIntentGeneration.awaitWhileIntentCurrent( assertFreshIntent, async () => ({ phase: "connection-ready" }), ); writes.push("fresh-checkpoint"); assert.deepEqual(writes, ["fresh-checkpoint"]); }); test("a fresh explicit K1 intent supersedes the previous intent in one runtime", () => { const generation = new operatorIntentGeneration.OperatorIntentGeneration(); generation.activateRuntime(); const first = generation.beginOperatorIntent(); const second = generation.beginOperatorIntent(); assert.ok(first); assert.ok(second); assert.equal(first.runtimeGeneration, second.runtimeGeneration); assert.notEqual(first.intentGeneration, second.intentGeneration); assert.equal(generation.isOperatorIntentCurrent(first), false); assert.equal(generation.isOperatorIntentCurrent(second), true); }); test("canonical K1 launch wires the generation guard through every async stage", async () => { const hookSource = await readFile( new URL( "../../../plugins/xgrids-k1/frontend/src/useXgridsK1Runtime.ts", import.meta.url, ), "utf8", ); const canonicalStart = hookSource.slice( hookSource.indexOf("const startCanonicalAcquisition"), hookSource.indexOf("const prepareAcquisition"), ); const pollingLoop = hookSource.slice( hookSource.indexOf("async function waitForControlPhase"), hookSource.indexOf("function messageFor"), ); assert.doesNotMatch(hookSource, /mounted\.current/); assert.match(canonicalStart, /beginOperatorIntent\(\)/); assert.match(canonicalStart, /isOperatorIntentCurrent\(intentToken\)/); assert.doesNotMatch(canonicalStart, /await xgridsK1Api\./); assert.doesNotMatch(canonicalStart, /await waitForControlPhase\(/); assert.ok( canonicalStart.match(/awaitWhileIntentCurrent\(/g)?.length >= 8, "each canonical REST/checkpoint boundary must use the intent guard", ); assert.match(pollingLoop, /for \(;;\) \{\s*assertOperatorIntentCurrent\(\)/); assert.equal(pollingLoop.match(/awaitWhileIntentCurrent\(/g)?.length, 2); }); test("K1 control errors stay informative and only fetch failures mark transport unavailable", async () => { assert.equal( localizeRuntimeMessage( "control MQTT connect call failed: [Errno 61] Connection refused", ), "Управляющее соединение со сканером не открылось: устройство не приняло MQTT-соединение. Команды сканирования не отправлялись.", ); const domainError = new ApiError("Диалог остановлен до команды START."); assert.equal(domainError.transportUnavailable, false); const originalFetch = globalThis.fetch; globalThis.fetch = async () => { throw new TypeError("synthetic network failure"); }; try { await assert.rejects( () => xgridsK1Api.getState(), (error) => error instanceof ApiError && error.transportUnavailable === true, ); } finally { globalThis.fetch = originalFetch; } }); test("v1alpha2 compatibility profile is exact-key and rejects duplicated profile status", () => { const document = manifestDocument({ apiVersion: "missioncore.nodedc/v1alpha2" }); document.spec.compatibilityProfiles[0].status = "verified"; assert.throws( () => parseDevicePluginManifest(document), /неизвестные поля status/, ); }); test("v1alpha2 compatibility profile fails closed on unsafe paths", () => { const document = manifestDocument({ apiVersion: "missioncore.nodedc/v1alpha2" }); document.spec.compatibilityProfiles[0].path = "../private/profile.json"; assert.throws( () => parseDevicePluginManifest(document), /безопасным относительным JSON-путём/, ); }); test("v1alpha2 compatibility profile cannot reference an unknown model", () => { const document = manifestDocument({ apiVersion: "missioncore.nodedc/v1alpha2" }); document.spec.compatibilityProfiles[0].modelId = "missing.model"; assert.throws( () => parseDevicePluginManifest(document), /ссылается на неизвестную модель missing\.model/, ); }); test("registry accepts v1alpha1 and v1alpha2 plugins together", () => { const legacy = parseDevicePluginManifest( manifestDocument({ pluginId: "test.legacy", modelId: "test.legacy.model" }), ); const current = parseDevicePluginManifest( manifestDocument({ apiVersion: "missioncore.nodedc/v1alpha2", pluginId: "test.current", modelId: "test.current.model", }), ); const registry = createDevicePluginRegistry([uiPlugin(legacy), uiPlugin(current)]); assert.equal(registry.plugins.length, 2); assert.equal(registry.models.length, 2); assert.equal(registry.resolveModel("test.current.model")?.plugin.manifest.metadata.id, "test.current"); }); test("registry independently rejects an uncovered v1alpha2 model", () => { const current = parseDevicePluginManifest( manifestDocument({ apiVersion: "missioncore.nodedc/v1alpha2" }), ); current.spec.compatibilityProfiles[0].modelId = "missing.model"; assert.throws( () => createDevicePluginRegistry([uiPlugin(current)]), /ссылается на неизвестную модель missing\.model/, ); }); test("live source is confirmed only by an acquiring acquisition", () => { const waiting = { source_mode: "live", phase: "live", acquisition: { state: "awaiting_external_start" }, }; const acquiring = { ...waiting, acquisition: { state: "acquiring" }, }; assert.equal(lifecycle.isConfirmedLiveState(waiting), false); assert.equal(lifecycle.confirmedRuntimeSourceMode(waiting), "idle"); assert.equal(lifecycle.sourceStatusLabel(waiting), "Ожидание реальных данных"); assert.equal(lifecycle.isConfirmedLiveState(acquiring), true); assert.equal(lifecycle.confirmedRuntimeSourceMode(acquiring), "live"); }); test("prepared acquisition resumes without another prepare and remains recoverable", () => { const prepared = { source_mode: "idle", acquisition: { acquisition_id: "acq-1", state: "prepared", }, }; assert.equal(lifecycle.liveStartPlan(prepared), "resume-prepared"); assert.equal(lifecycle.recoverableAcquisition(prepared)?.acquisition_id, "acq-1"); }); test("project name is canonicalized and rejected outside the bounded safe contract", () => { assert.deepEqual(projectName.validateProjectName(" Mission 01 "), { value: "Mission 01", error: null, }); assert.match(projectName.validateProjectName("line\nbreak").error, /управляющие/); assert.match(projectName.validateProjectName("\ud800").error, /управляющие/); assert.match(projectName.validateProjectName("x".repeat(97)).error, /не длиннее 96/); assert.match(projectName.validateProjectName(" \t ").error, /Введите название/); }); test("automatic spatial source replaces the old scene only after a successful start", async () => { const failedEvents = []; assert.equal(await automaticSourceStart.runAutomaticSpatialSourceStart( async () => { failedEvents.push("start"); return false; }, () => failedEvents.push("activate"), () => failedEvents.push("open"), ), false); assert.deepEqual(failedEvents, ["start"]); const successfulEvents = []; assert.equal(await automaticSourceStart.runAutomaticSpatialSourceStart( async () => { successfulEvents.push("start"); return true; }, () => successfulEvents.push("activate"), () => successfulEvents.push("open"), ), true); assert.deepEqual(successfulEvents, ["start", "activate", "open"]); }); test("vendor commands fail closed unless the profile and acquisition both enable them", () => { const capability = { compatibility: { vendor_writes_enabled: true, permitted_mode: "active-control", }, }; assert.equal(lifecycle.isVendorWriteCapable(capability), true); assert.equal(lifecycle.isVendorWriteCapable({ compatibility: { vendor_writes_enabled: true, permitted_mode: "read-only" }, }), false); assert.equal(lifecycle.isSoftwareCommandedAcquisition({ ...capability, acquisition: { control_mode: "operator-manual" }, }), false); assert.equal(lifecycle.isSoftwareCommandedAcquisition({ ...capability, acquisition: { control_mode: "plugin-commanded" }, }), true); }); test("device modeling telemetry maps only finite non-negative values", () => { assert.deepEqual(presentation.deviceTelemetry({ device_elapsed_seconds: 12.5, device_route_distance_meters: 8.25, device_speed_meters_per_second: 0.75, }), { elapsedSeconds: 12.5, routeDistanceMeters: 8.25, speedMetersPerSecond: 0.75, }); assert.deepEqual(presentation.deviceTelemetry({ device_elapsed_seconds: -1, device_route_distance_meters: Number.NaN, device_speed_meters_per_second: Number.POSITIVE_INFINITY, }), { elapsedSeconds: null, routeDistanceMeters: null, speedMetersPerSecond: null, }); }); test("spatial K1 action failures have an explicit retry-safe presentation", () => { assert.equal(presentation.spatialActionFailure(null), null); assert.equal(presentation.spatialActionFailure(" "), null); assert.deepEqual(presentation.spatialActionFailure(" stop failed "), { title: "Действие K1 не выполнено", detail: "stop failed", }); assert.equal(lifecycle.shouldRenderSpatialControls({ source_mode: "live", acquisition: { state: "failed", cleanup_pending: true }, }), true); assert.equal(lifecycle.shouldRenderSpatialControls({ source_mode: "idle", acquisition: { state: "failed", cleanup_pending: false }, }), false); }); test("replay ignores a stale failed live acquisition", () => { const replay = { source_mode: "replay", phase: "replay", rerun_grpc_url: "rerun+http://127.0.0.1:9876/proxy", acquisition: { acquisition_id: "old-live-acquisition", state: "failed", }, }; assert.equal(lifecycle.normalizeRuntimePhase(replay), "replaying"); assert.equal(lifecycle.effectiveAcquisition(replay), null); assert.equal( lifecycle.spatialSourceId(replay, replay.rerun_grpc_url), `replay:${replay.rerun_grpc_url}`, ); }); test("K1 configuration anchors expose future modes without enabling them", () => { assert.equal(configuration.SUPPORTED_CONNECTION_MODE, "bridge"); assert.equal(configuration.SUPPORTED_MOUNT_TYPE, "handheld"); assert.equal(configuration.SUPPORTED_GNSS_MODE, "none"); assert.deepEqual( configuration.connectionModeOptions.map(({ value, disabled = false }) => ({ value, disabled })), [ { value: "bridge", disabled: false }, { value: "quick-connect", disabled: true }, { value: "direct-connect", disabled: true }, ], ); assert.deepEqual( configuration.mountTypeOptions.map(({ value, disabled = false }) => ({ value, disabled })), [ { value: "handheld", disabled: false }, { value: "vehicle-mounted", disabled: true }, { value: "uav", disabled: true }, { value: "backpack", disabled: true }, ], ); assert.deepEqual( configuration.gnssModeOptions.map(({ value, disabled = false }) => ({ value, disabled })), [ { value: "none", disabled: false }, { value: "rtk", disabled: true }, { value: "ppk", disabled: true }, ], ); }); test("a provisioned address is not presented as a verified device connection", () => { assert.equal(lifecycle.normalizeRuntimePhase({ phase: "connected", application_control_session: { state: "idle" }, }), "configuring"); assert.equal(lifecycle.normalizeRuntimePhase({ phase: "connected", application_control_session: { state: "connection-ready" }, }), "connected"); }); test("provisioning intent keeps one idempotency key and exposes unsafe outcomes", () => { let created = 0; const createUuid = () => { created += 1; return "11111111-1111-4111-8111-111111111111"; }; const first = lifecycle.provisioningIntentKey(null, createUuid); const repeated = lifecycle.provisioningIntentKey(first, createUuid); const failedOperation = { status: "failed", error: { safe_to_retry: false }, }; assert.equal(first, "network-provision:11111111-1111-4111-8111-111111111111"); assert.equal(repeated, first); assert.equal(created, 1); assert.equal(lifecycle.operationNeedsReconciliation(failedOperation), true); }); test("device mutations send explicit nested compatibility attestation", async () => { const originalFetch = globalThis.fetch; const calls = []; const syntheticCredential = "x".repeat(32); globalThis.fetch = async (path, init) => { calls.push({ path, init }); return new Response(JSON.stringify({ state: { source_mode: "idle" } }), { status: 200, headers: { "Content-Type": "application/json" }, }); }; const attestation = { firmware_version: "3.0.2", topology: "direct-lan", verification: "live-device-info", }; try { await xgridsK1Api.connect({ device_id: "ble-device", ssid: "lab-network", password: syntheticCredential, connection_mode: "bridge", compatibility_attestation: attestation, idempotency_key: "network-provision:test", }); await xgridsK1Api.prepareAcquisition({ project_name: "Mission 01", mount_type: "handheld", gnss_mode: "none", compatibility_attestation: attestation, }); } finally { globalThis.fetch = originalFetch; } assert.equal(calls.length, 2); const provisioning = JSON.parse(calls[0].init.body); const prepare = JSON.parse(calls[1].init.body); assert.deepEqual(provisioning.input.compatibility_attestation, attestation); assert.equal(provisioning.input.idempotency_key, "network-provision:test"); assert.deepEqual(prepare.input.compatibility_attestation, attestation); assert.equal(prepare.input.project_name, "Mission 01"); });