feat(k1): complete primary acquisition lifecycle
This commit is contained in:
@@ -10,6 +10,9 @@ let xgridsK1Manifest;
|
||||
let xgridsK1Actions;
|
||||
let xgridsK1Api;
|
||||
let lifecycle;
|
||||
let projectName;
|
||||
let automaticSourceStart;
|
||||
let presentation;
|
||||
|
||||
before(async () => {
|
||||
server = await createServer({
|
||||
@@ -29,6 +32,15 @@ before(async () => {
|
||||
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",
|
||||
);
|
||||
({ xgridsK1Api } = await server.ssrLoadModule(
|
||||
"@xgrids-k1/frontend/api.ts",
|
||||
));
|
||||
@@ -279,6 +291,100 @@ test("prepared acquisition resumes without another prepare and remains recoverab
|
||||
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",
|
||||
@@ -343,6 +449,7 @@ test("device mutations send explicit nested compatibility attestation", async ()
|
||||
idempotency_key: "network-provision:test",
|
||||
});
|
||||
await xgridsK1Api.prepareAcquisition({
|
||||
project_name: "Mission 01",
|
||||
compatibility_attestation: attestation,
|
||||
});
|
||||
} finally {
|
||||
@@ -355,4 +462,5 @@ test("device mutations send explicit nested compatibility attestation", async ()
|
||||
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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user