Files
Alfred/tests/web/routes/workbench.test.tsx
lanyuanxiaoyao db40d04dc5 refactor(db): 统一数据库 schema — 软删除、命名规范、约束标准化
- 全表新增 deleted_at 列,统一软删除替代硬删除+archived_at
- models.model_id 重命名为 external_id,消除语义混淆
- conversations.model_id 改为可空(模型为建议而非绑定)
- messages 新增 updated_at,移除 CASCADE 改为 DAO 层级联
- 移除 DB 层 UNIQUE 约束,改为应用层检查(配合软删除)
- 新增 helpers.ts(baseColumns + 构造层防御)、ESLint 规则、契约测试
- 迁移 0004 补全 CHECK 约束(providers.type/materials.status/messages.role)
- DAO 层全面重写:级联软删除、应用层唯一、provider 删除保护
- 路由/前端/测试全量适配 externalId 重命名及类型变更
2026-06-05 01:02:23 +08:00

153 lines
4.1 KiB
TypeScript

import { screen, waitFor } from "@testing-library/react";
import { describe, expect, test } from "bun:test";
import { createElement } from "react";
import { App } from "../../../src/web/app";
import { renderWithProviders } from "../test-utils";
const MOCK_PROJECT = {
createdAt: "2024-01-01T00:00:00.000Z",
description: "测试项目",
id: "test-project-id",
name: "测试项目",
status: "active" as const,
updatedAt: "2024-01-01T00:00:00.000Z",
};
function createMockHandler(overrides?: { status?: "active" | "archived" }) {
const project = { ...MOCK_PROJECT, ...overrides };
const handler = (input: RequestInfo | URL) => {
const url = input instanceof Request ? input.url : typeof input === "string" ? input : input.toString();
if (url.includes("/api/meta")) {
return new Response(
JSON.stringify({ ok: true, service: "test-app", timestamp: new Date().toISOString(), version: "0.1.0" }),
{ headers: { "Content-Type": "application/json" }, status: 200 },
);
}
if (url.includes(`/api/projects/${project.id}`)) {
return new Response(JSON.stringify({ project }), {
headers: { "Content-Type": "application/json" },
status: 200,
});
}
if (url.includes("/api/projects/") && url.includes("/conversations")) {
return new Response(JSON.stringify({ items: [], page: 1, pageSize: 100, total: 0 }), {
headers: { "Content-Type": "application/json" },
status: 200,
});
}
return new Response(JSON.stringify({ error: "Not Found" }), { status: 404 });
};
const mocked = handler as unknown as typeof fetch;
globalThis.fetch = mocked;
window.fetch = mocked;
}
describe("Workbench 路由", () => {
test("active 项目可进入 Workbench", async () => {
createMockHandler();
renderWithProviders(createElement(App), {
initialRoute: `/workbench/${MOCK_PROJECT.id}`,
});
await waitFor(
() => {
const body = document.body.textContent ?? "";
expect(body).toContain("工作台");
},
{ timeout: 10000 },
);
});
test("Workbench 显示返回管理台按钮", async () => {
createMockHandler();
renderWithProviders(createElement(App), {
initialRoute: `/workbench/${MOCK_PROJECT.id}`,
});
await waitFor(
() => {
expect(screen.getByText("返回管理台")).not.toBeNull();
},
{ timeout: 10000 },
);
});
test("不存在项目显示不可访问", async () => {
createMockHandler();
renderWithProviders(createElement(App), {
initialRoute: "/workbench/nonexistent-id",
});
await waitFor(
() => {
expect(screen.getByText("项目不存在或不可访问")).not.toBeNull();
},
{ timeout: 10000 },
);
});
test("archived 项目显示不可访问", async () => {
createMockHandler({ status: "archived" });
renderWithProviders(createElement(App), {
initialRoute: `/workbench/${MOCK_PROJECT.id}`,
});
await waitFor(
() => {
expect(screen.getByText("项目不存在或不可访问")).not.toBeNull();
},
{ timeout: 10000 },
);
});
test("Workbench 显示聊天室菜单", async () => {
createMockHandler();
renderWithProviders(createElement(App), {
initialRoute: `/workbench/${MOCK_PROJECT.id}`,
});
await waitFor(
() => {
expect(screen.getByText("聊天室")).not.toBeNull();
},
{ timeout: 10000 },
);
});
test("Workbench 收集箱路由可达", async () => {
createMockHandler();
renderWithProviders(createElement(App), {
initialRoute: `/workbench/${MOCK_PROJECT.id}/inbox`,
});
await waitFor(
() => {
expect(screen.getByText("新增素材")).not.toBeNull();
},
{ timeout: 10000 },
);
});
test("Workbench 显示收集箱菜单", async () => {
createMockHandler();
renderWithProviders(createElement(App), {
initialRoute: `/workbench/${MOCK_PROJECT.id}`,
});
await waitFor(
() => {
expect(screen.getByText("收集箱")).not.toBeNull();
},
{ timeout: 10000 },
);
});
});