forked from EduCraft/curriculum-project-hub
133 lines
5.5 KiB
TypeScript
133 lines
5.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { mkdir, realpath, rm, symlink, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { resolveDeliverableFile } from "../../src/feishu/fileDelivery.js";
|
|
|
|
const itOnLinux = process.platform === "linux" ? it : it.skip;
|
|
|
|
describe("file delivery path resolution", () => {
|
|
itOnLinux("never resolves a Git-root file outside the current project workspace", async () => {
|
|
const root = await makeRepo();
|
|
try {
|
|
const workspace = join(root, "examples", "TH-141");
|
|
await writeFile(join(root, "README.md"), "# root readme\n");
|
|
|
|
await expect(resolveDeliverableFile("README.md", join(root, "examples"), workspace)).resolves.toBeNull();
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
itOnLinux("resolves a bare build filename against workspace/build", async () => {
|
|
const root = await makeRepo();
|
|
try {
|
|
const workspace = join(root, "examples", "TH-141");
|
|
const pdf = join(workspace, "build", "student.pdf");
|
|
await writeFile(pdf, "pdf bytes");
|
|
|
|
const resolved = await resolveDeliverableFile("student.pdf", join(root, "examples"), workspace);
|
|
expect(resolved).toMatchObject({ path: await realpath(pdf), name: "student.pdf" });
|
|
expect(resolved?.data.toString("utf8")).toBe("pdf bytes");
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
itOnLinux("accepts arbitrary extensions and extensionless workspace files", async () => {
|
|
const root = await makeRepo();
|
|
try {
|
|
const workspace = join(root, "examples", "TH-141");
|
|
await writeFile(join(workspace, "lesson.json"), "{\"ok\":true}\n");
|
|
await writeFile(join(workspace, "Makefile"), "all:\n\t@true\n");
|
|
|
|
await expect(resolveDeliverableFile("lesson.json", join(root, "examples"), workspace))
|
|
.resolves.toMatchObject({ name: "lesson.json" });
|
|
await expect(resolveDeliverableFile("Makefile", join(root, "examples"), workspace))
|
|
.resolves.toMatchObject({ name: "Makefile" });
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
itOnLinux("allows the Feishu inbox but refuses other platform runtime files", async () => {
|
|
const root = await makeRepo();
|
|
try {
|
|
const workspace = join(root, "examples", "TH-141");
|
|
await mkdir(join(workspace, ".cph", "agent-runtime"), { recursive: true });
|
|
await mkdir(join(workspace, ".cph", "inbox"), { recursive: true });
|
|
await writeFile(join(workspace, ".cph", "agent-runtime", "session.jsonl"), "internal\n");
|
|
await writeFile(join(workspace, ".cph", "denials.log"), "internal\n");
|
|
await writeFile(join(workspace, ".cph", "inbox", "source.bin"), "source\n");
|
|
|
|
await expect(
|
|
resolveDeliverableFile(".cph/inbox/source.bin", join(root, "examples"), workspace),
|
|
).resolves.toMatchObject({ name: "source.bin" });
|
|
|
|
await expect(
|
|
resolveDeliverableFile(".cph/agent-runtime/session.jsonl", join(root, "examples"), workspace),
|
|
).rejects.toMatchObject({ reason: "boundary" });
|
|
await expect(
|
|
resolveDeliverableFile(".cph/denials.log", join(root, "examples"), workspace),
|
|
).rejects.toMatchObject({ reason: "boundary" });
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
itOnLinux("refuses a file larger than the configured delivery limit", async () => {
|
|
const root = await makeRepo();
|
|
try {
|
|
const workspace = join(root, "examples", "TH-141");
|
|
await writeFile(join(workspace, "exact.bin"), Buffer.alloc(10));
|
|
await writeFile(join(workspace, "artifact.bin"), Buffer.alloc(11));
|
|
|
|
await expect(
|
|
resolveDeliverableFile("exact.bin", join(root, "examples"), workspace, 10),
|
|
).resolves.toMatchObject({ name: "exact.bin", data: Buffer.alloc(10) });
|
|
await expect(
|
|
resolveDeliverableFile("artifact.bin", join(root, "examples"), workspace, 10),
|
|
).rejects.toMatchObject({ reason: "limit" });
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
itOnLinux("rejects a deliverable symlink even when its target exists", async () => {
|
|
const root = await makeRepo();
|
|
try {
|
|
const workspace = join(root, "examples", "TH-141");
|
|
const outside = join(root, "outside.pdf");
|
|
await writeFile(outside, "outside secret");
|
|
await symlink(outside, join(workspace, "build", "student.pdf"));
|
|
|
|
await expect(
|
|
resolveDeliverableFile("student.pdf", join(root, "examples"), workspace),
|
|
).rejects.toThrow(/symlink|no-follow|directory/i);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
itOnLinux("does not infer files from natural-language prompts", async () => {
|
|
const root = await makeRepo();
|
|
try {
|
|
const workspace = join(root, "examples", "TH-141");
|
|
await writeFile(join(workspace, "build", "student.pdf"), "pdf bytes");
|
|
|
|
await expect(
|
|
resolveDeliverableFile("cph build 生成 PDF 给我", join(root, "examples"), workspace),
|
|
).resolves.toBeNull();
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
async function makeRepo(): Promise<string> {
|
|
const root = await mkdir(join(tmpdir(), `hub-file-delivery-${Date.now()}-${Math.random().toString(36).slice(2)}`), { recursive: true });
|
|
await mkdir(join(root, ".git"));
|
|
await mkdir(join(root, "examples", "TH-141", "build"), { recursive: true });
|
|
return root;
|
|
}
|