fix(prd-7): M4 test coverage review — 28 new tests for comment operations and branch setup
- test/create-initial-comment.test.ts: 15 tests covering GitHub/Forgejo platform branches, sticky comment matching, review comment fallback, error handling with GITHUB_OUTPUT verification - test/update-tracking-comment.test.ts: 11 tests covering body construction, branch link logic (issue vs PR), comment type routing, Forgejo URLs - test/setup-branch-forgejo.test.ts: 2 tests verifying Forgejo uses repos.getBranch() while GitHub uses git.getRef() for source branch SHA - test/COVERAGE-NOTES.md: documents all coverage status and accepted gaps (tag mode orchestrator, entrypoint) with rationale - Notable finding: CLAUDE_APP_BOT_ID discrepancy flagged for M5 Progress: 67% complete (4/6 milestones) — M5 code quality next Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
6b0302d226
commit
bbfd630fc5
5 files changed
+1006
-1
No files matched your search
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import {
|
||||
describe,
|
||||
test,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
spyOn,
|
||||
type Mock,
|
||||
} from "bun:test";
|
||||
import { updateTrackingComment } from "../src/github/operations/comments/update-with-branch";
|
||||
import { _resetPlatformCache } from "../src/platform/detector";
|
||||
import { createMockContext } from "./mockContext";
|
||||
import type { Octokits } from "../src/github/api/client";
|
||||
|
||||
function createMockOctokits(overrides: Record<string, any> = {}): Octokits {
|
||||
// updateTrackingComment calls updateClaudeComment(octokit.rest, ...),
|
||||
// which accesses octokit.rest.issues.updateComment / octokit.rest.pulls.updateReviewComment
|
||||
return {
|
||||
rest: {
|
||||
rest: {
|
||||
issues: {
|
||||
updateComment:
|
||||
overrides.updateComment ??
|
||||
(async () => ({ data: { id: 1, html_url: "", updated_at: "" } })),
|
||||
},
|
||||
pulls: {
|
||||
updateReviewComment:
|
||||
overrides.updateReviewComment ??
|
||||
(async () => ({ data: { id: 1, html_url: "", updated_at: "" } })),
|
||||
},
|
||||
},
|
||||
},
|
||||
graphql: {} as any,
|
||||
} as unknown as Octokits;
|
||||
}
|
||||
|
||||
describe("updateTrackingComment", () => {
|
||||
let savedPlatform: string | undefined;
|
||||
let savedServerUrl: string | undefined;
|
||||
let savedRunNumber: string | undefined;
|
||||
let savedRunId: string | undefined;
|
||||
let consoleLogSpy: Mock<(...args: any[]) => void>;
|
||||
let consoleErrorSpy: Mock<(...args: any[]) => void>;
|
||||
|
||||
beforeEach(() => {
|
||||
_resetPlatformCache();
|
||||
savedPlatform = process.env.PLATFORM;
|
||||
savedServerUrl = process.env.GITHUB_SERVER_URL;
|
||||
savedRunNumber = process.env.GITHUB_RUN_NUMBER;
|
||||
savedRunId = process.env.GITHUB_RUN_ID;
|
||||
|
||||
// Default to GitHub platform
|
||||
delete process.env.PLATFORM;
|
||||
process.env.GITHUB_SERVER_URL = "https://github.com";
|
||||
|
||||
consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
|
||||
consoleErrorSpy = spyOn(console, "error").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (savedPlatform !== undefined) process.env.PLATFORM = savedPlatform;
|
||||
else delete process.env.PLATFORM;
|
||||
if (savedServerUrl !== undefined)
|
||||
process.env.GITHUB_SERVER_URL = savedServerUrl;
|
||||
else delete process.env.GITHUB_SERVER_URL;
|
||||
if (savedRunNumber !== undefined)
|
||||
process.env.GITHUB_RUN_NUMBER = savedRunNumber;
|
||||
else delete process.env.GITHUB_RUN_NUMBER;
|
||||
if (savedRunId !== undefined) process.env.GITHUB_RUN_ID = savedRunId;
|
||||
else delete process.env.GITHUB_RUN_ID;
|
||||
|
||||
consoleLogSpy.mockRestore();
|
||||
consoleErrorSpy.mockRestore();
|
||||
_resetPlatformCache();
|
||||
});
|
||||
|
||||
describe("GitHub platform — body construction", () => {
|
||||
test("includes GitHub job run link using context.runId", async () => {
|
||||
let capturedBody = "";
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async (args: any) => {
|
||||
capturedBody = args.body;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext({ runId: "55555" });
|
||||
await updateTrackingComment(mockOctokits, context, 1);
|
||||
|
||||
expect(capturedBody).toContain("actions/runs/55555");
|
||||
expect(capturedBody).toContain("https://github.com/test-owner/test-repo");
|
||||
});
|
||||
|
||||
test("includes branch link for issue context", async () => {
|
||||
let capturedBody = "";
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async (args: any) => {
|
||||
capturedBody = args.body;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext({ isPR: false });
|
||||
await updateTrackingComment(mockOctokits, context, 1, "claude/fix-123");
|
||||
|
||||
expect(capturedBody).toContain("View branch");
|
||||
expect(capturedBody).toContain("tree/claude/fix-123");
|
||||
});
|
||||
|
||||
test("omits branch link for PR context even when branch provided", async () => {
|
||||
let capturedBody = "";
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async (args: any) => {
|
||||
capturedBody = args.body;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext({ isPR: true });
|
||||
await updateTrackingComment(mockOctokits, context, 1, "claude/fix-123");
|
||||
|
||||
expect(capturedBody).not.toContain("View branch");
|
||||
expect(capturedBody).not.toContain("tree/");
|
||||
});
|
||||
|
||||
test("omits branch link when branch is undefined", async () => {
|
||||
let capturedBody = "";
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async (args: any) => {
|
||||
capturedBody = args.body;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext({ isPR: false });
|
||||
await updateTrackingComment(mockOctokits, context, 1);
|
||||
|
||||
expect(capturedBody).not.toContain("View branch");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Forgejo platform — body construction", () => {
|
||||
beforeEach(() => {
|
||||
_resetPlatformCache();
|
||||
process.env.PLATFORM = "forgejo";
|
||||
process.env.GITHUB_SERVER_URL = "https://git.example.com";
|
||||
process.env.GITHUB_RUN_NUMBER = "77";
|
||||
});
|
||||
|
||||
test("uses Forgejo job run link with GITHUB_RUN_NUMBER", async () => {
|
||||
let capturedBody = "";
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async (args: any) => {
|
||||
capturedBody = args.body;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext();
|
||||
await updateTrackingComment(mockOctokits, context, 1);
|
||||
|
||||
expect(capturedBody).toContain(
|
||||
"https://git.example.com/test-owner/test-repo/actions/runs/77",
|
||||
);
|
||||
});
|
||||
|
||||
test("uses Forgejo branch link for issues", async () => {
|
||||
let capturedBody = "";
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async (args: any) => {
|
||||
capturedBody = args.body;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext({ isPR: false });
|
||||
await updateTrackingComment(mockOctokits, context, 1, "claude/fix-42");
|
||||
|
||||
expect(capturedBody).toContain(
|
||||
"https://git.example.com/test-owner/test-repo/tree/claude/fix-42",
|
||||
);
|
||||
});
|
||||
|
||||
test("uses Forgejo comment body with Unicode spinner", async () => {
|
||||
let capturedBody = "";
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async (args: any) => {
|
||||
capturedBody = args.body;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext();
|
||||
await updateTrackingComment(mockOctokits, context, 1);
|
||||
|
||||
expect(capturedBody).toContain("⏳");
|
||||
expect(capturedBody).not.toContain("user-attachments/assets");
|
||||
});
|
||||
});
|
||||
|
||||
describe("comment type routing", () => {
|
||||
test("uses issue comment update for non-review events", async () => {
|
||||
let issueUpdateCalled = false;
|
||||
let reviewUpdateCalled = false;
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async () => {
|
||||
issueUpdateCalled = true;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
updateReviewComment: async () => {
|
||||
reviewUpdateCalled = true;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext({ eventName: "issue_comment" });
|
||||
await updateTrackingComment(mockOctokits, context, 1);
|
||||
|
||||
expect(issueUpdateCalled).toBe(true);
|
||||
expect(reviewUpdateCalled).toBe(false);
|
||||
});
|
||||
|
||||
test("uses PR review comment update for review comment events", async () => {
|
||||
let issueUpdateCalled = false;
|
||||
let reviewUpdateCalled = false;
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async () => {
|
||||
issueUpdateCalled = true;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
updateReviewComment: async () => {
|
||||
reviewUpdateCalled = true;
|
||||
return { data: { id: 1, html_url: "", updated_at: "" } };
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext({
|
||||
eventName: "pull_request_review_comment",
|
||||
isPR: true,
|
||||
});
|
||||
await updateTrackingComment(mockOctokits, context, 1);
|
||||
|
||||
expect(reviewUpdateCalled).toBe(true);
|
||||
expect(issueUpdateCalled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
test("throws and logs error when update fails", async () => {
|
||||
const mockOctokits = createMockOctokits({
|
||||
updateComment: async () => {
|
||||
throw new Error("Update failed");
|
||||
},
|
||||
});
|
||||
|
||||
const context = createMockContext();
|
||||
await expect(
|
||||
updateTrackingComment(mockOctokits, context, 1),
|
||||
).rejects.toThrow("Update failed");
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("logs correct comment type label on success", async () => {
|
||||
const mockOctokits = createMockOctokits();
|
||||
|
||||
const issueContext = createMockContext({ eventName: "issue_comment" });
|
||||
await updateTrackingComment(mockOctokits, issueContext, 42);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("issue"),
|
||||
);
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("42"));
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user