|
| 1 | +import { mkdtempSync, rmSync } from "node:fs"; |
| 2 | +import { tmpdir } from "node:os"; |
| 3 | +import { join } from "node:path"; |
| 4 | +import { afterEach, describe, expect, it } from "vitest"; |
| 5 | +import { closeFixtureServer, runAsync, runExpectingFailure, startFixtureServer } from "./support/mcp-cli-harness"; |
| 6 | + |
| 7 | +describe("loopover-mcp CLI — contributor-profile (#6737)", () => { |
| 8 | + let tempDir: string | null = null; |
| 9 | + |
| 10 | + afterEach(async () => { |
| 11 | + await closeFixtureServer(); |
| 12 | + if (tempDir) rmSync(tempDir, { recursive: true, force: true }); |
| 13 | + tempDir = null; |
| 14 | + }); |
| 15 | + |
| 16 | + async function env(onApiRequest?: (request: import("node:http").IncomingMessage) => void) { |
| 17 | + tempDir = mkdtempSync(join(tmpdir(), "loopover-cli-")); |
| 18 | + const url = await startFixtureServer(onApiRequest ? { onApiRequest } : {}); |
| 19 | + return { LOOPOVER_API_URL: url, LOOPOVER_TOKEN: "session-token", LOOPOVER_CONFIG_DIR: tempDir, LOOPOVER_API_TIMEOUT_MS: "1000" }; |
| 20 | + } |
| 21 | + |
| 22 | + it("mirrors GET /v1/contributors/:login/profile for an explicit --login (plain + json)", async () => { |
| 23 | + const requests: string[] = []; |
| 24 | + const e = await env((request) => requests.push(request.url ?? "")); |
| 25 | + |
| 26 | + const plain = await runAsync(["contributor-profile", "--login", "octocat"], e); |
| 27 | + expect(plain).toMatch(/LoopOver contributor profile for octocat\./); |
| 28 | + expect(plain).toMatch(/3 registered repos; 12 merged PRs; strongest in review-tooling\./); |
| 29 | + expect(requests.at(-1)).toBe("/v1/contributors/octocat/profile"); |
| 30 | + |
| 31 | + const json = JSON.parse(await runAsync(["contributor-profile", "--login", "octocat", "--json"], e)) as { login: string; summary: string }; |
| 32 | + // Parity: the --json surface re-serializes the same payload the plain summary was built from. |
| 33 | + expect(json).toMatchObject({ login: "octocat", summary: "3 registered repos; 12 merged PRs; strongest in review-tooling." }); |
| 34 | + }); |
| 35 | + |
| 36 | + it("resolves the login from LOOPOVER_LOGIN when --login is omitted, and url-encodes it", async () => { |
| 37 | + const requests: string[] = []; |
| 38 | + const e = await env((request) => requests.push(request.url ?? "")); |
| 39 | + |
| 40 | + await runAsync(["contributor-profile"], { ...e, LOOPOVER_LOGIN: "a b/c" }); |
| 41 | + expect(requests.at(-1)).toBe("/v1/contributors/a%20b%2Fc/profile"); |
| 42 | + }); |
| 43 | + |
| 44 | + it("errors (never issuing a request) when no login can be resolved", async () => { |
| 45 | + const requests: string[] = []; |
| 46 | + const e = await env((request) => requests.push(request.url ?? "")); |
| 47 | + // Clear every login fallback the CLI would otherwise read from the ambient process env. |
| 48 | + const failure = runExpectingFailure(["contributor-profile"], { ...e, LOOPOVER_LOGIN: "", GITHUB_LOGIN: "" }); |
| 49 | + expect(`${failure.stdout}${failure.stderr}`).toMatch(/Pass --login/); |
| 50 | + expect(requests.filter((url) => url.includes("/profile"))).toHaveLength(0); |
| 51 | + }); |
| 52 | +}); |
0 commit comments