Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions test/unit/local-branch.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { execFileSync } from "node:child_process";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
Expand Down Expand Up @@ -2472,19 +2472,23 @@ describe("local MCP git metadata collection (#7329 coverage)", () => {
const root = join(tempDir, "root");
mkdirSync(join(root, "nested"), { recursive: true });
const roots = [{ uri: pathToFileURL(root).href }];
// resolveWorkspaceCwd resolves symlinks via realpathSync (safeResolvedPath), so the expected path must
// too -- on macOS, os.tmpdir() lives under /var/folders/..., itself a symlink to /private/var/folders/....
const expectedNested = realpathSync(join(root, "nested"));

const relative = resolveWorkspaceCwd({ cwd: "nested", workspaceRoots: roots });
expect(relative).toMatchObject({ cwd: join(root, "nested"), rootsAvailable: true });
expect(relative).toMatchObject({ cwd: expectedNested, rootsAvailable: true });

const absolute = resolveWorkspaceCwd({ cwd: join(root, "nested"), workspaceRoots: roots });
expect(absolute).toMatchObject({ cwd: join(root, "nested"), rootsAvailable: true });
expect(absolute).toMatchObject({ cwd: expectedNested, rootsAvailable: true });
});

it("normalizeMcpWorkspaceRoots ignores a root with a non-string uri and dedupes repeated paths", async () => {
const { normalizeMcpWorkspaceRoots } = await import("../../packages/loopover-mcp/lib/local-branch.js");
tempDir = mkdtempSync(join(tmpdir(), "loopover-local-"));
const uri = pathToFileURL(tempDir).href;
expect(normalizeMcpWorkspaceRoots([{ uri: 42 as unknown as string }, { uri }, { uri }])).toEqual([{ path: tempDir }]);
// normalizeMcpWorkspaceRoots resolves symlinks via realpathSync (safeResolvedPath) -- see the note above.
expect(normalizeMcpWorkspaceRoots([{ uri: 42 as unknown as string }, { uri }, { uri }])).toEqual([{ path: realpathSync(tempDir) }]);
expect(normalizeMcpWorkspaceRoots(undefined)).toEqual([]);
});

Expand Down
11 changes: 11 additions & 0 deletions test/unit/miner-init-verify-token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,21 @@ const tempDirs = new Set<string>();
function makeTempEnv() {
const configDir = mkdtempSync(join(tmpdir(), "loopover-miner-init-"));
tempDirs.add(configDir);
// Isolate from any real loopover-mcp session on disk: runInit's --verify-token path resolves a GitHub
// token via resolveGitHubToken (packages/loopover-miner/lib/github-token-resolution.js), which falls back
// to a live session-token fetch when ~/.config/loopover/config.json (or an inherited GITHUB_TOKEN) has
// real credentials -- a contributor machine with `loopover-mcp login` already run would otherwise make an
// extra fetch call and read a real token into these tests instead of the mocked one.
const loopoverConfigDir = mkdtempSync(join(tmpdir(), "loopover-miner-init-mcp-config-"));
tempDirs.add(loopoverConfigDir);
return {
env: {
...process.env,
LOOPOVER_MINER_CONFIG_DIR: configDir,
LOOPOVER_CONFIG_DIR: loopoverConfigDir,
LOOPOVER_CONFIG_PATH: undefined,
XDG_CONFIG_HOME: undefined,
GITHUB_TOKEN: undefined,
},
configDir,
dbPath: join(configDir, "laptop-state.sqlite3"),
Expand Down
51 changes: 29 additions & 22 deletions test/unit/miner-self-review-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
contributorLogin: "miner-bot",
linkedIssues: [7],
fetchImpl: fetchImpl as never,
loopoverAuth: null,
});

expect(result.repo).toEqual({
Expand Down Expand Up @@ -225,7 +226,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { linkedIssues: [7], fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { linkedIssues: [7], fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.inDuplicateCluster).toBe(true);
});

Expand All @@ -238,7 +239,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.issueQuality?.issues).toHaveLength(1);
expect(result.issueQuality?.issues[0]).toMatchObject({ number: 7, status: expect.any(String) });
expect(result.issueQuality?.repoFullName).toBe("acme/widgets");
Expand All @@ -260,7 +261,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.issues[0]?.linkedPrs).toEqual([]);
});

Expand All @@ -273,7 +274,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.issues[0]?.linkedPrs).toEqual([501]);
});
it("returns false for inDuplicateCluster when no linkedIssues are supplied", async () => {
Expand All @@ -285,7 +286,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.inDuplicateCluster).toBe(false);
});

Expand All @@ -298,7 +299,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.issues.map((issue) => issue.number)).toEqual([7]);
});

Expand All @@ -318,7 +319,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
return jsonResponse(null, 404);
};

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, perPage: 2 });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, perPage: 2, loopoverAuth: null });
expect(result.issues.map((issue) => issue.number)).toEqual([11, 12, 21, 22, 99]);
expect(issuePage).toBe(3);
});
Expand All @@ -337,7 +338,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
return jsonResponse(null, 404);
};

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, perPage: 1 });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, perPage: 1, loopoverAuth: null });
expect(result.issues.map((issue) => issue.number)).toEqual([7]);
});

Expand All @@ -350,7 +351,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.repo).toBeNull();
});

