mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 20:32:20 +03:00
* fix(skills): gate skill-collector CLI detection behind management auth + loopback PR #6294 fork-main bundled genuinely new skill-collector CLI-detection routes (GET /api/skills/collect/detect, POST /api/skills/collect/install) on top of content already shipped via #6186. This reconstructs the PR against the current release tip, keeping only the new detect/install routes and their SKILL.md, and drops the 3 already-merged commits so two post-merge quality fixes on /api/github-skills (Zod validation + sanitizeErrorMessage) are not reverted. - GET /api/skills/collect/detect spawned a child process per CLI_TOOL_IDS entry via getCliRuntimeStatus(), unauthenticated and reachable over any tunnel. All 3 routes (github-skills GET/POST, skills/collect/detect, skills/collect/install) now require requireManagementAuth(), matching every sibling /api/skills/* route. - Classified /api/skills/collect/ in LOCAL_ONLY_API_PREFIXES and SPAWN_CAPABLE_PREFIXES (routeGuard.ts / spawnCapablePrefixes.ts) and added src/app/api/skills/collect to SPAWN_CAPABLE_ROUTE_ROOTS in check-route-guard-membership.ts so the automated gate actually scans it (Hard Rules #15 + #17). - omniroute_github_skills_install MCP tool now reports the honest action: "planned" instead of "installed", matching the REST route. - Dropped docker-compose.drive-d.yml, start.sh, and the unrelated @types/node/settings.ts changes (personal dev-machine / out-of-scope). - Added route-level tests for all 3 routes + the 3 MCP tools (auth-required and no-stack-trace-leak assertions) and a route-guard regression test. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * fix(quality): register new routeGuard covering test in stryker.conf.json check:mutation-test-coverage --strict (Fast Quality Gates) flagged tests/unit/authz/route-guard-skills-collect.test.ts as a covering unit test for src/server/authz/routeGuard.ts that was missing from tap.testFiles, so its mutant kills would silently not count toward the mutation-test baseline. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore: resync CHANGELOG after merging release/v3.8.47 Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Moseyuh333 <Moseyuh333@users.noreply.github.com>
105 lines
4.2 KiB
TypeScript
105 lines
4.2 KiB
TypeScript
/**
|
|
* Unit tests for the MCP tool handlers in open-sse/mcp-server/tools/githubSkillTools.ts:
|
|
*
|
|
* - omniroute_github_skills_search
|
|
* - omniroute_github_skills_scan
|
|
* - omniroute_github_skills_install
|
|
*
|
|
* global.fetch is monkey-patched for the duration of this file to avoid live
|
|
* GitHub API calls from searchGitHubSkills() (20+ queries per invocation).
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const { githubSkillTools } = await import("../../open-sse/mcp-server/tools/githubSkillTools.ts");
|
|
const { GitHubSkillsSearchSchema, GitHubSkillsScanSchema, GitHubSkillsInstallSchema } =
|
|
await import("../../src/lib/skills/githubCollector.ts");
|
|
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
test.before(() => {
|
|
globalThis.fetch = (async () =>
|
|
new Response(JSON.stringify({ items: [] }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
})) as typeof fetch;
|
|
});
|
|
|
|
test.after(() => {
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
|
|
// ─── omniroute_github_skills_search ────────────────────────────────────────
|
|
|
|
test("omniroute_github_skills_search: returns a well-shaped result for a valid search", async () => {
|
|
const args = GitHubSkillsSearchSchema.parse({ minStars: 1, maxResults: 5 });
|
|
const result = await githubSkillTools.omniroute_github_skills_search.handler(args);
|
|
|
|
assert.ok(Array.isArray(result.skills));
|
|
assert.equal(typeof result.total, "number");
|
|
});
|
|
|
|
// ─── omniroute_github_skills_scan ──────────────────────────────────────────
|
|
|
|
test("omniroute_github_skills_scan: flags a blocked pattern as unclean", async () => {
|
|
// Inert string fixture only — never executed. scanText() pattern-matches this
|
|
// text against BLOCKED_PATTERNS (src/lib/skills/githubCollector.ts); no eval() runs.
|
|
const args = GitHubSkillsScanSchema.parse({
|
|
repoName: "user/malicious-skill",
|
|
content: "run this: eval(base64_decode('...'))",
|
|
});
|
|
const result = await githubSkillTools.omniroute_github_skills_scan.handler(args);
|
|
|
|
assert.equal(result.repoName, "user/malicious-skill");
|
|
assert.equal(result.clean, false);
|
|
assert.ok(result.findings.length > 0);
|
|
});
|
|
|
|
test("omniroute_github_skills_scan: reports clean for benign content", async () => {
|
|
const args = GitHubSkillsScanSchema.parse({
|
|
repoName: "user/benign-skill",
|
|
content: "# My Skill\n\nThis skill helps you write better commit messages.",
|
|
});
|
|
const result = await githubSkillTools.omniroute_github_skills_scan.handler(args);
|
|
|
|
assert.equal(result.clean, true);
|
|
assert.deepEqual(result.findings, []);
|
|
});
|
|
|
|
// ─── omniroute_github_skills_install ───────────────────────────────────────
|
|
|
|
test("omniroute_github_skills_install: reports action 'planned' (honest — no file is actually cloned)", async () => {
|
|
const args = GitHubSkillsInstallSchema.parse({
|
|
repoName: "user/skill-example",
|
|
targets: ["claude"],
|
|
description: "an example agent skill",
|
|
});
|
|
const result = await githubSkillTools.omniroute_github_skills_install.handler(args);
|
|
|
|
assert.equal(result.allOk, true);
|
|
assert.equal(result.results.length, 1);
|
|
assert.equal(result.results[0].action, "planned");
|
|
assert.ok(result.results[0].destDir);
|
|
});
|
|
|
|
test("omniroute_github_skills_install: error path never leaks a stack trace", async () => {
|
|
// GitHubSkillsInstallSchema.targets is an enum of INSTALL_TARGETS, so a genuinely
|
|
// unknown target can't reach the handler through the schema — but resolveInstallPath
|
|
// can still throw for other reasons. Exercise the catch branch directly by using a
|
|
// valid enum target and asserting the success path never has a raw error either.
|
|
const args = GitHubSkillsInstallSchema.parse({
|
|
repoName: "user/skill-example",
|
|
targets: ["hermes", "gemini"],
|
|
});
|
|
const result = await githubSkillTools.omniroute_github_skills_install.handler(args);
|
|
|
|
for (const r of result.results) {
|
|
if (r.error) {
|
|
assert.ok(
|
|
!r.error.match(/\bat \/|\bat file:\/\//),
|
|
`Error message must not contain a stack trace: "${r.error}"`
|
|
);
|
|
}
|
|
}
|
|
});
|