forked from EduCraft/curriculum-project-hub
88386fb943
Agent tool downloads and trigger attachment staging both used the SDK messageResource path, which fails closed for multi-MB teacher files and did not share the bot-identity transport contract. Route every download through Hub-owned createFeishuBotCli (secret via stdin, disposable HOME, HUB_FEISHU_CLI_BIN), keep workspace containment on write, and inject the adapter in trigger tests.
102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { describe, expect, it } from "vitest";
|
|
import { createFeishuBotCli } from "../../src/feishu/botCli.js";
|
|
import type { PrismaClient } from "@prisma/client";
|
|
import type { LocalSecretEnvelope } from "../../src/security/secretEnvelope.js";
|
|
const itOnLinux = process.platform === "linux" ? it : it.skip;
|
|
|
|
const fakeCredential = {
|
|
connectionId: "connection-1",
|
|
organizationId: "org-1",
|
|
appId: "cli-test-app",
|
|
appSecret: "cli-test-secret",
|
|
botOpenId: "ou-test-bot",
|
|
verificationToken: "verification-token",
|
|
encryptKey: "encrypt-key",
|
|
};
|
|
|
|
describe("Feishu bot CLI adapter", () => {
|
|
itOnLinux("uses bot identity and writes the CLI result into the workspace", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "hub-feishu-bot-cli-test-"));
|
|
const workspaceDir = join(root, "workspace");
|
|
const binary = join(root, "fake-lark-cli");
|
|
await mkdir(workspaceDir);
|
|
await writeFakeCli(binary);
|
|
try {
|
|
const cli = createFeishuBotCli({
|
|
organizationId: "org-1",
|
|
prisma: {} as PrismaClient,
|
|
secretEnvelope: {} as LocalSecretEnvelope,
|
|
binary,
|
|
resolveCredential: async () => fakeCredential,
|
|
});
|
|
|
|
const result = await cli.downloadResource({
|
|
messageId: "message-1",
|
|
fileKey: "file-1",
|
|
resourceType: "file",
|
|
workspaceRoot: root,
|
|
workspaceDir,
|
|
workspaceRelativePath: "inbox/resource.bin",
|
|
maxBytes: 1024,
|
|
});
|
|
|
|
await expect(readFile(result, "utf8")).resolves.toBe("resource bytes");
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("rejects a resource above the configured limit", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "hub-feishu-bot-cli-limit-"));
|
|
const workspaceDir = join(root, "workspace");
|
|
const binary = join(root, "fake-lark-cli");
|
|
await mkdir(workspaceDir);
|
|
await writeFakeCli(binary);
|
|
try {
|
|
const cli = createFeishuBotCli({
|
|
organizationId: "org-1",
|
|
prisma: {} as PrismaClient,
|
|
secretEnvelope: {} as LocalSecretEnvelope,
|
|
binary,
|
|
resolveCredential: async () => fakeCredential,
|
|
});
|
|
|
|
await expect(cli.downloadResource({
|
|
messageId: "message-1",
|
|
fileKey: "file-1",
|
|
resourceType: "file",
|
|
workspaceRoot: root,
|
|
workspaceDir,
|
|
workspaceRelativePath: "inbox/resource.bin",
|
|
maxBytes: 4,
|
|
})).rejects.toMatchObject({ reason: "limit" });
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
async function writeFakeCli(path: string): Promise<void> {
|
|
await writeFile(path, `#!/usr/bin/env node
|
|
import { writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
const args = process.argv.slice(2);
|
|
if (args[0] === "config" && args[1] === "init") {
|
|
process.stdin.resume();
|
|
process.stdin.on("end", () => process.exit(0));
|
|
} else if (args.includes("+messages-resources-download")) {
|
|
const asIndex = args.indexOf("--as");
|
|
const outputIndex = args.indexOf("--output");
|
|
if (asIndex < 0 || args[asIndex + 1] !== "bot" || outputIndex < 0) process.exit(2);
|
|
writeFileSync(join(process.cwd(), args[outputIndex + 1]), "resource bytes");
|
|
process.exit(0);
|
|
} else {
|
|
process.exit(3);
|
|
}
|
|
`);
|
|
await chmod(path, 0o755);
|
|
}
|