Skip to content
Closed
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
224 changes: 224 additions & 0 deletions test/unit/find-opportunities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ import {
validateFindOpportunitiesInput,
} from "../../src/mcp/find-opportunities";
import { upsertRepositoryFromGitHub } from "../../src/db/repositories";
import { createInstallationToken } from "../../src/github/app";
import { createTestEnv } from "../helpers/d1";

vi.mock("@loopover/engine", async () => {
return import("../../packages/loopover-engine/src/index");
});

vi.mock("../../src/github/app", async (importOriginal) => ({
...(await importOriginal<typeof import("../../src/github/app")>()),
createInstallationToken: vi.fn(async () => "unused-token"),
}));

const fixtureDir = join(dirname(fileURLToPath(import.meta.url)), "../fixtures/ai-policy");

function readFixture(name: string): string {
Expand Down Expand Up @@ -57,6 +63,8 @@ const issue = (number: number) => ({

afterEach(() => {
vi.unstubAllGlobals();
vi.mocked(createInstallationToken).mockReset();
vi.mocked(createInstallationToken).mockImplementation(async () => "unused-token");
});

describe("validateFindOpportunitiesInput", () => {
Expand Down Expand Up @@ -275,4 +283,220 @@ describe("runFindOpportunities", () => {
expect(allowed.status).toBe("ok");
expect(allowed.ranked).toHaveLength(1);
});

it("returns invalid_request end-to-end when neither targets nor searchQuery are provided", async () => {
const env = createTestEnv();
const result = await runFindOpportunities(env, {});
expect(result).toEqual({
status: "invalid_request",
ranked: [],
totalCandidates: 0,
reason: "targets_or_search_query_required",
});
});

it("resolves issues via the searchQuery path instead of targets", async () => {
const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "test-token" });
const allowedPolicy = readFixture("allowed-silent.md");
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/search/issues?")) {
return jsonResponse({ items: [{ ...issue(21), repository: { full_name: "acme/searched" } }] });
}
if (url.includes("/repos/acme/searched/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/searched/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
return jsonResponse({}, { status: 404 });
});

const result = await runFindOpportunities(env, { searchQuery: "improve docs" });

expect(result.status).toBe("ok");
expect(result.ranked.map((entry) => `${entry.owner}/${entry.repo}#${entry.issueNumber}`)).toEqual(["acme/searched#21"]);
});

it("re-filters searchQuery results through canAccessRepo after retrieval", async () => {
const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "test-token" });
const allowedPolicy = readFixture("allowed-silent.md");
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/search/issues?")) {
return jsonResponse({
items: [
{ ...issue(22), repository: { full_name: "acme/searched" } },
{ ...issue(23), repository: { full_name: "acme/other" } },
],
});
}
if (url.includes("/repos/acme/searched/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/searched/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
if (url.includes("/repos/acme/other/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/other/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
return jsonResponse({}, { status: 404 });
});

const result = await runFindOpportunities(
env,
{ searchQuery: "improve docs" },
{ canAccessRepo: async (repoFullName) => repoFullName === "acme/searched" },
);

expect(result.status).toBe("ok");
expect(result.ranked.map((entry) => `${entry.owner}/${entry.repo}`)).toEqual(["acme/searched"]);
});

it("narrows lane fit and reports appliedLane when goalSpec.lane is set", async () => {
const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "test-token" });
const allowedPolicy = readFixture("allowed-silent.md");
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/repos/acme/allowed/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/allowed/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
if (url.includes("/repos/acme/allowed/issues?")) return jsonResponse([issue(9)]);
return jsonResponse({}, { status: 404 });
});

const unscoped = await runFindOpportunities(env, { targets: [{ owner: "acme", repo: "allowed" }] });
const scoped = await runFindOpportunities(env, {
targets: [{ owner: "acme", repo: "allowed" }],
goalSpec: { lane: "documentation" },
});

expect(unscoped.appliedLane).toBeUndefined();
expect(scoped.appliedLane).toBe("documentation");
expect(scoped.ranked[0]?.rankScore).toBeLessThan(unscoped.ranked[0]?.rankScore ?? 0);
});

it("builds wantedPaths globs from goalSpec.languages when no lane is set", async () => {
const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "test-token" });
const allowedPolicy = readFixture("allowed-silent.md");
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/repos/acme/allowed/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/allowed/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
if (url.includes("/repos/acme/allowed/issues?")) return jsonResponse([issue(11)]);
return jsonResponse({}, { status: 404 });
});

const result = await runFindOpportunities(env, {
targets: [{ owner: "acme", repo: "allowed" }],
goalSpec: { languages: ["typescript"] },
});

expect(result.status).toBe("ok");
expect(result.appliedLane).toBeUndefined();
expect(result.ranked).toHaveLength(1);
});

