45bfae6e94
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
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
import fs from "node:fs/promises";
|
||
import os from "node:os";
|
||
import path from "node:path";
|
||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||
import { assertValidId, isValidId, loadOrInitAgentState } from "../src/state/index.js";
|
||
|
||
describe("isValidId / assertValidId", () => {
|
||
const valid = ["default_project", "default_agent", "my-agent", "agent-1", "ABC_123"];
|
||
// Only alphanumerics, `_`, and `-` are allowed; dots, spaces/tabs, path separators, and any
|
||
// other character are all rejected.
|
||
const invalid = [
|
||
"",
|
||
" ",
|
||
"a b",
|
||
"a\tb",
|
||
" lead",
|
||
"trail ",
|
||
"a/b",
|
||
"a\\b",
|
||
".",
|
||
"..",
|
||
"...",
|
||
"a.b",
|
||
"v1.0",
|
||
".hidden",
|
||
"中文ok",
|
||
"emoji😀",
|
||
];
|
||
|
||
it.each(valid)("接受合法单段目录名:%j", (id) => {
|
||
expect(isValidId(id)).toBe(true);
|
||
expect(() => assertValidId("project_id", id)).not.toThrow();
|
||
expect(() => assertValidId("agent_id", id)).not.toThrow();
|
||
});
|
||
|
||
it.each(invalid)("拒绝非法 id:%j", (id) => {
|
||
expect(isValidId(id)).toBe(false);
|
||
expect(() => assertValidId("agent_id", id)).toThrow();
|
||
});
|
||
|
||
it("错误信息点明 kind 与非法 id", () => {
|
||
expect(() => assertValidId("agent_id", "a/b")).toThrow(/agent_id/);
|
||
expect(() => assertValidId("agent_id", "a/b")).toThrow(/a\/b/);
|
||
expect(() => assertValidId("project_id", "..")).toThrow(/project_id/);
|
||
});
|
||
});
|
||
|
||
describe("loadOrInitAgentState id 校验", () => {
|
||
let tmpRoot: string;
|
||
let prevHome: string | undefined;
|
||
|
||
beforeEach(async () => {
|
||
prevHome = process.env.PENGUIN_HOME;
|
||
tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "penguin-validate-id-"));
|
||
process.env.PENGUIN_HOME = tmpRoot;
|
||
});
|
||
|
||
afterEach(async () => {
|
||
if (prevHome === undefined) {
|
||
delete process.env.PENGUIN_HOME;
|
||
} else {
|
||
process.env.PENGUIN_HOME = prevHome;
|
||
}
|
||
await fs.rm(tmpRoot, { recursive: true, force: true });
|
||
});
|
||
|
||
it("agentId 含路径分隔符时抛错", async () => {
|
||
await expect(loadOrInitAgentState({ agentId: "a/b" })).rejects.toThrow(/agent_id/);
|
||
});
|
||
|
||
it("projectId 为 .. 时抛错", async () => {
|
||
await expect(loadOrInitAgentState({ projectId: ".." })).rejects.toThrow(/project_id/);
|
||
});
|
||
|
||
it("agentId 为 Windows 结尾空格变体 '.. ' 时抛错(防路径穿越绕过)", async () => {
|
||
await expect(loadOrInitAgentState({ agentId: ".. " })).rejects.toThrow(/agent_id/);
|
||
});
|
||
|
||
it("默认 id 合法,可正常初始化", async () => {
|
||
const state = await loadOrInitAgentState();
|
||
expect(state.projectId).toBe("default_project");
|
||
expect(state.agentId).toBe("default_agent");
|
||
});
|
||
});
|