296 lines
8.6 KiB
TypeScript
296 lines
8.6 KiB
TypeScript
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");
|
|
});
|
|
|
|
test("device heartbeat without capability payload refreshes stale gui cli lastSeenAt", async () => {
|
|
await setup();
|
|
|
|
const staleCapabilityTime = "2026-03-25T11:52:00+08:00";
|
|
const staleState = await readState();
|
|
await writeState({
|
|
...staleState,
|
|
devices: staleState.devices.map((device) =>
|
|
device.id === "mac-studio"
|
|
? {
|
|
...device,
|
|
lastSeenAt: staleCapabilityTime,
|
|
capabilities: {
|
|
gui: {
|
|
...device.capabilities?.gui,
|
|
connected: true,
|
|
lastSeenAt: staleCapabilityTime,
|
|
lastActiveProjectId: "master-agent",
|
|
},
|
|
cli: {
|
|
...device.capabilities?.cli,
|
|
connected: true,
|
|
lastSeenAt: staleCapabilityTime,
|
|
lastActiveProjectId: "master-agent",
|
|
},
|
|
},
|
|
}
|
|
: device,
|
|
),
|
|
});
|
|
|
|
await upsertDeviceHeartbeat({
|
|
deviceId: "mac-studio",
|
|
name: "Mac Studio",
|
|
avatar: "M",
|
|
account: "17600003315",
|
|
status: "online",
|
|
quota5h: 72,
|
|
quota7d: 86,
|
|
preferredExecutionMode: "cli",
|
|
projects: ["硬件审计协作"],
|
|
endpoint: "mac://kris.local",
|
|
});
|
|
|
|
const state = await readState();
|
|
const device = state.devices.find((item) => item.id === "mac-studio");
|
|
|
|
assert.ok(device);
|
|
assert.notEqual(device.lastSeenAt, staleCapabilityTime);
|
|
assert.equal(device.capabilities?.gui.lastSeenAt, device.lastSeenAt);
|
|
assert.equal(device.capabilities?.cli.lastSeenAt, device.lastSeenAt);
|
|
});
|