From c2b5df6442ff2942106731aab00c33ede25f44f9 Mon Sep 17 00:00:00 2001 From: looooown2006 <102905927+looooown2006@users.noreply.github.com> Date: Sat, 9 May 2026 15:04:23 +0800 Subject: [PATCH 1/2] test: cover resolveSubagentPath candidates --- __tests__/lib/resolve-subagent-path.test.ts | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 __tests__/lib/resolve-subagent-path.test.ts diff --git a/__tests__/lib/resolve-subagent-path.test.ts b/__tests__/lib/resolve-subagent-path.test.ts new file mode 100644 index 0000000..a68667e --- /dev/null +++ b/__tests__/lib/resolve-subagent-path.test.ts @@ -0,0 +1,65 @@ +// @vitest-environment node +import { mkdtemp, mkdir, rm, writeFile } from "fs/promises"; +import { tmpdir } from "os"; +import { dirname, join, relative } from "path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { resolveSubagentPath } from "@/lib/resolve-subagent-path"; + +describe("resolveSubagentPath", () => { + let fixtureRoot: string; + let projectsPath: string; + + const projectName = "project"; + const sessionId = "session"; + const agentId = "abc123"; + + beforeEach(async () => { + fixtureRoot = await mkdtemp(join(tmpdir(), "failproofai-subagents-")); + projectsPath = join(fixtureRoot, "projects"); + await mkdir(join(projectsPath, projectName, sessionId, "subagents"), { recursive: true }); + }); + + afterEach(async () => { + await rm(fixtureRoot, { recursive: true, force: true }); + }); + + async function touch(path: string): Promise { + await mkdir(dirname(path), { recursive: true }); + await writeFile(path, ""); + } + + it("returns candidate 1 when the project-level agent log exists", async () => { + const candidate = join(projectsPath, projectName, `agent-${agentId}.jsonl`); + await touch(candidate); + + await expect(resolveSubagentPath(projectsPath, projectName, sessionId, agentId)).resolves.toBe(candidate); + }); + + it("returns candidate 2 when candidate 1 is missing", async () => { + const candidate = join(projectsPath, projectName, sessionId, `agent-${agentId}.jsonl`); + await touch(candidate); + + await expect(resolveSubagentPath(projectsPath, projectName, sessionId, agentId)).resolves.toBe(candidate); + }); + + it("returns candidate 3 when candidates 1 and 2 are missing", async () => { + const candidate = join(projectsPath, projectName, sessionId, "subagents", `agent-${agentId}.jsonl`); + await touch(candidate); + + await expect(resolveSubagentPath(projectsPath, projectName, sessionId, agentId)).resolves.toBe(candidate); + }); + + it("returns null when none of the candidate paths exist", async () => { + await expect(resolveSubagentPath(projectsPath, projectName, sessionId, agentId)).resolves.toBeNull(); + }); + + it("does not resolve an existing candidate outside the projects path", async () => { + const traversalAgentId = "../../../../escape"; + const escapedCandidate = join(projectsPath, projectName, `agent-${traversalAgentId}.jsonl`); + expect(relative(projectsPath, escapedCandidate).startsWith("..")).toBe(true); + await touch(escapedCandidate); + + await expect(resolveSubagentPath(projectsPath, projectName, sessionId, traversalAgentId)).resolves.toBeNull(); + }); +}); From 53f51a9e29f2203b954d20e32f80919960b8ae1d Mon Sep 17 00:00:00 2001 From: looooown2006 <102905927+looooown2006@users.noreply.github.com> Date: Sat, 9 May 2026 15:59:51 +0800 Subject: [PATCH 2/2] test: cover subagent path priority --- __tests__/lib/resolve-subagent-path.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/__tests__/lib/resolve-subagent-path.test.ts b/__tests__/lib/resolve-subagent-path.test.ts index a68667e..4b5993d 100644 --- a/__tests__/lib/resolve-subagent-path.test.ts +++ b/__tests__/lib/resolve-subagent-path.test.ts @@ -50,6 +50,17 @@ describe("resolveSubagentPath", () => { await expect(resolveSubagentPath(projectsPath, projectName, sessionId, agentId)).resolves.toBe(candidate); }); + it("returns candidate 1 when all candidate paths exist", async () => { + const candidate1 = join(projectsPath, projectName, `agent-${agentId}.jsonl`); + const candidate2 = join(projectsPath, projectName, sessionId, `agent-${agentId}.jsonl`); + const candidate3 = join(projectsPath, projectName, sessionId, "subagents", `agent-${agentId}.jsonl`); + await touch(candidate3); + await touch(candidate2); + await touch(candidate1); + + await expect(resolveSubagentPath(projectsPath, projectName, sessionId, agentId)).resolves.toBe(candidate1); + }); + it("returns null when none of the candidate paths exist", async () => { await expect(resolveSubagentPath(projectsPath, projectName, sessionId, agentId)).resolves.toBeNull(); }); @@ -58,6 +69,7 @@ describe("resolveSubagentPath", () => { const traversalAgentId = "../../../../escape"; const escapedCandidate = join(projectsPath, projectName, `agent-${traversalAgentId}.jsonl`); expect(relative(projectsPath, escapedCandidate).startsWith("..")).toBe(true); + expect(relative(fixtureRoot, escapedCandidate).startsWith("..")).toBe(false); await touch(escapedCandidate); await expect(resolveSubagentPath(projectsPath, projectName, sessionId, traversalAgentId)).resolves.toBeNull();