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
56 changes: 38 additions & 18 deletions src/__tests__/fixtures/mock-deps.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
import { jest } from "@jest/globals";
import { Dependencies, OctokitClient, Logger } from "../../types/Dependencies.js";
import {
Dependencies,
OctokitClient,
Logger,
} from "../../types/Dependencies.js";


export function createMockLogger(): Logger & { [K in keyof Logger]: jest.Mock } {
return {
info: jest.fn<any>(),
error: jest.fn<any>(),
warning: jest.fn<any>(),
debug: jest.fn<any>(),
};
}
export function createMockLogger(): Logger & {
[K in keyof Logger]: jest.Mock;
} {
return {
info: jest.fn<any>(),
error: jest.fn<any>(),
warning: jest.fn<any>(),
debug: jest.fn<any>(),
};
}

// creates a mock OctokitClient with sensible defaults
export function createMockOctokit(overrides: Partial<DeepPartial<OctokitClient>> = {}): OctokitClient {
export function createMockOctokit(
overrides: Partial<DeepPartial<OctokitClient>> = {},
): OctokitClient {
return {
rest: {
repos: {
get: overrides.rest?.repos?.get as OctokitClient["rest"]["repos"]["get"] ??
get:
(overrides.rest?.repos
?.get as OctokitClient["rest"]["repos"]["get"]) ??
jest.fn<any>().mockResolvedValue({
data: {
name: "test-repo",
Expand All @@ -28,31 +37,42 @@ export function createMockOctokit(overrides: Partial<DeepPartial<OctokitClient>>
created_at: "2024-01-01T00:00:00Z",
updated_at: "2024-06-01T00:00:00Z",
default_branch: "main",
fork: false,
parent: null,
},
}),
listLanguages: overrides.rest?.repos?.listLanguages as OctokitClient["rest"]["repos"]["listLanguages"] ??
listLanguages:
(overrides.rest?.repos
?.listLanguages as OctokitClient["rest"]["repos"]["listLanguages"]) ??
jest.fn<any>().mockResolvedValue({
data: { TypeScript: 5000, JavaScript: 2000 },
}),
getContent: overrides.rest?.repos?.getContent as OctokitClient["rest"]["repos"]["getContent"] ??
getContent:
(overrides.rest?.repos
?.getContent as OctokitClient["rest"]["repos"]["getContent"]) ??
jest.fn<any>().mockResolvedValue({
data: { sha: "abc123" },
}),
createOrUpdateFileContents: overrides.rest?.repos?.createOrUpdateFileContents as OctokitClient["rest"]["repos"]["createOrUpdateFileContents"] ??
createOrUpdateFileContents:
(overrides.rest?.repos
?.createOrUpdateFileContents as OctokitClient["rest"]["repos"]["createOrUpdateFileContents"]) ??
jest.fn<any>().mockResolvedValue({
data: { commit: { sha: "def456" } },
}),
},
},
createPullRequest: overrides.createPullRequest as OctokitClient["createPullRequest"] ??
createPullRequest:
(overrides.createPullRequest as OctokitClient["createPullRequest"]) ??
jest.fn<any>().mockResolvedValue({
data: { html_url: "https://github.com/test-owner/test-repo/pull/1" },
}),
};
}

// creates a full mock Dependencies object with sensible defaults
export function createMockDeps(overrides: Partial<Dependencies> = {}): Dependencies {
export function createMockDeps(
overrides: Partial<Dependencies> = {},
): Dependencies {
const mockOctokit = createMockOctokit();

return {
Expand Down Expand Up @@ -84,4 +104,4 @@ export function createMockDeps(overrides: Partial<Dependencies> = {}): Dependenc
// helper type for deep partial overrides
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
};
70 changes: 66 additions & 4 deletions src/__tests__/unit/helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ describe("createHelpers - calculateMetaData", () => {

expect(result.name).toBe("test-repo");
expect(result.description).toBe("A test repository");
expect(result.repositoryURL).toBe("https://github.com/test-owner/test-repo");
expect(result.repositoryURL).toBe(
"https://github.com/test-owner/test-repo",
);
expect(result.repositoryVisibility).toBe("public");
expect(result.languages).toEqual(["TypeScript", "JavaScript"]);
expect(result.laborHours).toBeGreaterThan(0);
Expand Down Expand Up @@ -193,7 +195,9 @@ describe("createHelpers - pushDirectlyWithFallback", () => {

await helpers.pushDirectlyWithFallback({ name: "test" } as any, "main");

expect(adminOctokit.rest.repos.createOrUpdateFileContents).toHaveBeenCalled();
expect(
adminOctokit.rest.repos.createOrUpdateFileContents,
).toHaveBeenCalled();
expect(deps.setOutput).toHaveBeenCalledWith("method_used", "direct_push");
});
});
Expand All @@ -215,7 +219,11 @@ describe("createHelpers - validateOnly", () => {
it("succeeds for valid code.json", async () => {
const validCodeJSON = await import("../fixtures/test-code.json");
const deps = createMockDeps({
readFile: jest.fn<any>().mockResolvedValue(JSON.stringify(validCodeJSON.default ?? validCodeJSON)),
readFile: jest
.fn<any>()
.mockResolvedValue(
JSON.stringify(validCodeJSON.default ?? validCodeJSON),
),
});
const helpers = createHelpers(deps);

Expand Down Expand Up @@ -383,6 +391,60 @@ describe("createHelpers - detectReusedCode", () => {
});
});

describe("createHelpers - detectForkParent", () => {
function mockRepoGet(data: Record<string, unknown>): Dependencies {
return createMockDeps({
octokit: createMockOctokit({
rest: {
repos: {
get: jest.fn<any>().mockResolvedValue({ data }),
},
},
}),
});
}

it("returns the upstream parent when the repo is a fork", async () => {
const deps = mockRepoGet({
fork: true,
parent: {
full_name: "upstream-owner/upstream-repo",
html_url: "https://github.com/upstream-owner/upstream-repo",
},
});

expect(await createHelpers(deps).detectForkParent()).toEqual({
name: "upstream-owner/upstream-repo",
URL: "https://github.com/upstream-owner/upstream-repo",
});
});

it("returns null when the repo is not a fork", async () => {
const deps = mockRepoGet({ fork: false, parent: null });
expect(await createHelpers(deps).detectForkParent()).toBeNull();
});

it("returns null when fork is true but parent is missing", async () => {
const deps = mockRepoGet({ fork: true });
expect(await createHelpers(deps).detectForkParent()).toBeNull();
});

it("returns null and logs when the API call fails", async () => {
const deps = createMockDeps({
octokit: createMockOctokit({
rest: {
repos: {
get: jest.fn<any>().mockRejectedValue(new Error("API down")),
},
},
}),
});

expect(await createHelpers(deps).detectForkParent()).toBeNull();
expect(deps.log.error).toHaveBeenCalled();
});
});

describe("mergeReusedCode", () => {
it("appends detected entries to existing ones", () => {
const existing = [{ name: "Other Gov Tool", URL: "https://example.gov" }];
Expand Down Expand Up @@ -417,4 +479,4 @@ describe("GOV_DEPENDENCIES integrity", () => {
expect(entry.name.trim()).not.toBe("");
expect(entry.URL).toMatch(/^https:\/\//);
});
});
});
76 changes: 67 additions & 9 deletions src/__tests__/unit/main.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { describe, it, expect, jest, beforeEach, afterEach } from "@jest/globals";
import {
describe,
it,
expect,
jest,
beforeEach,
afterEach,
} from "@jest/globals";
import { runWithDeps, filterValidFields, getMetaData } from "../../main.js";
import { createHelpers } from "../../helper.js";
import { createMockDeps } from "../fixtures/mock-deps.js";
import { createMockDeps, createMockOctokit } from "../fixtures/mock-deps.js";
import validCodeJSON from "../fixtures/test-code.json";

describe("filterValidFields", () => {
it("keeps known fields", () => {
const result = filterValidFields({ name: "test", version: "1.0", description: "hi" });
const result = filterValidFields({
name: "test",
version: "1.0",
description: "hi",
});
expect(result).toHaveProperty("name", "test");
expect(result).toHaveProperty("version", "1.0");
});
Expand All @@ -23,10 +34,15 @@ describe("getMetaData", () => {
const deps = createMockDeps();
const helpers = createHelpers(deps);

const existing = { ...validCodeJSON, feedbackMechanism: "https://custom.example.com/feedback" } as any;
const existing = {
...validCodeJSON,
feedbackMechanism: "https://custom.example.com/feedback",
} as any;
const result = await getMetaData(helpers, deps, existing);

expect(result.feedbackMechanism).toBe("https://custom.example.com/feedback");
expect(result.feedbackMechanism).toBe(
"https://custom.example.com/feedback",
);
});

it("defaults feedbackMechanism to issues URL", async () => {
Expand Down Expand Up @@ -57,6 +73,42 @@ describe("getMetaData", () => {

expect(result.contractNumber).toEqual(["LEGACY-001"]);
});

it("adds the fork upstream to reusedCode", async () => {
const forkOctokit = createMockOctokit({
rest: {
repos: {
get: jest.fn<any>().mockResolvedValue({
data: {
name: "test-repo",
description: "A forked repository",
html_url: "https://github.com/test-owner/test-repo",
private: false,
forks_count: 0,
topics: [],
created_at: "2024-01-01T00:00:00Z",
updated_at: "2024-06-01T00:00:00Z",
default_branch: "main",
fork: true,
parent: {
full_name: "upstream-owner/upstream-repo",
html_url: "https://github.com/upstream-owner/upstream-repo",
},
},
}),
},
},
});
const deps = createMockDeps({ octokit: forkOctokit });
const helpers = createHelpers(deps);

const result = await getMetaData(helpers, deps, null);

expect(result.reusedCode).toContainEqual({
name: "upstream-owner/upstream-repo",
URL: "https://github.com/upstream-owner/upstream-repo",
});
});
});

describe("runWithDeps", () => {
Expand Down Expand Up @@ -106,9 +158,13 @@ describe("runWithDeps", () => {
const adminOctokit = {
rest: {
repos: {
get: jest.fn<any>().mockResolvedValue({ data: { default_branch: "main" } }),
get: jest
.fn<any>()
.mockResolvedValue({ data: { default_branch: "main" } }),
listLanguages: jest.fn<any>().mockResolvedValue({ data: {} }),
getContent: jest.fn<any>().mockResolvedValue({ data: { sha: "abc" } }),
getContent: jest
.fn<any>()
.mockResolvedValue({ data: { sha: "abc" } }),
createOrUpdateFileContents: jest.fn<any>().mockResolvedValue({
data: { commit: { sha: "pushed123" } },
}),
Expand All @@ -125,7 +181,9 @@ describe("runWithDeps", () => {

await runWithDeps(deps);

expect(adminOctokit.rest.repos.createOrUpdateFileContents).toHaveBeenCalled();
expect(
adminOctokit.rest.repos.createOrUpdateFileContents,
).toHaveBeenCalled();
expect(deps.setOutput).toHaveBeenCalledWith("method_used", "direct_push");
});

Expand Down Expand Up @@ -158,4 +216,4 @@ describe("runWithDeps", () => {
expect.stringContaining("Action failed"),
);
});
});
});
Loading
Loading