Expand All @@ -368,7 +369,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
return jsonResponse(null, 404);
};

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(requestedPaths).toEqual([
"https://raw.githubusercontent.com/acme/widgets/HEAD/.loopover.yml",
"https://raw.githubusercontent.com/acme/widgets/HEAD/.github/loopover.yml",
Expand All @@ -392,7 +393,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
return jsonResponse(null, 404);
};

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(manifestRequests).toBe(4);
expect(result.manifest.present).toBe(false);
expect(result.manifest.warnings).toEqual([]);
Expand All @@ -410,7 +411,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
return jsonResponse(null, 404);
};

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(cancelCount).toBe(4);
expect(result.manifest.present).toBe(false);
});
Expand All @@ -429,7 +430,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
return jsonResponse(null, 404);
};

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(requestedPaths).toHaveLength(1);
expect(result.manifest.gate?.duplicates).toBe("advisory");
});
Expand All @@ -448,7 +449,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
return jsonResponse(null, 404);
};

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.confirmedContributor).toBe(false);
expect(minersCalled).toBe(false);
});
Expand All @@ -461,7 +462,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"/repos/acme/widgets": () => jsonResponse(REPO_PAYLOAD),
"raw.githubusercontent.com": () => jsonResponse(null, 404),
});
expect((await fetchSelfReviewContext("acme/widgets", { contributorLogin: "Miner-Bot", fetchImpl: miss as never })).confirmedContributor).toBe(false);
expect((await fetchSelfReviewContext("acme/widgets", { contributorLogin: "Miner-Bot", fetchImpl: miss as never, loopoverAuth: null })).confirmedContributor).toBe(false);

const notOk = routedFetch({
"api.gittensor.io/miners": () => jsonResponse(null, 500),
Expand All @@ -470,7 +471,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"/repos/acme/widgets": () => jsonResponse(REPO_PAYLOAD),
"raw.githubusercontent.com": () => jsonResponse(null, 404),
});
expect((await fetchSelfReviewContext("acme/widgets", { contributorLogin: "miner-bot", fetchImpl: notOk as never })).confirmedContributor).toBe(false);
expect((await fetchSelfReviewContext("acme/widgets", { contributorLogin: "miner-bot", fetchImpl: notOk as never, loopoverAuth: null })).confirmedContributor).toBe(false);

const throwing = async (url: string) => {
if (url.includes("api.gittensor.io/miners")) throw new Error("network down");
Expand All @@ -480,7 +481,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
if (url.includes("raw.githubusercontent.com")) return jsonResponse(null, 404);
return jsonResponse(null, 404);
};
expect((await fetchSelfReviewContext("acme/widgets", { contributorLogin: "miner-bot", fetchImpl: throwing as never })).confirmedContributor).toBe(false);
expect((await fetchSelfReviewContext("acme/widgets", { contributorLogin: "miner-bot", fetchImpl: throwing as never, loopoverAuth: null })).confirmedContributor).toBe(false);
});

it("matches a confirmed contributor case-insensitively", async () => {
Expand All @@ -491,7 +492,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"/repos/acme/widgets": () => jsonResponse(REPO_PAYLOAD),
"raw.githubusercontent.com": () => jsonResponse(null, 404),
});
const result = await fetchSelfReviewContext("acme/widgets", { contributorLogin: "miner-bot", fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { contributorLogin: "miner-bot", fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.confirmedContributor).toBe(true);
});

Expand All @@ -510,7 +511,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.pullRequests[0]?.linkedIssues).toEqual([7]);
});

Expand All @@ -523,7 +524,7 @@ describe("fetchSelfReviewContext (#5145)", () => {
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(result.pullRequests.find((pr) => pr.number === 51)?.mergeableState).toBe("dirty");
expect(result.pullRequests.find((pr) => pr.number === 52)?.mergeableState).toBeNull();
});
Expand Down Expand Up @@ -563,7 +564,11 @@ describe("fetchSelfReviewContext (#5145)", () => {
if (url.includes("api.gittensor.io/miners")) return jsonResponse([]);
return jsonResponse(null, 404);
};
await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
// loopoverAuth: null forces the ORB live-gate-thresholds probe off (#6487) -- without it, a real
// loopover-mcp session recorded on disk (e.g. from `loopover-mcp login` on a contributor's machine)
// would fire an extra concurrent fetch whose "Bearer <session token>" header could race with and
// overwrite capturedAuth, since its URL also matches this test's own "/repos/acme/widgets" routing.
await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, loopoverAuth: null });
expect(capturedAuth).toBe("Bearer env-token");
} finally {
if (original === undefined) delete process.env.GITHUB_TOKEN;
Expand All @@ -584,7 +589,9 @@ describe("fetchSelfReviewContext (#5145)", () => {
return jsonResponse(null, 404);
};

await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, contributorLogin: "miner-bot" });
// loopoverAuth: null keeps this test's call count and 10s-default assertion honest -- see the note on
// the "defaults GITHUB_TOKEN" test above for why an unforced probe is a real-environment hazard here too.
await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never, contributorLogin: "miner-bot", loopoverAuth: null });

// repo + issues + pulls (githubGetJson) + 4 manifest candidates + 1 contributor lookup = 8 calls, every one bounded.
expect(capturedSignals.length).toBeGreaterThanOrEqual(7);
Expand Down