Skip to content

Commit 4afca7e

Browse files
committed
bugfix-303-bugbot-first-message-bugs: Enhance tests for IssueRepository, ProjectRepository, and PullRequestRepository. Add new test cases for handling error scenarios, including API failures and empty responses. Update BranchRepository type definition for consistent status options.
1 parent d77fdae commit 4afca7e

File tree

5 files changed

+576
-1
lines changed

5 files changed

+576
-1
lines changed

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;

src/data/repository/__tests__/issue_repository.test.ts

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,189 @@ jest.mock('@actions/github', () => ({
5050
}),
5151
}));
5252

53+
/** Build Labels with optional currentIssueLabels (for isHotfix, containsBranchedLabel, etc.). */
54+
function makeLabels(overrides: { currentIssueLabels?: string[] } = {}): Labels {
55+
const labels = new Labels(
56+
'launch',
57+
'bug',
58+
'bugfix',
59+
'hotfix',
60+
'enhancement',
61+
'feature',
62+
'release',
63+
'question',
64+
'help',
65+
'deploy',
66+
'deployed',
67+
'docs',
68+
'documentation',
69+
'chore',
70+
'maintenance',
71+
'priority-high',
72+
'priority-medium',
73+
'priority-low',
74+
'priority-none',
75+
'xxl',
76+
'xl',
77+
'l',
78+
'm',
79+
's',
80+
'xs'
81+
);
82+
if (overrides.currentIssueLabels) {
83+
labels.currentIssueLabels = overrides.currentIssueLabels;
84+
}
85+
return labels;
86+
}
87+
5388
describe('IssueRepository', () => {
5489
const repo = new IssueRepository();
5590

5691
beforeEach(() => {
5792
jest.clearAllMocks();
5893
});
5994

95+
describe('updateTitleIssueFormat', () => {
96+
it('updates title with emoji when hotfix and branched', async () => {
97+
const labels = makeLabels({ currentIssueLabels: ['hotfix', 'launch'] });
98+
mockRest.issues.update.mockResolvedValue(undefined);
99+
const result = await repo.updateTitleIssueFormat(
100+
'o',
101+
'r',
102+
'',
103+
'Fix login',
104+
1,
105+
false,
106+
'x',
107+
labels,
108+
'token'
109+
);
110+
expect(result).toBe('🔥x - Fix login');
111+
expect(mockRest.issues.update).toHaveBeenCalledWith({
112+
owner: 'o',
113+
repo: 'r',
114+
issue_number: 1,
115+
title: '🔥x - Fix login',
116+
});
117+
});
118+
119+
it('returns undefined when formatted title equals current title', async () => {
120+
const labels = makeLabels();
121+
const result = await repo.updateTitleIssueFormat(
122+
'o',
123+
'r',
124+
'',
125+
'🤖 - Clean title',
126+
1,
127+
false,
128+
'x',
129+
labels,
130+
'token'
131+
);
132+
expect(result).toBeUndefined();
133+
expect(mockRest.issues.update).not.toHaveBeenCalled();
134+
});
135+
136+
it('includes version in title when version length > 0', async () => {
137+
const labels = makeLabels({ currentIssueLabels: ['feature', 'launch'] });
138+
mockRest.issues.update.mockResolvedValue(undefined);
139+
const result = await repo.updateTitleIssueFormat(
140+
'o',
141+
'r',
142+
'v1.0',
143+
'Add API',
144+
2,
145+
true,
146+
'y',
147+
labels,
148+
'token'
149+
);
150+
expect(result).toContain('v1.0');
151+
expect(result).toContain('Add API');
152+
});
153+
154+
it('returns undefined and setFailed when update throws', async () => {
155+
const labels = makeLabels({ currentIssueLabels: ['bug'] });
156+
mockRest.issues.update.mockRejectedValue(new Error('API error'));
157+
const result = await repo.updateTitleIssueFormat(
158+
'o',
159+
'r',
160+
'',
161+
'Broken',
162+
1,
163+
false,
164+
'x',
165+
labels,
166+
'token'
167+
);
168+
expect(result).toBeUndefined();
169+
expect(mockSetFailed).toHaveBeenCalled();
170+
});
171+
});
172+
173+
describe('updateTitlePullRequestFormat', () => {
174+
it('updates PR title with [#N] and emoji when title differs', async () => {
175+
const labels = makeLabels({ currentIssueLabels: ['feature'] });
176+
mockRest.issues.update.mockResolvedValue(undefined);
177+
const result = await repo.updateTitlePullRequestFormat(
178+
'o',
179+
'r',
180+
'Old PR title',
181+
'Add feature',
182+
42,
183+
10,
184+
false,
185+
'x',
186+
labels,
187+
'token'
188+
);
189+
expect(result).toBe('[#42] ✨ - Add feature');
190+
expect(mockRest.issues.update).toHaveBeenCalledWith({
191+
owner: 'o',
192+
repo: 'r',
193+
issue_number: 10,
194+
title: '[#42] ✨ - Add feature',
195+
});
196+
});
197+
198+
it('returns undefined when formatted title equals pullRequestTitle', async () => {
199+
const labels = makeLabels();
200+
const result = await repo.updateTitlePullRequestFormat(
201+
'o',
202+
'r',
203+
'[#1] 🤖 - Same',
204+
'Same',
205+
1,
206+
5,
207+
false,
208+
'x',
209+
labels,
210+
'token'
211+
);
212+
expect(result).toBeUndefined();
213+
expect(mockRest.issues.update).not.toHaveBeenCalled();
214+
});
215+
216+
it('returns undefined and setFailed when update throws', async () => {
217+
const labels = makeLabels({ currentIssueLabels: ['hotfix'] });
218+
mockRest.issues.update.mockRejectedValue(new Error('API error'));
219+
const result = await repo.updateTitlePullRequestFormat(
220+
'o',
221+
'r',
222+
'PR',
223+
'Fix',
224+
1,
225+
1,
226+
false,
227+
'x',
228+
labels,
229+
'token'
230+
);
231+
expect(result).toBeUndefined();
232+
expect(mockSetFailed).toHaveBeenCalled();
233+
});
234+
});
235+
60236
describe('getDescription', () => {
61237
it('returns undefined when issueNumber is -1', async () => {
62238
const result = await repo.getDescription('o', 'r', -1, 'token');
@@ -531,6 +707,68 @@ describe('IssueRepository', () => {
531707
});
532708
});
533709

710+
describe('setIssueType', () => {
711+
const issueTypes = new IssueTypes(
712+
'Task', 'Task desc', 'BLUE',
713+
'Bug', 'Bug desc', 'RED',
714+
'Feature', 'Feature desc', 'GREEN',
715+
'Docs', 'Docs desc', 'GREY',
716+
'Maintenance', 'Maint desc', 'GREY',
717+
'Hotfix', 'Hotfix desc', 'RED',
718+
'Release', 'Release desc', 'BLUE',
719+
'Question', 'Q desc', 'PURPLE',
720+
'Help', 'Help desc', 'PURPLE'
721+
);
722+
723+
it('sets issue type when type exists in organization', async () => {
724+
const labels = makeLabels({ currentIssueLabels: ['bug'] });
725+
mockGraphql
726+
.mockResolvedValueOnce({ repository: { issue: { id: 'I_1' } } })
727+
.mockResolvedValueOnce({
728+
organization: {
729+
id: 'O_1',
730+
issueTypes: { nodes: [{ id: 'T_BUG', name: 'Bug' }] },
731+
},
732+
})
733+
.mockResolvedValueOnce({ updateIssueIssueType: { issue: { id: 'I_1', issueType: { id: 'T_BUG', name: 'Bug' } } } });
734+
await repo.setIssueType('org', 'repo', 1, labels, issueTypes, 'token');
735+
expect(mockGraphql).toHaveBeenCalledTimes(3);
736+
});
737+
738+
it('creates issue type when not found then updates issue', async () => {
739+
const labels = makeLabels({ currentIssueLabels: ['hotfix'] });
740+
mockGraphql
741+
.mockResolvedValueOnce({ repository: { issue: { id: 'I_1' } } })
742+
.mockResolvedValueOnce({
743+
organization: { id: 'O_1', issueTypes: { nodes: [] } },
744+
})
745+
.mockResolvedValueOnce({ createIssueType: { issueType: { id: 'T_NEW' } } })
746+
.mockResolvedValueOnce({ updateIssueIssueType: { issue: { id: 'I_1' } } });
747+
await repo.setIssueType('org', 'repo', 1, labels, issueTypes, 'token');
748+
expect(mockGraphql).toHaveBeenCalledTimes(4);
749+
});
750+
751+
it('returns early when createIssueType throws', async () => {
752+
const labels = makeLabels({ currentIssueLabels: ['release'] });
753+
mockGraphql
754+
.mockResolvedValueOnce({ repository: { issue: { id: 'I_1' } } })
755+
.mockResolvedValueOnce({
756+
organization: { id: 'O_1', issueTypes: { nodes: [] } },
757+
})
758+
.mockRejectedValueOnce(new Error('Create failed'));
759+
await repo.setIssueType('org', 'repo', 1, labels, issueTypes, 'token');
760+
expect(mockGraphql).toHaveBeenCalledTimes(3);
761+
});
762+
763+
it('throws when getId or organization query fails', async () => {
764+
const labels = makeLabels({ currentIssueLabels: ['feature'] });
765+
mockGraphql.mockRejectedValue(new Error('GraphQL error'));
766+
await expect(
767+
repo.setIssueType('org', 'repo', 1, labels, issueTypes, 'token')
768+
).rejects.toThrow('GraphQL error');
769+
});
770+
});
771+
534772
describe('ensureLabels', () => {
535773
it('ensures all required labels and returns counts', async () => {
536774
const labels = new Labels(
@@ -566,6 +804,20 @@ describe('IssueRepository', () => {
566804
expect(result.errors).toEqual([]);
567805
expect(result.created).toBeGreaterThan(0);
568806
});
807+
808+
it('collects errors when one ensureLabel throws', async () => {
809+
const labels = makeLabels();
810+
mockRest.issues.listLabelsForRepo.mockResolvedValue({ data: [] });
811+
let callCount = 0;
812+
mockRest.issues.createLabel.mockImplementation(() => {
813+
callCount++;
814+
if (callCount === 2) return Promise.reject(new Error('Label exists'));
815+
return Promise.resolve(undefined);
816+
});
817+
const result = await repo.ensureLabels('o', 'r', labels, 'token');
818+
expect(result.errors.length).toBeGreaterThan(0);
819+
expect(result.errors.some((e) => e.includes('Label exists'))).toBe(true);
820+
});
569821
});
570822

571823
describe('listIssueTypes', () => {
@@ -662,6 +914,35 @@ describe('IssueRepository', () => {
662914
expect(result.created).toBe(0);
663915
expect(result.errors).toEqual([]);
664916
});
917+
918+
it('collects errors when one ensureIssueType throws', async () => {
919+
const issueTypesForTest = new IssueTypes(
920+
'Task', 'Task desc', 'BLUE',
921+
'Bug', 'Bug desc', 'RED',
922+
'Feature', 'Feature desc', 'GREEN',
923+
'Docs', 'Docs desc', 'GREY',
924+
'Maintenance', 'Maint desc', 'GREY',
925+
'Hotfix', 'Hotfix desc', 'RED',
926+
'Release', 'Release desc', 'BLUE',
927+
'Question', 'Q desc', 'PURPLE',
928+
'Help', 'Help desc', 'PURPLE'
929+
);
930+
let callCount = 0;
931+
mockGraphql.mockImplementation(() => {
932+
callCount++;
933+
if (callCount === 1) {
934+
return Promise.resolve({
935+
organization: { id: 'O_1', issueTypes: { nodes: [{ id: 'T1', name: 'Task' }] } },
936+
});
937+
}
938+
if (callCount <= 3) {
939+
return Promise.resolve({ organization: { id: 'O_1', issueTypes: { nodes: [] } } });
940+
}
941+
return Promise.reject(new Error('Create failed'));
942+
});
943+
const result = await repo.ensureIssueTypes('org', issueTypesForTest, 'token');
944+
expect(result.errors.length).toBeGreaterThan(0);
945+
});
665946
});
666947
});
667948

0 commit comments

Comments
 (0)