mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-08-03 01:38:30 +08:00
GitHub's GraphQL author field is null when the account behind a comment, review, PR, or issue has been deleted (the ghost user). The action typed author as non-null and read author.login directly, so a single comment from a deleted account threw and was swallowed into a generic 'Failed to fetch PR/issue data', failing the entire run. Make author nullable on the four affected types and fall back to 'ghost' at each login read. With the type nullable, tsc flags every dereference, so all sites are covered.
931 lines
26 KiB
TypeScript
931 lines
26 KiB
TypeScript
import { expect, test, describe } from "bun:test";
|
|
import {
|
|
formatContext,
|
|
formatBody,
|
|
formatComments,
|
|
formatReviewComments,
|
|
formatChangedFiles,
|
|
formatChangedFilesWithSHA,
|
|
} from "../src/github/data/formatter";
|
|
import type {
|
|
GitHubPullRequest,
|
|
GitHubIssue,
|
|
GitHubComment,
|
|
GitHubFile,
|
|
} from "../src/github/types";
|
|
import type { GitHubFileWithSHA } from "../src/github/data/fetcher";
|
|
|
|
describe("formatContext", () => {
|
|
test("formats PR context correctly", () => {
|
|
const prData: GitHubPullRequest = {
|
|
title: "Test PR",
|
|
body: "PR body",
|
|
author: { login: "test-user" },
|
|
baseRefName: "main",
|
|
headRefName: "feature/test",
|
|
headRefOid: "abc123",
|
|
isCrossRepository: false,
|
|
headRepository: { owner: { login: "testowner" }, name: "testrepo" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
additions: 50,
|
|
deletions: 30,
|
|
state: "OPEN",
|
|
labels: {
|
|
nodes: [],
|
|
},
|
|
commits: {
|
|
totalCount: 3,
|
|
nodes: [],
|
|
},
|
|
files: {
|
|
nodes: [{} as GitHubFile, {} as GitHubFile],
|
|
},
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
reviews: {
|
|
nodes: [],
|
|
},
|
|
};
|
|
|
|
const result = formatContext(prData, true);
|
|
expect(result).toBe(
|
|
`PR Title: Test PR
|
|
PR Author: test-user
|
|
PR Branch: feature/test -> main
|
|
PR State: OPEN
|
|
PR Labels: none
|
|
PR Additions: 50
|
|
PR Deletions: 30
|
|
Total Commits: 3
|
|
Changed Files: 2 files`,
|
|
);
|
|
});
|
|
|
|
test("formats PR context with labels", () => {
|
|
const prData: GitHubPullRequest = {
|
|
title: "Test PR",
|
|
body: "PR body",
|
|
author: { login: "test-user" },
|
|
baseRefName: "main",
|
|
headRefName: "feature/test",
|
|
headRefOid: "abc123",
|
|
isCrossRepository: false,
|
|
headRepository: { owner: { login: "testowner" }, name: "testrepo" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
additions: 50,
|
|
deletions: 30,
|
|
state: "OPEN",
|
|
labels: {
|
|
nodes: [{ name: "bug" }, { name: "enhancement" }],
|
|
},
|
|
commits: {
|
|
totalCount: 3,
|
|
nodes: [],
|
|
},
|
|
files: {
|
|
nodes: [{} as GitHubFile, {} as GitHubFile],
|
|
},
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
reviews: {
|
|
nodes: [],
|
|
},
|
|
};
|
|
|
|
const result = formatContext(prData, true);
|
|
expect(result).toBe(
|
|
`PR Title: Test PR
|
|
PR Author: test-user
|
|
PR Branch: feature/test -> main
|
|
PR State: OPEN
|
|
PR Labels: bug, enhancement
|
|
PR Additions: 50
|
|
PR Deletions: 30
|
|
Total Commits: 3
|
|
Changed Files: 2 files`,
|
|
);
|
|
});
|
|
|
|
test("formats Issue context correctly", () => {
|
|
const issueData: GitHubIssue = {
|
|
title: "Test Issue",
|
|
body: "Issue body",
|
|
author: { login: "test-user" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
state: "OPEN",
|
|
labels: {
|
|
nodes: [],
|
|
},
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
};
|
|
|
|
const result = formatContext(issueData, false);
|
|
expect(result).toBe(
|
|
`Issue Title: Test Issue
|
|
Issue Author: test-user
|
|
Issue State: OPEN
|
|
Issue Labels: none`,
|
|
);
|
|
});
|
|
|
|
test("formats Issue context with labels", () => {
|
|
const issueData: GitHubIssue = {
|
|
title: "Test Issue",
|
|
body: "Issue body",
|
|
author: { login: "test-user" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
state: "OPEN",
|
|
labels: {
|
|
nodes: [
|
|
{ name: "architecture" },
|
|
{ name: "agent-sdk" },
|
|
{ name: "drift:functional" },
|
|
],
|
|
},
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
};
|
|
|
|
const result = formatContext(issueData, false);
|
|
expect(result).toBe(
|
|
`Issue Title: Test Issue
|
|
Issue Author: test-user
|
|
Issue State: OPEN
|
|
Issue Labels: architecture, agent-sdk, drift:functional`,
|
|
);
|
|
});
|
|
|
|
test("renders a deleted (null-author) issue author as 'ghost'", () => {
|
|
const issueData: GitHubIssue = {
|
|
title: "Test Issue",
|
|
body: "Issue body",
|
|
author: null,
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
state: "OPEN",
|
|
labels: { nodes: [] },
|
|
comments: { nodes: [] },
|
|
};
|
|
|
|
const result = formatContext(issueData, false);
|
|
expect(result).toContain("Issue Author: ghost");
|
|
});
|
|
});
|
|
|
|
describe("formatBody", () => {
|
|
test("replaces image URLs with local paths", () => {
|
|
const body = `Here is some text with an image: 
|
|
|
|
And another one: 
|
|
|
|
Some more text.`;
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/test-image.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/another-image.jpg",
|
|
"/tmp/github-images/image-1234-1.jpg",
|
|
],
|
|
]);
|
|
|
|
const result = formatBody(body, imageUrlMap);
|
|
expect(result)
|
|
.toBe(`Here is some text with an image: 
|
|
|
|
And another one: 
|
|
|
|
Some more text.`);
|
|
});
|
|
|
|
test("handles empty image map", () => {
|
|
const body = "No images here";
|
|
const imageUrlMap = new Map<string, string>();
|
|
|
|
const result = formatBody(body, imageUrlMap);
|
|
expect(result).toBe("No images here");
|
|
});
|
|
|
|
test("preserves body when no images match", () => {
|
|
const body = "";
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/different.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatBody(body, imageUrlMap);
|
|
expect(result).toBe("");
|
|
});
|
|
|
|
test("handles multiple occurrences of same image", () => {
|
|
const body = `First: 
|
|
Second: `;
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/test.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatBody(body, imageUrlMap);
|
|
expect(result).toBe(`First: 
|
|
Second: `);
|
|
});
|
|
});
|
|
|
|
describe("formatComments", () => {
|
|
test("formats comments correctly", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "First comment",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
},
|
|
{
|
|
id: "2",
|
|
databaseId: "100002",
|
|
body: "Second comment",
|
|
author: { login: "user2" },
|
|
createdAt: "2023-01-02T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const result = formatComments(comments);
|
|
expect(result).toBe(
|
|
`[user1 at 2023-01-01T00:00:00Z]: First comment\n\n[user2 at 2023-01-02T00:00:00Z]: Second comment`,
|
|
);
|
|
});
|
|
|
|
test("renders deleted (null-author) comments as 'ghost'", () => {
|
|
// GitHub returns author: null for comments from deleted accounts.
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "From a deleted account",
|
|
author: null,
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const result = formatComments(comments);
|
|
expect(result).toBe(
|
|
"[ghost at 2023-01-01T00:00:00Z]: From a deleted account",
|
|
);
|
|
});
|
|
|
|
test("returns empty string for empty comments array", () => {
|
|
const result = formatComments([]);
|
|
expect(result).toBe("");
|
|
});
|
|
|
|
test("replaces image URLs in comments", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "Check out this screenshot: ",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
},
|
|
{
|
|
id: "2",
|
|
databaseId: "100002",
|
|
body: "Here's another image: ",
|
|
author: { login: "user2" },
|
|
createdAt: "2023-01-02T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/screenshot.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/bug-report.jpg",
|
|
"/tmp/github-images/image-1234-1.jpg",
|
|
],
|
|
]);
|
|
|
|
const result = formatComments(comments, imageUrlMap);
|
|
expect(result).toBe(
|
|
`[user1 at 2023-01-01T00:00:00Z]: Check out this screenshot: \n\n[user2 at 2023-01-02T00:00:00Z]: Here's another image: `,
|
|
);
|
|
});
|
|
|
|
test("handles comments with multiple images", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "Two images:  and ",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/first.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/second.png",
|
|
"/tmp/github-images/image-1234-1.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatComments(comments, imageUrlMap);
|
|
expect(result).toBe(
|
|
`[user1 at 2023-01-01T00:00:00Z]: Two images:  and `,
|
|
);
|
|
});
|
|
|
|
test("preserves comments when imageUrlMap is undefined", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "Image: ",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const result = formatComments(comments);
|
|
expect(result).toBe(
|
|
`[user1 at 2023-01-01T00:00:00Z]: Image: `,
|
|
);
|
|
});
|
|
|
|
test("filters out minimized comments", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "Normal comment",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
isMinimized: false,
|
|
},
|
|
{
|
|
id: "2",
|
|
databaseId: "100002",
|
|
body: "Minimized comment",
|
|
author: { login: "user2" },
|
|
createdAt: "2023-01-02T00:00:00Z",
|
|
isMinimized: true,
|
|
},
|
|
{
|
|
id: "3",
|
|
databaseId: "100003",
|
|
body: "Another normal comment",
|
|
author: { login: "user3" },
|
|
createdAt: "2023-01-03T00:00:00Z",
|
|
},
|
|
];
|
|
|
|
const result = formatComments(comments);
|
|
expect(result).toBe(
|
|
`[user1 at 2023-01-01T00:00:00Z]: Normal comment\n\n[user3 at 2023-01-03T00:00:00Z]: Another normal comment`,
|
|
);
|
|
});
|
|
|
|
test("returns empty string when all comments are minimized", () => {
|
|
const comments: GitHubComment[] = [
|
|
{
|
|
id: "1",
|
|
databaseId: "100001",
|
|
body: "Minimized comment 1",
|
|
author: { login: "user1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
isMinimized: true,
|
|
},
|
|
{
|
|
id: "2",
|
|
databaseId: "100002",
|
|
body: "Minimized comment 2",
|
|
author: { login: "user2" },
|
|
createdAt: "2023-01-02T00:00:00Z",
|
|
isMinimized: true,
|
|
},
|
|
];
|
|
|
|
const result = formatComments(comments);
|
|
expect(result).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("formatReviewComments", () => {
|
|
test("formats review with body and comments correctly", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "This is a great PR! LGTM.",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Nice implementation",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/index.ts",
|
|
line: 42,
|
|
},
|
|
{
|
|
id: "comment2",
|
|
databaseId: "200002",
|
|
body: "Consider adding error handling",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/utils.ts",
|
|
line: null,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nThis is a great PR! LGTM.\n [Comment on src/index.ts:42]: Nice implementation\n [Comment on src/utils.ts:?]: Consider adding error handling`,
|
|
);
|
|
});
|
|
|
|
test("formats review with only body (no comments) correctly", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300002",
|
|
author: { login: "reviewer1" },
|
|
body: "Looks good to me!",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nLooks good to me!`,
|
|
);
|
|
});
|
|
|
|
test("formats review without body correctly", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300003",
|
|
author: { login: "reviewer1" },
|
|
body: "",
|
|
state: "COMMENTED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200003",
|
|
body: "Small suggestion here",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/main.ts",
|
|
line: 15,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: COMMENTED\n [Comment on src/main.ts:15]: Small suggestion here`,
|
|
);
|
|
});
|
|
|
|
test("renders deleted (null-author) reviews as 'ghost'", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300099",
|
|
author: null,
|
|
body: "Left before deleting the account",
|
|
state: "COMMENTED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by ghost at 2023-01-01T00:00:00Z]: COMMENTED\nLeft before deleting the account`,
|
|
);
|
|
});
|
|
|
|
test("formats multiple reviews correctly", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300004",
|
|
author: { login: "reviewer1" },
|
|
body: "Needs changes",
|
|
state: "CHANGES_REQUESTED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
},
|
|
{
|
|
id: "review2",
|
|
databaseId: "300005",
|
|
author: { login: "reviewer2" },
|
|
body: "LGTM",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-02T00:00:00Z",
|
|
comments: {
|
|
nodes: [],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: CHANGES_REQUESTED\nNeeds changes\n\n[Review by reviewer2 at 2023-01-02T00:00:00Z]: APPROVED\nLGTM`,
|
|
);
|
|
});
|
|
|
|
test("returns empty string for null reviewData", () => {
|
|
const result = formatReviewComments(null);
|
|
expect(result).toBe("");
|
|
});
|
|
|
|
test("returns empty string for empty reviewData", () => {
|
|
const result = formatReviewComments({ nodes: [] });
|
|
expect(result).toBe("");
|
|
});
|
|
|
|
test("replaces image URLs in review comments", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "Review with image: ",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Comment with image: ",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/index.ts",
|
|
line: 42,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/review.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/comment.png",
|
|
"/tmp/github-images/image-1234-1.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatReviewComments(reviewData, imageUrlMap);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nReview with image: \n [Comment on src/index.ts:42]: Comment with image: `,
|
|
);
|
|
});
|
|
|
|
test("handles multiple images in review comments", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "Good work",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Two issues:  and ",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/main.ts",
|
|
line: 15,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const imageUrlMap = new Map([
|
|
[
|
|
"https://github.com/user-attachments/assets/issue1.png",
|
|
"/tmp/github-images/image-1234-0.png",
|
|
],
|
|
[
|
|
"https://github.com/user-attachments/assets/issue2.png",
|
|
"/tmp/github-images/image-1234-1.png",
|
|
],
|
|
]);
|
|
|
|
const result = formatReviewComments(reviewData, imageUrlMap);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nGood work\n [Comment on src/main.ts:15]: Two issues:  and `,
|
|
);
|
|
});
|
|
|
|
test("preserves review comments when imageUrlMap is undefined", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "Review body",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Image: ",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/index.ts",
|
|
line: 42,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nReview body\n [Comment on src/index.ts:42]: Image: `,
|
|
);
|
|
});
|
|
|
|
test("filters out minimized review comments", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "Review with mixed comments",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Normal review comment",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/index.ts",
|
|
line: 42,
|
|
isMinimized: false,
|
|
},
|
|
{
|
|
id: "comment2",
|
|
databaseId: "200002",
|
|
body: "Minimized review comment",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/utils.ts",
|
|
line: 15,
|
|
isMinimized: true,
|
|
},
|
|
{
|
|
id: "comment3",
|
|
databaseId: "200003",
|
|
body: "Another normal comment",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/main.ts",
|
|
line: 10,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nReview with mixed comments\n [Comment on src/index.ts:42]: Normal review comment\n [Comment on src/main.ts:10]: Another normal comment`,
|
|
);
|
|
});
|
|
|
|
test("returns review with only body when all review comments are minimized", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "Review body only",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Minimized comment 1",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/index.ts",
|
|
line: 42,
|
|
isMinimized: true,
|
|
},
|
|
{
|
|
id: "comment2",
|
|
databaseId: "200002",
|
|
body: "Minimized comment 2",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/utils.ts",
|
|
line: 15,
|
|
isMinimized: true,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nReview body only`,
|
|
);
|
|
});
|
|
|
|
test("handles multiple reviews with mixed minimized comments", () => {
|
|
const reviewData = {
|
|
nodes: [
|
|
{
|
|
id: "review1",
|
|
databaseId: "300001",
|
|
author: { login: "reviewer1" },
|
|
body: "First review",
|
|
state: "APPROVED",
|
|
submittedAt: "2023-01-01T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment1",
|
|
databaseId: "200001",
|
|
body: "Good comment",
|
|
author: { login: "reviewer1" },
|
|
createdAt: "2023-01-01T00:00:00Z",
|
|
path: "src/index.ts",
|
|
line: 42,
|
|
isMinimized: false,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
id: "review2",
|
|
databaseId: "300002",
|
|
author: { login: "reviewer2" },
|
|
body: "Second review",
|
|
state: "COMMENTED",
|
|
submittedAt: "2023-01-02T00:00:00Z",
|
|
comments: {
|
|
nodes: [
|
|
{
|
|
id: "comment2",
|
|
databaseId: "200002",
|
|
body: "Spam comment",
|
|
author: { login: "reviewer2" },
|
|
createdAt: "2023-01-02T00:00:00Z",
|
|
path: "src/utils.ts",
|
|
line: 15,
|
|
isMinimized: true,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
const result = formatReviewComments(reviewData);
|
|
expect(result).toBe(
|
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nFirst review\n [Comment on src/index.ts:42]: Good comment\n\n[Review by reviewer2 at 2023-01-02T00:00:00Z]: COMMENTED\nSecond review`,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("formatChangedFiles", () => {
|
|
test("formats changed files correctly", () => {
|
|
const files: GitHubFile[] = [
|
|
{
|
|
path: "src/index.ts",
|
|
additions: 10,
|
|
deletions: 5,
|
|
changeType: "MODIFIED",
|
|
},
|
|
{
|
|
path: "src/utils.ts",
|
|
additions: 20,
|
|
deletions: 0,
|
|
changeType: "ADDED",
|
|
},
|
|
];
|
|
|
|
const result = formatChangedFiles(files);
|
|
expect(result).toBe(
|
|
`- src/index.ts (MODIFIED) +10/-5\n- src/utils.ts (ADDED) +20/-0`,
|
|
);
|
|
});
|
|
|
|
test("returns empty string for empty files array", () => {
|
|
const result = formatChangedFiles([]);
|
|
expect(result).toBe("");
|
|
});
|
|
});
|
|
|
|
describe("formatChangedFilesWithSHA", () => {
|
|
test("formats changed files with SHA correctly", () => {
|
|
const files: GitHubFileWithSHA[] = [
|
|
{
|
|
path: "src/index.ts",
|
|
additions: 10,
|
|
deletions: 5,
|
|
changeType: "MODIFIED",
|
|
sha: "abc123",
|
|
},
|
|
{
|
|
path: "src/utils.ts",
|
|
additions: 20,
|
|
deletions: 0,
|
|
changeType: "ADDED",
|
|
sha: "def456",
|
|
},
|
|
];
|
|
|
|
const result = formatChangedFilesWithSHA(files);
|
|
expect(result).toBe(
|
|
`- src/index.ts (MODIFIED) +10/-5 SHA: abc123\n- src/utils.ts (ADDED) +20/-0 SHA: def456`,
|
|
);
|
|
});
|
|
|
|
test("returns empty string for empty files array", () => {
|
|
const result = formatChangedFilesWithSHA([]);
|
|
expect(result).toBe("");
|
|
});
|
|
});
|