import test from "node:test"; import assert from "node:assert/strict"; import os from "node:os"; import path from "node:path"; import { mkdtemp, rm } from "node:fs/promises"; import { NextRequest } from "next/server"; let runtimeRoot = ""; let createEnrollmentRoute: (typeof import("../src/app/api/v1/devices/enrollments/route"))["POST"]; let deviceHeartbeatRoute: (typeof import("../src/app/api/device-heartbeat/route"))["POST"]; let getImportDraftRoute: (typeof import("../src/app/api/v1/devices/[deviceId]/import-draft/route"))["GET"]; let selectImportDraftRoute: (typeof import("../src/app/api/v1/devices/[deviceId]/import-draft/select/route"))["POST"]; let reviewImportDraftRoute: (typeof import("../src/app/api/v1/devices/[deviceId]/import-draft/review/route"))["POST"]; let applyImportDraftRoute: (typeof import("../src/app/api/v1/devices/[deviceId]/import-draft/apply/route"))["POST"]; let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"]; let readState: (typeof import("../src/lib/boss-data"))["readState"]; let AUTH_SESSION_COOKIE = ""; async function setup() { if (runtimeRoot) return; runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-device-import-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [enrollmentModule, heartbeatModule, importDraftModule, selectModule, reviewModule, applyModule, data, auth] = await Promise.all([ import("../src/app/api/v1/devices/enrollments/route.ts"), import("../src/app/api/device-heartbeat/route.ts"), import("../src/app/api/v1/devices/[deviceId]/import-draft/route.ts"), import("../src/app/api/v1/devices/[deviceId]/import-draft/select/route.ts"), import("../src/app/api/v1/devices/[deviceId]/import-draft/review/route.ts"), import("../src/app/api/v1/devices/[deviceId]/import-draft/apply/route.ts"), import("../src/lib/boss-data.ts"), import("../src/lib/boss-auth.ts"), ]); createEnrollmentRoute = enrollmentModule.POST; deviceHeartbeatRoute = heartbeatModule.POST; getImportDraftRoute = importDraftModule.GET; selectImportDraftRoute = selectModule.POST; reviewImportDraftRoute = reviewModule.POST; applyImportDraftRoute = applyModule.POST; createAuthSession = data.createAuthSession; readState = data.readState; AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE; } test.after(async () => { if (runtimeRoot) { await rm(runtimeRoot, { recursive: true, force: true }); } }); async function createAuthedRequest(url: string, method: "GET" | "POST", body?: unknown) { const session = await createAuthSession({ account: "17600003315", role: "highest_admin", displayName: "Boss 超级管理员", loginMethod: "password", }); return new NextRequest(url, { method, headers: { cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`, ...(body ? { "content-type": "application/json" } : {}), }, body: body ? JSON.stringify(body) : undefined, }); } test("device import draft flow scans candidates, selects imports, resolves suggestions, and creates real chat windows", async () => { await setup(); const enrollmentResponse = await createEnrollmentRoute( await createAuthedRequest("http://127.0.0.1:3000/api/v1/devices/enrollments", "POST", { name: "MacBook Pro", avatar: "P", account: "17600003315", endpoint: "mac://mbp.local", note: "待导入新设备", }), ); assert.equal(enrollmentResponse.status, 200); const enrollmentPayload = (await enrollmentResponse.json()) as { enrollment: { deviceId: string; pairingCode: string }; device: { id: string }; }; const heartbeatResponse = await deviceHeartbeatRoute( new NextRequest("http://127.0.0.1:3000/api/device-heartbeat", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ deviceId: enrollmentPayload.device.id, pairingCode: enrollmentPayload.enrollment.pairingCode, name: "MacBook Pro", avatar: "P", account: "17600003315", status: "online", quota5h: 72, quota7d: 88, projects: [], endpoint: "mac://mbp.local", projectCandidates: [ { folderName: "北区试产线", folderRef: "north-line", threadId: "thread-north-regression", threadDisplayName: "北区试产线回归", codexFolderRef: "north-line", codexThreadRef: "thread-north-regression", lastActiveAt: "2026-03-30T10:18:00+08:00", suggestedImport: true, }, { folderName: "北区试产线", folderRef: "north-line", threadId: "thread-north-audit", threadDisplayName: "北区试产线审计", codexFolderRef: "north-line", codexThreadRef: "thread-north-audit", lastActiveAt: "2026-03-30T10:20:00+08:00", suggestedImport: true, }, ], }), }), ); assert.equal(heartbeatResponse.status, 200); const draftResponse = await getImportDraftRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/devices/${enrollmentPayload.device.id}/import-draft`, "GET", ), { params: Promise.resolve({ deviceId: enrollmentPayload.device.id }) }, ); assert.equal(draftResponse.status, 200); const draftPayload = (await draftResponse.json()) as { draft: { draftId: string; candidates: Array<{ candidateId: string; threadDisplayName: string }> } | null; }; assert.ok(draftPayload.draft, "expected an import draft after heartbeat candidates"); assert.equal(draftPayload.draft.candidates.length, 2); const selectedCandidateIds = draftPayload.draft.candidates .filter((candidate) => candidate.threadDisplayName === "北区试产线回归") .map((candidate) => candidate.candidateId); assert.equal(selectedCandidateIds.length, 1); const selectResponse = await selectImportDraftRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/devices/${enrollmentPayload.device.id}/import-draft/select`, "POST", { selectedCandidateIds }, ), { params: Promise.resolve({ deviceId: enrollmentPayload.device.id }) }, ); assert.equal(selectResponse.status, 200); const reviewResponse = await reviewImportDraftRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/devices/${enrollmentPayload.device.id}/import-draft/review`, "POST", {}, ), { params: Promise.resolve({ deviceId: enrollmentPayload.device.id }) }, ); assert.equal(reviewResponse.status, 200); const reviewPayload = (await reviewResponse.json()) as { resolution: { summary: string; items: Array<{ action: string; threadDisplayName: string }> }; }; assert.match(reviewPayload.resolution.summary, /MacBook Pro 导入建议/); assert.deepEqual( reviewPayload.resolution.items.map((item) => item.action), ["create_thread_conversation"], ); const applyResponse = await applyImportDraftRoute( await createAuthedRequest( `http://127.0.0.1:3000/api/v1/devices/${enrollmentPayload.device.id}/import-draft/apply`, "POST", {}, ), { params: Promise.resolve({ deviceId: enrollmentPayload.device.id }) }, ); assert.equal(applyResponse.status, 200); const nextState = await readState(); const importedProject = nextState.projects.find( (project) => project.threadMeta.codexThreadRef === "thread-north-regression", ); assert.ok(importedProject, "expected selected candidate to become a real chat window"); assert.equal(importedProject?.threadMeta.threadDisplayName, "北区试产线回归"); assert.equal(importedProject?.threadMeta.folderName, "北区试产线"); const device = nextState.devices.find((item) => item.id === enrollmentPayload.device.id); assert.deepEqual(device?.projects, ["北区试产线"]); const appliedDraft = nextState.deviceImportDrafts.find( (draft) => draft.deviceId === enrollmentPayload.device.id, ); const appliedResolution = nextState.deviceImportResolutions.find( (resolution) => resolution.deviceId === enrollmentPayload.device.id, ); assert.equal(appliedDraft?.status, "applied"); assert.equal(appliedResolution?.status, "applied"); });