strengthen repo verification and runtime coverage

Add clearer app docs plus targeted desktop, CLI, web, and worker tests so cross-surface regressions are caught earlier and the repo is easier to navigate.
This commit is contained in:
nocxcloud-oss 2026-04-15 19:10:41 +08:00
parent 2133d7226f
commit 4239f9f1ef
63 changed files with 3678 additions and 764 deletions

View file

@ -0,0 +1,83 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import path from "node:path";
import { WorkDir } from "../dist/config/config.js";
import { FSModelConfigRepo } from "../dist/models/repo.js";
import { FSMcpConfigRepo } from "../dist/mcp/repo.js";
import { FSAgentsRepo } from "../dist/agents/repo.js";
import { FSRunsRepo } from "../dist/runs/repo.js";
test("uses ROWBOAT_WORKDIR override and eagerly creates expected directories", async () => {
assert.equal(WorkDir, process.env.ROWBOAT_WORKDIR);
for (const dirName of ["agents", "config", "runs"]) {
const stats = await fs.stat(path.join(WorkDir, dirName));
assert.equal(stats.isDirectory(), true);
}
});
test("FSModelConfigRepo returns defaults on a fresh workspace", async () => {
const repo = new FSModelConfigRepo();
const config = await repo.getConfig();
assert.equal(config.defaults.provider, "openai");
assert.equal(config.defaults.model, "gpt-5.1");
assert.equal(config.providers.openai?.flavor, "openai");
});
test("FSMcpConfigRepo returns an empty config on a fresh workspace", async () => {
const repo = new FSMcpConfigRepo();
const config = await repo.getConfig();
assert.deepEqual(config, { mcpServers: {} });
});
test("FSAgentsRepo can create and read nested agent files", async () => {
const repo = new FSAgentsRepo();
await repo.create({
name: "team/copilot",
description: "Team helper",
provider: "openai",
model: "gpt-5.1",
instructions: "Be helpful.",
});
const fetched = await repo.fetch("team/copilot");
assert.equal(fetched.name, "team/copilot");
assert.equal(fetched.description, "Team helper");
assert.equal(fetched.instructions, "Be helpful.");
});
test("FSRunsRepo creates, fetches, and lists runs", async () => {
let nextId = 0;
const repo = new FSRunsRepo({
idGenerator: {
next: async () => `run-${++nextId}`,
},
});
const first = await repo.create({ agentId: "copilot" });
await repo.appendEvents(first.id, [{
type: "message",
runId: first.id,
subflow: [],
messageId: "msg-1",
message: {
role: "user",
content: "hello",
},
}]);
const second = await repo.create({ agentId: "planner" });
const fetched = await repo.fetch(first.id);
assert.equal(fetched.id, first.id);
assert.equal(fetched.agentId, "copilot");
assert.equal(fetched.log.length, 2);
assert.equal(fetched.log[1].type, "message");
const listed = await repo.list();
assert.deepEqual(listed.runs.map((run) => run.id), [second.id, first.id]);
});

View file

@ -0,0 +1,31 @@
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const packageDir = path.resolve(__dirname, "..");
const tempRoot = await mkdtemp(path.join(tmpdir(), "rowboat-cli-test-"));
const testWorkDir = path.join(tempRoot, "workspace");
try {
const exitCode = await new Promise((resolve, reject) => {
const child = spawn(process.execPath, ["--test", "./test/repos.test.mjs", "./test/server.test.mjs"], {
cwd: packageDir,
stdio: "inherit",
env: {
...process.env,
ROWBOAT_WORKDIR: testWorkDir,
},
});
child.on("error", reject);
child.on("exit", (code) => resolve(code ?? 1));
});
process.exitCode = Number(exitCode);
} finally {
await rm(tempRoot, { recursive: true, force: true });
}

View file

@ -0,0 +1,131 @@
import test from "node:test";
import assert from "node:assert/strict";
import { createApp } from "../dist/server.js";
test("message endpoint creates a message and returns its id", async () => {
const calls = [];
const app = createApp({
createMessage: async (runId, message) => {
calls.push({ runId, message });
return "msg-123";
},
authorizePermission: async () => {},
replyToHumanInputRequest: async () => {},
stop: async () => {},
subscribeToEvents: async () => () => {},
});
const response = await app.request("/runs/run-1/messages/new", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ message: "hello" }),
});
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), { messageId: "msg-123" });
assert.deepEqual(calls, [{ runId: "run-1", message: "hello" }]);
});
test("permission endpoint validates payload and calls dependency", async () => {
const calls = [];
const app = createApp({
createMessage: async () => "unused",
authorizePermission: async (runId, payload) => {
calls.push({ runId, payload });
},
replyToHumanInputRequest: async () => {},
stop: async () => {},
subscribeToEvents: async () => () => {},
});
const response = await app.request("/runs/run-2/permissions/authorize", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
subflow: ["child"],
toolCallId: "tool-1",
response: "approve",
}),
});
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), { success: true });
assert.deepEqual(calls, [{
runId: "run-2",
payload: {
subflow: ["child"],
toolCallId: "tool-1",
response: "approve",
},
}]);
});
test("invalid message payload returns a validation error", async () => {
const app = createApp({
createMessage: async () => "unused",
authorizePermission: async () => {},
replyToHumanInputRequest: async () => {},
stop: async () => {},
subscribeToEvents: async () => () => {},
});
const response = await app.request("/runs/run-1/messages/new", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({}),
});
assert.equal(response.status, 400);
});
test("openapi endpoint is exposed", async () => {
const app = createApp({
createMessage: async () => "unused",
authorizePermission: async () => {},
replyToHumanInputRequest: async () => {},
stop: async () => {},
subscribeToEvents: async () => () => {},
});
const response = await app.request("/openapi.json");
const body = await response.json();
assert.equal(response.status, 200);
assert.equal(body.info.title, "Hono");
assert.ok(body.paths["/runs/{runId}/messages/new"]);
});
test("stream endpoint emits SSE payloads and unsubscribes on cancel", async () => {
let listener;
let unsubscribed = false;
const app = createApp({
createMessage: async () => "unused",
authorizePermission: async () => {},
replyToHumanInputRequest: async () => {},
stop: async () => {},
subscribeToEvents: async (fn) => {
listener = fn;
return () => {
unsubscribed = true;
};
},
});
const response = await app.request("/stream");
assert.equal(response.status, 200);
assert.equal(response.headers.get("content-type"), "text/event-stream");
await listener({ type: "message", data: { hello: "world" } });
const reader = response.body.getReader();
const chunk = await reader.read();
const text = new TextDecoder().decode(chunk.value);
assert.match(text, /event: message/);
assert.match(text, /"hello":"world"/);
await reader.cancel();
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(unsubscribed, true);
});