Browse Source

feat: add POST create endpoint and E2E tests for agent-documents

- POST /api/agent-documents for manual document creation
- E2E tests: list, detail, type filter, search, edit, delete, auth guard, nav

Co-authored-by: CodeFree <codefree@chinatelcom.cn>
feat/agent-independence
npmrun 2 months ago
parent
commit
0c8d51a770
  1. 191
      e2e/specs/agent-documents.spec.ts
  2. 31
      server/api/agent-documents/index.post.ts

191
e2e/specs/agent-documents.spec.ts

@ -0,0 +1,191 @@
import { test, expect } from "@playwright/test";
import { E2E_ADMIN, FIXED_CAPTCHA } from "../fixtures/test-users";
async function loginViaUI(page: import("@playwright/test").Page) {
await page.goto("/auth/login");
await page.fill("#login-username", E2E_ADMIN.username);
await page.fill("#login-password", E2E_ADMIN.password);
await page.fill("#login-captcha", FIXED_CAPTCHA);
await page.click('button[type="submit"]');
await page.waitForURL("/", { timeout: 15000 });
}
async function createDocViaAPI(
request: import("@playwright/test").APIRequestContext,
body: Record<string, unknown>,
) {
const res = await request.post("/api/agent-documents", { data: body });
return res;
}
test.describe("Agent 文档", () => {
test("列表页可访问(空状态)", async ({ page, request }) => {
await loginViaUI(page);
await page.goto("/agent-documents");
await expect(page.locator(".agent-documents-page")).toBeVisible({ timeout: 10000 });
});
test("通过 API 创建文档后列表显示", async ({ page, request }) => {
await loginViaUI(page);
const title = `E2E测试文档_${Date.now()}`;
const res = await createDocViaAPI(request, {
title,
content: "## 测试内容\n这是一个E2E测试创建的文档。",
type: "document",
tags: ["e2e", "test"],
});
expect(res.ok()).toBeTruthy();
const json = await res.json();
expect(json.code).toBe(0);
const docId = json.data.id;
await page.goto("/agent-documents");
await expect(page.locator(".document-card").first()).toBeVisible({ timeout: 10000 });
await expect(page.locator(".document-card").first()).toContainText(title);
await request.delete(`/api/agent-documents/${docId}`);
});
test("详情页显示文档内容", async ({ page, request }) => {
await loginViaUI(page);
const title = `E2E详情测试_${Date.now()}`;
const content = "## 详情页测试\nMarkdown渲染验证。";
const res = await createDocViaAPI(request, {
title,
content,
type: "document",
tags: ["detail"],
});
const json = await res.json();
const docId = json.data.id;
await page.goto(`/agent-documents/${docId}`);
await expect(page.locator(".detail-title")).toContainText(title, { timeout: 10000 });
await expect(page.locator(".detail-content")).toBeVisible();
await request.delete(`/api/agent-documents/${docId}`);
});
test("类型筛选工作正常", async ({ page, request }) => {
await loginViaUI(page);
const docRes = await createDocViaAPI(request, {
title: `E2E文档类型_${Date.now()}`,
content: "文档类型测试",
type: "document",
});
const memRes = await createDocViaAPI(request, {
title: `E2E记忆类型_${Date.now()}`,
content: "记忆类型测试",
type: "memory",
});
const docId = (await docRes.json()).data.id;
const memId = (await memRes.json()).data.id;
await page.goto("/agent-documents");
await page.click(".filter-tab", { text: "文档" });
await page.waitForTimeout(500);
const docCards = page.locator(".document-card");
const count1 = await docCards.count();
expect(count1).toBeGreaterThan(0);
for (let i = 0; i < count1; i++) {
await expect(docCards.nth(i).locator(".type-badge")).toHaveClass(/document/);
}
await page.click(".filter-tab", { text: "记忆" });
await page.waitForTimeout(500);
const memCards = page.locator(".document-card");
const count2 = await memCards.count();
expect(count2).toBeGreaterThan(0);
for (let i = 0; i < count2; i++) {
await expect(memCards.nth(i).locator(".type-badge")).toHaveClass(/memory/);
}
await request.delete(`/api/agent-documents/${docId}`);
await request.delete(`/api/agent-documents/${memId}`);
});
test("关键词搜索工作正常", async ({ page, request }) => {
await loginViaUI(page);
const unique = `E2E搜索关键词_${Date.now()}`;
const res = await createDocViaAPI(request, {
title: unique,
content: "搜索测试内容",
type: "document",
});
const docId = (await res.json()).data.id;
await page.goto("/agent-documents");
await page.fill(".search-input", unique);
await page.waitForTimeout(600);
const cards = page.locator(".document-card");
await expect(cards.first()).toBeVisible({ timeout: 10000 });
const count = await cards.count();
expect(count).toBeGreaterThanOrEqual(1);
await expect(cards.first()).toContainText(unique);
await request.delete(`/api/agent-documents/${docId}`);
});
test("详情页编辑文档", async ({ page, request }) => {
await loginViaUI(page);
const res = await createDocViaAPI(request, {
title: `E2E编辑前_${Date.now()}`,
content: "编辑前内容",
type: "document",
});
const docId = (await res.json()).data.id;
await page.goto(`/agent-documents/${docId}`);
await page.click("button.action-btn", { text: "编辑" });
await expect(page.locator(".form-dialog")).toBeVisible({ timeout: 5000 });
await page.fill(".form-input", `E2E编辑后_${Date.now()}`);
await page.click("button.btn-save");
await expect(page.locator(".Toastify__toast").first()).toBeVisible({ timeout: 10000 });
await page.waitForTimeout(500);
await request.delete(`/api/agent-documents/${docId}`);
});
test("详情页删除文档", async ({ page, request }) => {
await loginViaUI(page);
const res = await createDocViaAPI(request, {
title: `E2E删除测试_${Date.now()}`,
content: "待删除",
type: "document",
});
const docId = (await res.json()).data.id;
await page.goto(`/agent-documents/${docId}`);
await page.click("button.action-btn.danger");
await expect(page.locator(".confirm-dialog")).toBeVisible({ timeout: 5000 });
await page.click(".confirm-dialog .btn-save.danger");
await expect(page).toHaveURL(/\/agent-documents$/, { timeout: 10000 });
});
test("未登录访问列表页重定向到登录", async ({ page }) => {
await page.goto("/agent-documents");
await expect(page).toHaveURL(/\/auth\/login/);
});
test("未登录 API 返回 401", async ({ request }) => {
const res = await request.get("/api/agent-documents");
expect(res.status()).toBe(401);
});
test("导航栏包含文档入口", async ({ page }) => {
await loginViaUI(page);
await page.goto("/");
const docLink = page.locator(".nav-links a", { hasText: "文档" });
await expect(docLink).toBeVisible({ timeout: 10000 });
});
});

31
server/api/agent-documents/index.post.ts

@ -0,0 +1,31 @@
import { createDocument, deserializeTags, type AgentDocumentType } from "../../service/agent-document";
import { requireUser } from "#server/utils/context";
import { validate } from "#server/utils/validation";
import { z } from "zod";
const createSchema = z.object({
title: z.string().min(1).max(200),
content: z.string().min(1).max(50000),
type: z.enum(["memory", "document"]).optional(),
tags: z.array(z.string()).optional(),
sessionId: z.string().optional(),
});
export default defineWrappedResponseHandler(async (event) => {
const user = await requireUser(event);
const body = await validate(createSchema, await readBody(event));
const created = await createDocument({
userId: user.id,
sessionId: body.sessionId ?? null,
type: body.type as AgentDocumentType | undefined,
title: body.title,
content: body.content,
tags: body.tags ?? null,
});
return R.success({
...created,
tags: deserializeTags(created.tags),
});
});
Loading…
Cancel
Save