test(api): add unit tests for Jira/Confluence/GitLab helpers, drop obsolete shell test
This commit is contained in:
Generated
+4
-1208
File diff suppressed because it is too large
Load Diff
+2
-4
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "node --experimental-strip-types test/guardrails.test.ts && bash test/shell.test.sh && bash test/uninstall.test.sh"
|
||||
"test": "node --experimental-strip-types test/guardrails.test.ts && node --experimental-strip-types test/api.test.ts && bash test/uninstall.test.sh"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-ai": "*",
|
||||
@@ -37,9 +37,7 @@
|
||||
"typebox": "*"
|
||||
},
|
||||
"dependencies": {},
|
||||
"optionalDependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.29.0"
|
||||
},
|
||||
"optionalDependencies": {},
|
||||
"devDependencies": {
|
||||
"typescript": "^5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Unit tests for the Jira / Confluence / GitLab REST helpers.
|
||||
* The lib modules are pure (no pi API), so we inject a mocked `fetchImpl`.
|
||||
* Run with: node --experimental-strip-types test/api.test.ts
|
||||
*/
|
||||
process.env.PI_KIT_AUDIT_OFF = "1";
|
||||
process.env.JIRA_TOKEN = "t-jira";
|
||||
process.env.CONFLUENCE_TOKEN = "t-conf";
|
||||
process.env.GITLAB_TOKEN = "t-glab";
|
||||
|
||||
import { buildUrl, requireToken, ApiError } from "../extensions/lib/atlassian.ts";
|
||||
import { jiraBaseUrl, issueGet } from "../extensions/lib/jira.ts";
|
||||
import { gitlabBaseUrl, projectId } from "../extensions/lib/gitlab.ts";
|
||||
import { confluenceBaseUrl } from "../extensions/lib/confluence.ts";
|
||||
|
||||
let fails = 0;
|
||||
function check(name: string, cond: boolean) {
|
||||
console.log(`${cond ? "PASS" : "FAIL"} ${name}`);
|
||||
if (!cond) fails++;
|
||||
}
|
||||
|
||||
function okFetch(jsonBody: unknown): typeof fetch {
|
||||
return (async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => jsonBody,
|
||||
})) as any;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// --- atlassian helpers ---
|
||||
check("buildUrl: appends query, drops empties", buildUrl("https://x/", "/a", { q: "1", e: undefined }) === "https://x/a?q=1");
|
||||
check("buildUrl: no query when none", buildUrl("https://x", "/a") === "https://x/a");
|
||||
check("requireToken: returns value", requireToken("JIRA_TOKEN", "h") === "t-jira");
|
||||
check("requireToken: throws on missing", (() => {
|
||||
try { requireToken("NO_SUCH_VAR_ZZZ", "h"); return false; } catch (e) { return e instanceof ApiError; }
|
||||
})());
|
||||
|
||||
// --- config resolution (needs a real company.json present) ---
|
||||
check("jiraBaseUrl resolves", typeof jiraBaseUrl() === "string" && jiraBaseUrl() !== "");
|
||||
check("gitlabBaseUrl ends with api/v4", gitlabBaseUrl().endsWith("/api/v4"));
|
||||
check("confluenceBaseUrl resolves", typeof confluenceBaseUrl() === "string" && confluenceBaseUrl() !== "");
|
||||
|
||||
// --- gitlab project id encoding ---
|
||||
check("projectId encodes namespaces", projectId("grp/repo") === "grp%2Frepo");
|
||||
check("projectId passes through numeric id", projectId("42") === "42");
|
||||
|
||||
// --- issueGet through a mocked fetch (field mapping) ---
|
||||
const issueUrl = await issueGetByKey("PROJ-123");
|
||||
check("issueGet hits /rest/api/2/issue/<key>", issueUrl.includes("/rest/api/2/issue/"));
|
||||
|
||||
console.log(fails === 0 ? "\nALL PASS" : `\n${fails} FAILED`);
|
||||
process.exit(fails === 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
async function issueGetByKey(key: string): Promise<string> {
|
||||
// Capture the URL by injecting a mocked fetch into the issueGet call path.
|
||||
// We temporarily swap global.fetch.
|
||||
const realFetch = globalThis.fetch;
|
||||
let captured = "";
|
||||
globalThis.fetch = (async (input: any) => {
|
||||
captured = String(input);
|
||||
return { ok: true, status: 200, json: async () => ({ key, fields: { summary: "s", status: { name: "Done" } } }) };
|
||||
}) as any;
|
||||
try {
|
||||
await issueGet(key);
|
||||
} finally {
|
||||
globalThis.fetch = realFetch;
|
||||
}
|
||||
return captured;
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -25,7 +25,6 @@ import secretScanner from "../extensions/secret-scanner.ts";
|
||||
import commitGuard from "../extensions/commit-guard.ts";
|
||||
import llmRedaction from "../extensions/llm-redaction.ts";
|
||||
import companyContext from "../extensions/company-context.ts";
|
||||
import mcpBridge from "../extensions/mcp-bridge.ts";
|
||||
import { redact, scanSecrets } from "../extensions/lib/secrets.ts";
|
||||
import { normalize, renderContext, fetchRemoteConfig, MAX_CONTEXT_LINES } from "../extensions/lib/company-config.ts";
|
||||
|
||||
@@ -166,18 +165,6 @@ async function run() {
|
||||
check("company-context: registers /kit-config", grabCommands(companyContext).includes("kit-config"));
|
||||
}
|
||||
|
||||
// --- mcp-bridge: disabled servers must not connect or error ---
|
||||
{
|
||||
const registered: string[] = [];
|
||||
const tools: string[] = [];
|
||||
await mcpBridge({
|
||||
registerCommand: (name: string) => registered.push(name),
|
||||
registerTool: () => tools.push("x"),
|
||||
on() {},
|
||||
} as any);
|
||||
check("mcp-bridge: registers /mcp-status", registered.includes("mcp-status"));
|
||||
check("mcp-bridge: no tools with all servers disabled", tools.length === 0);
|
||||
}
|
||||
|
||||
console.log(fails === 0 ? "\nALL PASS" : `\n${fails} FAILED`);
|
||||
process.exit(fails === 0 ? 0 : 1);
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shell smoke tests for skills/jira-workflow/scripts/create-mr.sh.
|
||||
# Network-free: uses MR_DRY_RUN=1 and a throwaway git repo.
|
||||
# Run via `npm test` (after guardrails.test.ts) or directly: bash test/shell.test.sh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SCRIPT="$ROOT/skills/jira-workflow/scripts/create-mr.sh"
|
||||
FAILS=0
|
||||
|
||||
check() { # <name> <expected-substring> <actual>
|
||||
local name="$1" expected="$2" actual="$3"
|
||||
if grep -qF -- "$expected" <<<"$actual"; then
|
||||
echo "PASS $name"
|
||||
else
|
||||
echo "FAIL $name (expected substring: $expected)"
|
||||
echo "--- actual ---"
|
||||
echo "$actual"
|
||||
FAILS=$((FAILS + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# Fresh throwaway repo with an empty commit.
|
||||
DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$DIR"' EXIT
|
||||
cd "$DIR"
|
||||
git init -q
|
||||
git config user.email test@example.com
|
||||
git config user.name Test
|
||||
git remote add origin git@gitlab.tech.mvideo.ru:ai/foo.git
|
||||
git commit --allow-empty -q -m "chore: init"
|
||||
git checkout -q -b feature/PROJ-1-smoke
|
||||
|
||||
# 1. scp-like origin → API URL with encoded namespace.
|
||||
OUT="$(GITLAB_TOKEN=dummy MR_DRY_RUN=1 bash "$SCRIPT" "Test MR" 2>&1)"
|
||||
check "create-mr: scp-like origin parse" "POST https://gitlab.tech.mvideo.ru/api/v4/projects/ai%2Ffoo/merge_requests" "$OUT"
|
||||
check "create-mr: dry-run shows would-push" "would push: git push -u origin feature/PROJ-1-smoke" "$OUT"
|
||||
|
||||
# 2. https-like origin → same API shape, different host.
|
||||
git remote set-url origin "https://git.codelab.vc/ai/bar.git"
|
||||
OUT="$(GITLAB_TOKEN=dummy MR_DRY_RUN=1 bash "$SCRIPT" "Test MR" 2>&1)"
|
||||
check "create-mr: https origin parse" "POST https://git.codelab.vc/api/v4/projects/ai%2Fbar/merge_requests" "$OUT"
|
||||
|
||||
# 3. GITLAB_HOST overrides the derived host.
|
||||
OUT="$(GITLAB_TOKEN=dummy GITLAB_HOST=gitlab.example.com MR_DRY_RUN=1 bash "$SCRIPT" "Test MR" 2>&1)"
|
||||
check "create-mr: GITLAB_HOST override" "POST https://gitlab.example.com/api/v4/projects/ai%2Fbar/merge_requests" "$OUT"
|
||||
|
||||
# 4. Refuses to create MR from a protected/release source branch.
|
||||
git checkout -q -b release-42
|
||||
OUT="$(GITLAB_TOKEN=dummy MR_DRY_RUN=1 bash "$SCRIPT" "Test MR" 2>&1 || true)"
|
||||
check "create-mr: refuses release-* source" "Refusing: source branch" "$OUT"
|
||||
|
||||
echo ""
|
||||
if [ "$FAILS" -eq 0 ]; then
|
||||
echo "shell tests: ALL PASS"
|
||||
exit 0
|
||||
else
|
||||
echo "shell tests: $FAILS FAILED"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user