it("reports appliedMinRankScore when set, and omits it (and appliedLane) when neither is set", async () => {
const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "test-token" });
const allowedPolicy = readFixture("allowed-silent.md");
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/repos/acme/allowed/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/allowed/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
if (url.includes("/repos/acme/allowed/issues?")) return jsonResponse([issue(13)]);
return jsonResponse({}, { status: 404 });
});

const withMinScore = await runFindOpportunities(env, {
targets: [{ owner: "acme", repo: "allowed" }],
goalSpec: { minRankScore: 10 },
});
expect(withMinScore.appliedMinRankScore).toBe(10);
expect(withMinScore.appliedLane).toBeUndefined();

const bare = await runFindOpportunities(env, { targets: [{ owner: "acme", repo: "allowed" }] });
expect(bare.appliedMinRankScore).toBeUndefined();
expect(bare.appliedLane).toBeUndefined();
});

it("skips repos without an installation, falls through a failing token mint, and uses the first token that resolves", async () => {
const env = createTestEnv();
await upsertRepositoryFromGitHub(env, { name: "broken-install", full_name: "acme/broken-install" }, 111);
await upsertRepositoryFromGitHub(env, { name: "good-install", full_name: "acme/good-install" }, 222);
const bannedPolicy = readFixture("banned-ai-usage.md");
const allowedPolicy = readFixture("allowed-silent.md");
vi.mocked(createInstallationToken).mockImplementation(async (_env, installationId) => {
if (installationId === 111) throw new Error("mint failed");
return "good-token";
});
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/repos/acme/no-install/contents/AI-USAGE.md")) return contentResponse(bannedPolicy);
if (url.includes("/repos/acme/broken-install/contents/AI-USAGE.md")) return contentResponse(bannedPolicy);
if (url.includes("/repos/acme/good-install/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/good-install/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
if (url.includes("/repos/acme/good-install/issues?")) return jsonResponse([issue(31)]);
return jsonResponse({}, { status: 404 });
});

const result = await runFindOpportunities(env, {
targets: [
{ owner: "acme", repo: "no-install" },
{ owner: "acme", repo: "broken-install" },
{ owner: "acme", repo: "good-install" },
],
});

expect(result.status).toBe("ok");
expect(result.ranked.map((entry) => `${entry.owner}/${entry.repo}#${entry.issueNumber}`)).toEqual(["acme/good-install#31"]);
expect(vi.mocked(createInstallationToken)).toHaveBeenCalledWith(env, 111);
expect(vi.mocked(createInstallationToken)).toHaveBeenCalledWith(env, 222);
});

it("continues with a null token when a target has a repo record but no installation to mint against", async () => {
const env = createTestEnv();
await upsertRepositoryFromGitHub(env, { name: "no-token-install", full_name: "acme/no-token-install" });
const allowedPolicy = readFixture("allowed-silent.md");
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/repos/acme/no-token-install/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/no-token-install/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
if (url.includes("/repos/acme/no-token-install/issues?")) return jsonResponse([issue(41)]);
return jsonResponse({}, { status: 404 });
});

const result = await runFindOpportunities(env, { targets: [{ owner: "acme", repo: "no-token-install" }] });

expect(result.status).toBe("ok");
expect(result.ranked.map((entry) => `${entry.owner}/${entry.repo}#${entry.issueNumber}`)).toEqual(["acme/no-token-install#41"]);
expect(vi.mocked(createInstallationToken)).not.toHaveBeenCalled();
});

it("continues with a null token on the searchQuery path when no public token or targets are configured", async () => {
const env = createTestEnv();
const allowedPolicy = readFixture("allowed-silent.md");
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/search/issues?")) {
return jsonResponse({ items: [{ ...issue(42), repository: { full_name: "acme/searched" } }] });
}
if (url.includes("/repos/acme/searched/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/searched/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
return jsonResponse({}, { status: 404 });
});

const result = await runFindOpportunities(env, { searchQuery: "improve docs" });

expect(result.status).toBe("ok");
expect(result.ranked.map((entry) => `${entry.owner}/${entry.repo}#${entry.issueNumber}`)).toEqual(["acme/searched#42"]);
});

it("surfaces fetch warnings in the result when GitHub errors on an allowed repo's issues", async () => {
const env = createTestEnv({ GITHUB_PUBLIC_TOKEN: "test-token" });
const allowedPolicy = readFixture("allowed-silent.md");
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/repos/acme/allowed/contents/AI-USAGE.md")) return jsonResponse({}, { status: 404 });
if (url.includes("/repos/acme/allowed/contents/CONTRIBUTING.md")) return contentResponse(allowedPolicy);
if (url.includes("/repos/acme/allowed/issues?")) return jsonResponse({}, { status: 500 });
return jsonResponse({}, { status: 404 });
});

const result = await runFindOpportunities(env, { targets: [{ owner: "acme", repo: "allowed" }] });

expect(result.status).toBe("ok");
expect(result.ranked).toEqual([]);
expect(result.warnings).toEqual([{ repoFullName: "acme/allowed", stage: "issues", message: "GitHub returned 500" }]);
});
});
Loading