Add gui/cli capability conflict guards
This commit is contained in:
242
tests/device-gui-cli-capabilities.test.ts
Normal file
242
tests/device-gui-cli-capabilities.test.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
|
||||
let runtimeRoot = "";
|
||||
let readState: (typeof import("../src/lib/boss-data"))["readState"];
|
||||
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
|
||||
let upsertDeviceHeartbeat: (typeof import("../src/lib/boss-data"))["upsertDeviceHeartbeat"];
|
||||
let updateDevice: (typeof import("../src/lib/boss-data"))["updateDevice"];
|
||||
|
||||
async function setup() {
|
||||
if (runtimeRoot) return;
|
||||
|
||||
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-device-capabilities-"));
|
||||
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
||||
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
||||
|
||||
const data = await import("../src/lib/boss-data.ts");
|
||||
readState = data.readState;
|
||||
writeState = data.writeState;
|
||||
upsertDeviceHeartbeat = data.upsertDeviceHeartbeat;
|
||||
updateDevice = data.updateDevice;
|
||||
}
|
||||
|
||||
test.after(async () => {
|
||||
if (runtimeRoot) {
|
||||
await rm(runtimeRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("device stores gui and cli capabilities without splitting the physical device", async () => {
|
||||
await setup();
|
||||
|
||||
const state = await readState();
|
||||
const device = state.devices.find((item) => item.id === "mac-studio") as
|
||||
| ({
|
||||
capabilities?: {
|
||||
gui?: { connected?: boolean };
|
||||
cli?: { connected?: boolean };
|
||||
};
|
||||
preferredExecutionMode?: string;
|
||||
} & (typeof state.devices)[number])
|
||||
| undefined;
|
||||
|
||||
assert.ok(device);
|
||||
assert.equal(device.capabilities?.gui?.connected, true);
|
||||
assert.equal(device.capabilities?.cli?.connected, true);
|
||||
assert.equal(device.preferredExecutionMode, "cli");
|
||||
});
|
||||
|
||||
test("conflict policy is scoped to the active folder instead of the whole device", async () => {
|
||||
await setup();
|
||||
|
||||
const state = (await readState()) as typeof readState extends () => Promise<infer T>
|
||||
? T & {
|
||||
projectExecutionPolicies?: Array<{
|
||||
deviceId: string;
|
||||
folderKey?: string;
|
||||
projectId: string;
|
||||
allowPolicy: string;
|
||||
conflictState: string;
|
||||
updatedAt: string;
|
||||
}>;
|
||||
}
|
||||
: never;
|
||||
|
||||
state.projectExecutionPolicies = [
|
||||
{
|
||||
deviceId: "mac-studio",
|
||||
folderKey: "mac-studio:boss",
|
||||
projectId: "thread-ui",
|
||||
allowPolicy: "allow_always",
|
||||
conflictState: "warning",
|
||||
updatedAt: "2026-04-06T10:00:00.000Z",
|
||||
},
|
||||
];
|
||||
await writeState(state);
|
||||
|
||||
const nextState = (await readState()) as typeof state;
|
||||
const bossPolicy = nextState.projectExecutionPolicies?.find(
|
||||
(item) => item.folderKey === "mac-studio:boss",
|
||||
);
|
||||
const otherPolicy = nextState.projectExecutionPolicies?.find(
|
||||
(item) => item.folderKey === "mac-studio:talking",
|
||||
);
|
||||
|
||||
assert.equal(bossPolicy?.allowPolicy, "allow_always");
|
||||
assert.equal(otherPolicy, undefined);
|
||||
});
|
||||
|
||||
test("legacy device state without capabilities is normalized with seeded defaults", async () => {
|
||||
await setup();
|
||||
|
||||
const state = await readState();
|
||||
const legacyState = {
|
||||
...state,
|
||||
devices: state.devices.map((device) =>
|
||||
device.id === "mac-studio"
|
||||
? {
|
||||
...device,
|
||||
capabilities: undefined,
|
||||
preferredExecutionMode: undefined,
|
||||
}
|
||||
: device,
|
||||
),
|
||||
};
|
||||
|
||||
await writeFile(process.env.BOSS_STATE_FILE!, JSON.stringify(legacyState, null, 2), "utf8");
|
||||
|
||||
const normalized = await readState();
|
||||
const device = normalized.devices.find((item) => item.id === "mac-studio");
|
||||
|
||||
assert.ok(device);
|
||||
assert.equal(device.capabilities?.gui.connected, true);
|
||||
assert.equal(device.capabilities?.cli.connected, true);
|
||||
assert.equal(device.preferredExecutionMode, "cli");
|
||||
});
|
||||
|
||||
test("legacy device normalization matches seeded defaults by device id instead of array position", async () => {
|
||||
await setup();
|
||||
|
||||
const state = await readState();
|
||||
const reorderedLegacyDevices = [
|
||||
{
|
||||
id: "win-gpu-01",
|
||||
source: "production",
|
||||
capabilities: undefined,
|
||||
preferredExecutionMode: undefined,
|
||||
},
|
||||
{
|
||||
id: "mac-studio",
|
||||
source: "production",
|
||||
capabilities: undefined,
|
||||
preferredExecutionMode: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
await writeFile(
|
||||
process.env.BOSS_STATE_FILE!,
|
||||
JSON.stringify(
|
||||
{
|
||||
...state,
|
||||
devices: reorderedLegacyDevices,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const normalized = await readState();
|
||||
const windowsDevice = normalized.devices.find((item) => item.id === "win-gpu-01");
|
||||
const macDevice = normalized.devices.find((item) => item.id === "mac-studio");
|
||||
|
||||
assert.ok(windowsDevice);
|
||||
assert.ok(macDevice);
|
||||
assert.equal(windowsDevice.capabilities?.gui.connected, true);
|
||||
assert.equal(windowsDevice.capabilities?.cli.connected, false);
|
||||
assert.equal(windowsDevice.preferredExecutionMode, "gui");
|
||||
assert.equal(macDevice.capabilities?.gui.connected, true);
|
||||
assert.equal(macDevice.capabilities?.cli.connected, true);
|
||||
assert.equal(macDevice.preferredExecutionMode, "cli");
|
||||
});
|
||||
|
||||
test("device heartbeat persists gui cli capability state on the same physical device", async () => {
|
||||
await setup();
|
||||
|
||||
await upsertDeviceHeartbeat({
|
||||
deviceId: "mac-studio",
|
||||
name: "Mac Studio",
|
||||
avatar: "M",
|
||||
account: "17600003315",
|
||||
status: "online",
|
||||
quota5h: 72,
|
||||
quota7d: 86,
|
||||
preferredExecutionMode: "gui",
|
||||
capabilities: {
|
||||
gui: {
|
||||
connected: true,
|
||||
lastSeenAt: "2026-04-06T09:30:00.000Z",
|
||||
lastActiveProjectId: "audit-collab",
|
||||
},
|
||||
cli: {
|
||||
connected: true,
|
||||
lastSeenAt: "2026-04-06T09:31:00.000Z",
|
||||
lastActiveProjectId: "master-agent",
|
||||
},
|
||||
},
|
||||
projects: ["硬件审计协作"],
|
||||
endpoint: "mac://kris.local",
|
||||
});
|
||||
|
||||
const state = await readState();
|
||||
const device = state.devices.find((item) => item.id === "mac-studio");
|
||||
|
||||
assert.ok(device);
|
||||
assert.equal(device.preferredExecutionMode, "cli");
|
||||
assert.equal(device.capabilities?.gui.connected, true);
|
||||
assert.equal(device.capabilities?.gui.lastActiveProjectId, "audit-collab");
|
||||
assert.equal(device.capabilities?.cli.connected, true);
|
||||
assert.equal(device.capabilities?.cli.lastActiveProjectId, "master-agent");
|
||||
});
|
||||
|
||||
test("device heartbeat does not overwrite the preferred execution mode chosen in Boss for an existing device", async () => {
|
||||
await setup();
|
||||
|
||||
await updateDevice("mac-studio", {
|
||||
preferredExecutionMode: "gui",
|
||||
});
|
||||
|
||||
await upsertDeviceHeartbeat({
|
||||
deviceId: "mac-studio",
|
||||
name: "Mac Studio",
|
||||
avatar: "M",
|
||||
account: "17600003315",
|
||||
status: "online",
|
||||
quota5h: 72,
|
||||
quota7d: 86,
|
||||
preferredExecutionMode: "cli",
|
||||
capabilities: {
|
||||
gui: {
|
||||
connected: true,
|
||||
lastSeenAt: "2026-04-06T09:35:00.000Z",
|
||||
lastActiveProjectId: "master-agent",
|
||||
},
|
||||
cli: {
|
||||
connected: true,
|
||||
lastSeenAt: "2026-04-06T09:36:00.000Z",
|
||||
lastActiveProjectId: "audit-collab",
|
||||
},
|
||||
},
|
||||
projects: ["硬件审计协作"],
|
||||
endpoint: "mac://kris.local",
|
||||
});
|
||||
|
||||
const state = await readState();
|
||||
const device = state.devices.find((item) => item.id === "mac-studio");
|
||||
assert.ok(device);
|
||||
assert.equal(device.preferredExecutionMode, "gui");
|
||||
});
|
||||
Reference in New Issue
Block a user