Files
penguin-harness/packages/server/test/agent-trace-detail.test.ts
T
Yaowei Zheng 45bfae6e94 Initialize repository with harness code and assets
Initial import of all source code, config, and README assets: the
packages workspace (cli, core, server, web, docs, landing, skills),
build scripts, tooling config, and CI workflows.

Includes the data-layout revision made on this branch: the local data
root defaults to ~/.penguin/data (PENGUIN_HOME still overrides; the
installer keeps its binaries in ~/.penguin), and every Agent lives
under <project>/agents/<agent>/ — path helpers, the three
agent-enumeration scans, the system prompt, built-in Skills, tests
and docs all follow the new layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ihk8iQuo3kv2aPjAYEPuR
2026-07-19 14:06:53 +08:00

105 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Agent-level Trace detail endpoint integration tests (FD-3):
* The Trace page directory tree comes from an Agent-level scan (including unmanaged
* subagent child Sessions / Sessions created by the CLI), but the Session-level detail
* endpoint looks up the sessions table, so unmanaged ones return 404 directly. The new
* Agent-level detail endpoint locates the Trace file directly by
* (projectId, agentId, sessionId), with access controlled via requireProjectAccess.
*/
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { requestBegin, requestEnd, sessionMeta, userText } from "@prismshadow/penguin-core";
import type { SessionMetaPayload } from "@prismshadow/penguin-core";
import type {
ProjectCreateResponse,
TraceAnalysisResponse,
TraceEventsResponse,
} from "../src/api/types.js";
import { apiClient, createTestApp, provisionUser, writeTraceFile } from "./helpers.js";
import type { TestApp } from "./helpers.js";
/** An unmanaged child Session (only written to Trace, not inserted into the sessions table). */
const UNMANAGED = "session-2026-07-06-09-00-00-cafe0001";
function metaPayload(): SessionMetaPayload {
return {
session_id: UNMANAGED,
model_id: "sub-model",
provider: "custom",
model_context_window: 1000,
system_prompt: "",
tools: [],
thinking_level: "default",
agent_state: "/tmp/a",
workspace: "/tmp/w",
};
}
describe("agent-trace-detail", () => {
let t: TestApp;
let owner: ReturnType<typeof apiClient>;
let outsider: ReturnType<typeof apiClient>;
let projectId: string;
const base = () => `/api/projects/${projectId}/agents/default_agent/traces`;
beforeEach(async () => {
t = await createTestApp();
const a = await provisionUser(t.app, "owner");
const b = await provisionUser(t.app, "outsider");
owner = apiClient(t.app, a.cookie);
outsider = apiClient(t.app, b.cookie);
const created = (await (
await owner.post("/api/projects", { projectId: "owner-trace", name: "项目" })
).json()) as ProjectCreateResponse;
projectId = created.project.projectId;
await writeTraceFile(t.root, projectId, "default_agent", "2026-07-06", UNMANAGED, 1, [
sessionMeta(metaPayload()),
userText("子会话输入"),
requestBegin(),
requestEnd("completed"),
]);
});
afterEach(async () => {
await t.cleanup();
});
it("未纳管 Session 经 Session 级端点 404,经 Agent 级明细端点可读", async () => {
// Session-level: no such row in the sessions table -> 404.
expect((await owner.get(`/api/sessions/${UNMANAGED}/traces/1`)).status).toBe(404);
// Agent-level detail: locates the Trace file directly -> 200.
const res = await owner.get(`${base()}/${UNMANAGED}/1`);
expect(res.status).toBe(200);
const body = (await res.json()) as TraceEventsResponse;
expect(body.total).toBe(4);
expect((body.events[1]!.payload as { text: string }).text).toBe("子会话输入");
});
it("Agent 级明细分页参数生效", async () => {
const res = await owner.get(`${base()}/${UNMANAGED}/1?offset=1&limit=2`);
expect(res.status).toBe(200);
const body = (await res.json()) as TraceEventsResponse;
expect(body.offset).toBe(1);
expect(body.limit).toBe(2);
expect(body.events).toHaveLength(2);
// Invalid index / limit -> 400.
expect((await owner.get(`${base()}/${UNMANAGED}/0`)).status).toBe(400);
expect((await owner.get(`${base()}/${UNMANAGED}/1?limit=5000`)).status).toBe(400);
});
it("Agent 级性能分析端点派生 Request 配对", async () => {
const res = await owner.get(`${base()}/${UNMANAGED}/1/analysis`);
expect(res.status).toBe(200);
const body = (await res.json()) as TraceAnalysisResponse;
expect(body.requests).toHaveLength(1);
expect(body.requests[0]!.status).toBe("completed");
});
it("不存在的 index → 404 trace_not_found", async () => {
expect((await owner.get(`${base()}/${UNMANAGED}/9`)).status).toBe(404);
});
it("无访问权的用户 → 404(requireProjectAccess)", async () => {
expect((await outsider.get(`${base()}/${UNMANAGED}/1`)).status).toBe(404);
expect((await outsider.get(`${base()}/${UNMANAGED}/1/analysis`)).status).toBe(404);
});
});