Files
penguin-harness/packages/server/test/session-loader.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

103 lines
4.0 KiB
TypeScript

/**
* Integration tests for createCoreSessionLoader (#3/#13): failures recovering a
* historical Session (Workspace deleted / Model removed from config / Trace
* missing session_meta) all collapse into HttpError(409, session_unrecoverable),
* preserving the original core message instead of bubbling up as a 500.
*/
import fs from "node:fs/promises";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { createAgent, saveProjectConfig, sessionMeta, userText } from "@prismshadow/penguin-core";
import type { SessionMetaPayload } from "@prismshadow/penguin-core";
import { createCoreSessionLoader } from "../src/runtime/session-manager.js";
import type { SessionRow } from "../src/db/repos/sessions.js";
import { HttpError } from "../src/http/errors.js";
import { makeTempRoot, writeTraceFile } from "./helpers.js";
const PROJECT = "project-loader";
const AGENT = "default_agent";
const SID = "session-2026-07-06-11-00-00-abcd0001";
function meta(overrides: Partial<SessionMetaPayload> = {}): SessionMetaPayload {
return {
session_id: SID,
model_id: "custom/m1",
provider: "custom",
model_context_window: 1000,
system_prompt: "sp",
tools: [],
thinking_level: "default",
agent_state: "/tmp/a",
workspace: path.join("/tmp", "does-not-exist-xyz"),
...overrides,
};
}
function row(workspace: string): SessionRow {
return {
sessionId: SID,
projectId: PROJECT,
agentId: AGENT,
modelId: "custom/m1",
provider: "custom",
workspace,
approvalMode: "always-ask",
title: null,
createdAt: new Date().toISOString(),
};
}
describe("session-loader", () => {
let root: string;
beforeEach(async () => {
root = await makeTempRoot();
// Initialize Agent State (createAgent creates the directory and system_config.yaml).
await createAgent({ root, projectId: PROJECT, agentId: AGENT });
// Configure Model m1 so recovery doesn't fail on a missing Model (unless the test deletes it on purpose).
await saveProjectConfig(root, PROJECT, {
default_model: { provider: "custom", model_id: "m1" },
models: [{ provider: "custom", model_id: "m1", context_window: 1000 }],
});
});
afterEach(async () => {
await fs.rm(root, { recursive: true, force: true });
});
it("有 Trace 但 Workspace 已删 → 409 session_unrecoverable,保留原文", async () => {
await writeTraceFile(root, PROJECT, AGENT, "2026-07-06", SID, 1, [
sessionMeta(meta()), // workspace points to a nonexistent directory
userText("hi"),
]);
const loader = createCoreSessionLoader(root);
const err = await loader.load(row("/tmp/does-not-exist-xyz")).catch((e) => e);
expect(err).toBeInstanceOf(HttpError);
expect((err as HttpError).status).toBe(409);
expect((err as HttpError).code).toBe("session_unrecoverable");
expect((err as HttpError).message).toContain("Workspace 已不存在");
});
it("有 Trace 但 Model 已从配置移除 → 409 session_unrecoverable", async () => {
const ws = path.join(root, "ws");
await fs.mkdir(ws, { recursive: true });
await writeTraceFile(root, PROJECT, AGENT, "2026-07-06", SID, 1, [
sessionMeta(meta({ model_id: "removed-model", workspace: ws })),
userText("hi"),
]);
const loader = createCoreSessionLoader(root);
const err = await loader.load(row(ws)).catch((e) => e);
expect(err).toBeInstanceOf(HttpError);
expect((err as HttpError).status).toBe(409);
expect((err as HttpError).code).toBe("session_unrecoverable");
expect((err as HttpError).message).toContain("Model 不在 Project 配置中");
});
it("无 Trace 且 Workspace 已删(自愈分支)→ 409 workspace_missing", async () => {
const loader = createCoreSessionLoader(root);
const err = await loader.load(row("/tmp/gone-workspace-abc")).catch((e) => e);
expect(err).toBeInstanceOf(HttpError);
expect((err as HttpError).status).toBe(409);
expect((err as HttpError).code).toBe("workspace_missing");
});
});