Skip to content

Commit 4df08da

Browse files
committed
feature-296-bugbot-autofix: Enhance test coverage for Bugbot use cases by adding scenarios for handling resolved findings, verifying command execution, and managing issue and pull request numbers. Update existing tests to improve robustness and ensure proper logging for various edge cases. Additionally, refine type definitions in branch_repository.d.ts for clarity.
1 parent 9c75a5d commit 4df08da

37 files changed

Lines changed: 1569 additions & 1 deletion

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};

build/github_action/src/data/repository/branch_repository.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export declare class BranchRepository {
3333
totalCommits: number;
3434
files: {
3535
filename: string;
36-
status: "added" | "removed" | "modified" | "renamed" | "copied" | "changed" | "unchanged";
36+
status: "modified" | "added" | "removed" | "renamed" | "copied" | "changed" | "unchanged";
3737
additions: number;
3838
deletions: number;
3939
changes: number;
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
/**
2+
* Unit tests for mainRun (common_action).
3+
* Mocks use cases and queue; covers dispatch branches and error handling.
4+
*/
5+
6+
jest.mock('chalk', () => ({
7+
cyan: (s: string) => s,
8+
gray: (s: string) => s,
9+
default: { cyan: (s: string) => s, gray: (s: string) => s },
10+
}));
11+
jest.mock('boxen', () => jest.fn((text: string) => text));
12+
13+
import { mainRun } from '../common_action';
14+
import type { Execution } from '../../data/model/execution';
15+
import { Result } from '../../data/model/result';
16+
17+
jest.mock('@actions/core', () => ({
18+
setFailed: jest.fn(),
19+
}));
20+
21+
jest.mock('../../utils/logger', () => ({
22+
logInfo: jest.fn(),
23+
logError: jest.fn(),
24+
}));
25+
26+
jest.mock('../../utils/queue_utils', () => ({
27+
waitForPreviousRuns: jest.fn().mockResolvedValue(undefined),
28+
}));
29+
30+
const mockSingleActionInvoke = jest.fn();
31+
const mockIssueCommentInvoke = jest.fn();
32+
const mockIssueInvoke = jest.fn();
33+
const mockPullRequestReviewCommentInvoke = jest.fn();
34+
const mockPullRequestInvoke = jest.fn();
35+
const mockCommitInvoke = jest.fn();
36+
37+
jest.mock('../../usecase/single_action_use_case', () => ({
38+
SingleActionUseCase: jest.fn().mockImplementation(() => ({
39+
invoke: mockSingleActionInvoke,
40+
})),
41+
}));
42+
jest.mock('../../usecase/issue_comment_use_case', () => ({
43+
IssueCommentUseCase: jest.fn().mockImplementation(() => ({
44+
invoke: mockIssueCommentInvoke,
45+
})),
46+
}));
47+
jest.mock('../../usecase/issue_use_case', () => ({
48+
IssueUseCase: jest.fn().mockImplementation(() => ({
49+
invoke: mockIssueInvoke,
50+
})),
51+
}));
52+
jest.mock('../../usecase/pull_request_review_comment_use_case', () => ({
53+
PullRequestReviewCommentUseCase: jest.fn().mockImplementation(() => ({
54+
invoke: mockPullRequestReviewCommentInvoke,
55+
})),
56+
}));
57+
jest.mock('../../usecase/pull_request_use_case', () => ({
58+
PullRequestUseCase: jest.fn().mockImplementation(() => ({
59+
invoke: mockPullRequestInvoke,
60+
})),
61+
}));
62+
jest.mock('../../usecase/commit_use_case', () => ({
63+
CommitUseCase: jest.fn().mockImplementation(() => ({
64+
invoke: mockCommitInvoke,
65+
})),
66+
}));
67+
68+
const core = require('@actions/core');
69+
const { waitForPreviousRuns } = require('../../utils/queue_utils');
70+
71+
function mockExecution(overrides: Record<string, unknown> = {}): Execution {
72+
const base = {
73+
setup: jest.fn().mockResolvedValue(undefined),
74+
welcome: undefined,
75+
runnedByToken: false,
76+
tokenUser: 'user',
77+
isSingleAction: false,
78+
singleAction: {
79+
validSingleAction: false,
80+
isSingleActionWithoutIssue: false,
81+
enabledSingleAction: false,
82+
},
83+
issueNumber: 42,
84+
isIssue: false,
85+
issue: { isIssueComment: false, isIssue: false },
86+
isPullRequest: false,
87+
pullRequest: { isPullRequestReviewComment: false, isPullRequest: false },
88+
isPush: false,
89+
...overrides,
90+
};
91+
return base as unknown as Execution;
92+
}
93+
94+
describe('mainRun', () => {
95+
beforeEach(() => {
96+
jest.clearAllMocks();
97+
(waitForPreviousRuns as jest.Mock).mockResolvedValue(undefined);
98+
mockSingleActionInvoke.mockResolvedValue([]);
99+
mockIssueCommentInvoke.mockResolvedValue([]);
100+
mockIssueInvoke.mockResolvedValue([]);
101+
mockPullRequestReviewCommentInvoke.mockResolvedValue([]);
102+
mockPullRequestInvoke.mockResolvedValue([]);
103+
mockCommitInvoke.mockResolvedValue([]);
104+
});
105+
106+
it('calls execution.setup()', async () => {
107+
const setupMock = jest.fn().mockResolvedValue(undefined);
108+
const execution = mockExecution({ setup: setupMock });
109+
await mainRun(execution);
110+
expect(setupMock).toHaveBeenCalledTimes(1);
111+
});
112+
113+
it('waits for previous runs when welcome is false', async () => {
114+
const execution = mockExecution({ welcome: undefined });
115+
await mainRun(execution);
116+
expect(waitForPreviousRuns).toHaveBeenCalledWith(execution);
117+
});
118+
119+
it('skips wait when welcome is set', async () => {
120+
const execution = mockExecution({
121+
welcome: { title: 'Hi', messages: ['Welcome'] },
122+
isPush: true,
123+
});
124+
await mainRun(execution);
125+
expect(waitForPreviousRuns).not.toHaveBeenCalled();
126+
expect(mockCommitInvoke).toHaveBeenCalled();
127+
});
128+
129+
it('runs SingleActionUseCase when runnedByToken and valid single action', async () => {
130+
const execution = mockExecution({
131+
runnedByToken: true,
132+
isSingleAction: true,
133+
singleAction: {
134+
validSingleAction: true,
135+
isSingleActionWithoutIssue: false,
136+
enabledSingleAction: true,
137+
},
138+
});
139+
const expected = [new Result({ id: 's', success: true, executed: true })];
140+
mockSingleActionInvoke.mockResolvedValue(expected);
141+
142+
const results = await mainRun(execution);
143+
144+
expect(mockSingleActionInvoke).toHaveBeenCalledWith(execution);
145+
expect(results).toEqual(expected);
146+
expect(mockCommitInvoke).not.toHaveBeenCalled();
147+
});
148+
149+
it('returns empty when runnedByToken but not valid single action', async () => {
150+
const execution = mockExecution({
151+
runnedByToken: true,
152+
isSingleAction: false,
153+
});
154+
155+
const results = await mainRun(execution);
156+
157+
expect(results).toEqual([]);
158+
expect(mockSingleActionInvoke).not.toHaveBeenCalled();
159+
});
160+
161+
it('runs SingleActionUseCase when issueNumber -1 and isSingleActionWithoutIssue', async () => {
162+
const execution = mockExecution({
163+
issueNumber: -1,
164+
isSingleAction: true,
165+
singleAction: {
166+
validSingleAction: false,
167+
isSingleActionWithoutIssue: true,
168+
enabledSingleAction: true,
169+
},
170+
});
171+
mockSingleActionInvoke.mockResolvedValue([new Result({ id: 't', success: true })]);
172+
173+
const results = await mainRun(execution);
174+
175+
expect(mockSingleActionInvoke).toHaveBeenCalledWith(execution);
176+
expect(results.length).toBeGreaterThan(0);
177+
});
178+
179+
it('returns empty when issueNumber -1 and not single action without issue', async () => {
180+
const execution = mockExecution({
181+
issueNumber: -1,
182+
isSingleAction: false,
183+
});
184+
185+
const results = await mainRun(execution);
186+
187+
expect(results).toEqual([]);
188+
expect(mockSingleActionInvoke).not.toHaveBeenCalled();
189+
});
190+
191+
it('runs IssueCommentUseCase when isIssue and issue comment', async () => {
192+
const execution = mockExecution({
193+
isIssue: true,
194+
issue: { isIssueComment: true, isIssue: false },
195+
});
196+
const expected = [new Result({ id: 'ic', success: true })];
197+
mockIssueCommentInvoke.mockResolvedValue(expected);
198+
199+
const results = await mainRun(execution);
200+
201+
expect(mockIssueCommentInvoke).toHaveBeenCalledWith(execution);
202+
expect(results).toEqual(expected);
203+
});
204+
205+
it('runs IssueUseCase when isIssue and not issue comment', async () => {
206+
const execution = mockExecution({
207+
isIssue: true,
208+
issue: { isIssueComment: false, isIssue: true },
209+
});
210+
const expected = [new Result({ id: 'i', success: true })];
211+
mockIssueInvoke.mockResolvedValue(expected);
212+
213+
const results = await mainRun(execution);
214+
215+
expect(mockIssueInvoke).toHaveBeenCalledWith(execution);
216+
expect(results).toEqual(expected);
217+
});
218+
219+
it('runs PullRequestReviewCommentUseCase when isPullRequest and review comment', async () => {
220+
const execution = mockExecution({
221+
isPullRequest: true,
222+
pullRequest: { isPullRequestReviewComment: true, isPullRequest: false },
223+
});
224+
const expected = [new Result({ id: 'prc', success: true })];
225+
mockPullRequestReviewCommentInvoke.mockResolvedValue(expected);
226+
227+
const results = await mainRun(execution);
228+
229+
expect(mockPullRequestReviewCommentInvoke).toHaveBeenCalledWith(execution);
230+
expect(results).toEqual(expected);
231+
});
232+
233+
it('runs PullRequestUseCase when isPullRequest and not review comment', async () => {
234+
const execution = mockExecution({
235+
isPullRequest: true,
236+
pullRequest: { isPullRequestReviewComment: false, isPullRequest: true },
237+
});
238+
const expected = [new Result({ id: 'pr', success: true })];
239+
mockPullRequestInvoke.mockResolvedValue(expected);
240+
241+
const results = await mainRun(execution);
242+
243+
expect(mockPullRequestInvoke).toHaveBeenCalledWith(execution);
244+
expect(results).toEqual(expected);
245+
});
246+
247+
it('runs CommitUseCase when isPush', async () => {
248+
const execution = mockExecution({ isPush: true });
249+
const expected = [new Result({ id: 'c', success: true })];
250+
mockCommitInvoke.mockResolvedValue(expected);
251+
252+
const results = await mainRun(execution);
253+
254+
expect(mockCommitInvoke).toHaveBeenCalledWith(execution);
255+
expect(results).toEqual(expected);
256+
});
257+
258+
it('calls core.setFailed when action not handled', async () => {
259+
const execution = mockExecution({
260+
isIssue: false,
261+
isPullRequest: false,
262+
isPush: false,
263+
});
264+
265+
const results = await mainRun(execution);
266+
267+
expect(core.setFailed).toHaveBeenCalledWith('Action not handled.');
268+
expect(results).toEqual([]);
269+
});
270+
271+
it('calls core.setFailed and returns [] when use case throws', async () => {
272+
const execution = mockExecution({ isPush: true });
273+
mockCommitInvoke.mockRejectedValue(new Error('Commit failed'));
274+
275+
const results = await mainRun(execution);
276+
277+
expect(core.setFailed).toHaveBeenCalledWith('Commit failed');
278+
expect(results).toEqual([]);
279+
});
280+
281+
it('exits process when waitForPreviousRuns rejects and welcome is false', async () => {
282+
const exitSpy = jest.spyOn(process, 'exit').mockImplementation((() => {}) as () => never);
283+
(waitForPreviousRuns as jest.Mock).mockRejectedValue(new Error('Queue error'));
284+
const execution = mockExecution({ welcome: undefined });
285+
286+
await mainRun(execution);
287+
288+
expect(exitSpy).toHaveBeenCalledWith(1);
289+
exitSpy.mockRestore();
290+
});
291+
});

src/usecase/steps/commit/__tests__/detect_potential_problems_use_case.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,26 @@ describe('DetectPotentialProblemsUseCase', () => {
123123
expect(mockAskAgent).not.toHaveBeenCalled();
124124
});
125125

126+
it('uses default ignore patterns and comment limit when ai has no getAiIgnoreFiles nor getBugbotCommentLimit', async () => {
127+
const minimalAi = {
128+
getOpencodeModel: () => 'opencode/model',
129+
getOpencodeServerUrl: () => 'http://localhost:4096',
130+
getBugbotMinSeverity: () => 'low',
131+
} as unknown as Execution['ai'];
132+
const param = baseParam({ ai: minimalAi });
133+
mockAskAgent.mockResolvedValue({
134+
findings: [{ id: 'f1', title: 'One', description: 'D' }],
135+
resolved_finding_ids: [],
136+
});
137+
138+
const results = await useCase.invoke(param);
139+
140+
expect(results).toHaveLength(1);
141+
expect(results[0].success).toBe(true);
142+
expect(mockAddComment).toHaveBeenCalledTimes(1);
143+
expect(mockAddComment.mock.calls[0][3]).toContain('One');
144+
});
145+
126146
it('returns empty results when issue number is -1', async () => {
127147
const param = baseParam({ issueNumber: -1 });
128148

0 commit comments

Comments
 (0)