feat: ship enterprise control and desktop governance
This commit is contained in:
154
tests/project-message-delete.test.ts
Normal file
154
tests/project-message-delete.test.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
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 readState: (typeof import("../src/lib/boss-data"))["readState"];
|
||||
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
|
||||
let deleteProjectMessage: (typeof import("../src/lib/boss-data"))["deleteProjectMessage"];
|
||||
let createAuthSession: (typeof import("../src/lib/boss-data"))["createAuthSession"];
|
||||
let deleteMessageRoute: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["DELETE"];
|
||||
let subscribeBossEvents: (typeof import("../src/lib/boss-events"))["subscribeBossEvents"];
|
||||
let AUTH_SESSION_COOKIE = "";
|
||||
|
||||
async function setup() {
|
||||
if (runtimeRoot) return;
|
||||
|
||||
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-project-message-delete-"));
|
||||
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
||||
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
||||
|
||||
const [data, route, auth, events] = await Promise.all([
|
||||
import("../src/lib/boss-data.ts"),
|
||||
import("../src/app/api/v1/projects/[projectId]/messages/route.ts"),
|
||||
import("../src/lib/boss-auth.ts"),
|
||||
import("../src/lib/boss-events.ts"),
|
||||
]);
|
||||
readState = data.readState;
|
||||
writeState = data.writeState;
|
||||
deleteProjectMessage = data.deleteProjectMessage;
|
||||
createAuthSession = data.createAuthSession;
|
||||
deleteMessageRoute = route.DELETE;
|
||||
subscribeBossEvents = events.subscribeBossEvents;
|
||||
AUTH_SESSION_COOKIE = auth.AUTH_SESSION_COOKIE;
|
||||
}
|
||||
|
||||
async function seedProject() {
|
||||
const state = await readState();
|
||||
const baseProject = state.projects.find((project) => project.id !== "master-agent") ?? state.projects[0];
|
||||
const project = structuredClone(baseProject);
|
||||
project.id = "message-delete-project";
|
||||
project.name = "消息删除测试";
|
||||
project.preview = "第二条回复";
|
||||
project.lastMessageAt = "2026-04-26T10:05:00.000Z";
|
||||
project.unreadCount = 2;
|
||||
project.messages = [
|
||||
{
|
||||
id: "msg-1",
|
||||
sender: "user",
|
||||
senderLabel: "你",
|
||||
body: "第一条请求",
|
||||
sentAt: "2026-04-26T10:00:00.000Z",
|
||||
kind: "text",
|
||||
},
|
||||
{
|
||||
id: "msg-process",
|
||||
sender: "device",
|
||||
senderLabel: "线程",
|
||||
body: "我先检查代码。",
|
||||
sentAt: "2026-04-26T10:03:00.000Z",
|
||||
kind: "thread_process",
|
||||
},
|
||||
{
|
||||
id: "msg-2",
|
||||
sender: "master",
|
||||
senderLabel: "主 Agent",
|
||||
body: "第二条回复",
|
||||
sentAt: "2026-04-26T10:05:00.000Z",
|
||||
kind: "text",
|
||||
},
|
||||
];
|
||||
state.projects = state.projects.filter((item) => item.id !== project.id);
|
||||
state.projects.unshift(project);
|
||||
await writeState(state);
|
||||
}
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await setup();
|
||||
await seedProject();
|
||||
});
|
||||
|
||||
test.after(async () => {
|
||||
if (runtimeRoot) {
|
||||
await rm(runtimeRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("deleteProjectMessage removes a message and refreshes project preview events", async () => {
|
||||
const events: Array<{ event: string; payload: { projectId?: string } }> = [];
|
||||
const unsubscribe = subscribeBossEvents((event, payload) => {
|
||||
events.push({ event, payload });
|
||||
});
|
||||
|
||||
const result = await deleteProjectMessage({
|
||||
projectId: "message-delete-project",
|
||||
messageId: "msg-2",
|
||||
});
|
||||
unsubscribe();
|
||||
|
||||
assert.equal(result.deletedMessage.id, "msg-2");
|
||||
assert.equal(result.projectId, "message-delete-project");
|
||||
|
||||
const project = (await readState()).projects.find((item) => item.id === "message-delete-project");
|
||||
assert.ok(project);
|
||||
assert.deepEqual(project!.messages.map((message) => message.id), ["msg-1", "msg-process"]);
|
||||
assert.equal(project!.preview, "第一条请求");
|
||||
assert.equal(project!.lastMessageAt, "2026-04-26T10:03:00.000Z");
|
||||
assert.ok(
|
||||
events.some(
|
||||
(item) =>
|
||||
item.event === "project.messages.updated" &&
|
||||
item.payload.projectId === "message-delete-project",
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
events.some(
|
||||
(item) =>
|
||||
item.event === "conversation.updated" &&
|
||||
item.payload.projectId === "message-delete-project",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("DELETE /api/v1/projects/[projectId]/messages deletes the requested message", async () => {
|
||||
const session = await createAuthSession({
|
||||
account: "krisolo",
|
||||
role: "highest_admin",
|
||||
displayName: "Boss 超级管理员",
|
||||
loginMethod: "password",
|
||||
});
|
||||
const request = new NextRequest(
|
||||
"http://127.0.0.1:3000/api/v1/projects/message-delete-project/messages?messageId=msg-2",
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
cookie: `${AUTH_SESSION_COOKIE}=${session.sessionToken}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const response = await deleteMessageRoute(request, {
|
||||
params: Promise.resolve({ projectId: "message-delete-project" }),
|
||||
});
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean;
|
||||
deletedMessage?: { id: string };
|
||||
};
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(payload.ok, true);
|
||||
assert.equal(payload.deletedMessage?.id, "msg-2");
|
||||
});
|
||||
Reference in New Issue
Block a user