Skip to content

Commit 1ee529d

Browse files
committed
bugfix-314-error-merging-release-branch-after-successful-deployment: Enhance ContentInterface tests to improve error handling and logging. Added test cases for scenarios where extraction and update operations throw errors, ensuring that errors are logged appropriately and that the functions return undefined as expected. This improves the robustness of the content handling logic.
1 parent 53f8788 commit 1ee529d

2 files changed

Lines changed: 237 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { OPENCODE_DEFAULT_MODEL } from '../../../utils/constants';
2+
import { Ai } from '../ai';
3+
4+
describe('Ai', () => {
5+
const defaultArgs: [
6+
string,
7+
string,
8+
boolean,
9+
boolean,
10+
string[],
11+
boolean,
12+
string,
13+
number,
14+
] = [
15+
'https://opencode.example',
16+
'opencode/kimi-k2.5-free',
17+
true,
18+
false,
19+
['*.min.js'],
20+
true,
21+
'warning',
22+
10,
23+
];
24+
25+
function createAi(
26+
overrides: Partial<{
27+
opencodeServerUrl: string;
28+
opencodeModel: string;
29+
aiPullRequestDescription: boolean;
30+
aiMembersOnly: boolean;
31+
aiIgnoreFiles: string[];
32+
aiIncludeReasoning: boolean;
33+
bugbotMinSeverity: string;
34+
bugbotCommentLimit: number;
35+
bugbotFixVerifyCommands: string[];
36+
}> = {},
37+
): Ai {
38+
return new Ai(
39+
overrides.opencodeServerUrl ?? defaultArgs[0],
40+
overrides.opencodeModel ?? defaultArgs[1],
41+
overrides.aiPullRequestDescription ?? defaultArgs[2],
42+
overrides.aiMembersOnly ?? defaultArgs[3],
43+
overrides.aiIgnoreFiles ?? defaultArgs[4],
44+
overrides.aiIncludeReasoning ?? defaultArgs[5],
45+
overrides.bugbotMinSeverity ?? defaultArgs[6],
46+
overrides.bugbotCommentLimit ?? defaultArgs[7],
47+
overrides.bugbotFixVerifyCommands,
48+
);
49+
}
50+
51+
describe('constructor and getters', () => {
52+
it('stores and returns all constructor values', () => {
53+
const ai = new Ai(
54+
'https://server',
55+
'anthropic/claude-3',
56+
true,
57+
true,
58+
['a', 'b'],
59+
false,
60+
'error',
61+
5,
62+
['npm run test'],
63+
);
64+
65+
expect(ai.getOpencodeServerUrl()).toBe('https://server');
66+
expect(ai.getOpencodeModel()).toBe('anthropic/claude-3');
67+
expect(ai.getAiPullRequestDescription()).toBe(true);
68+
expect(ai.getAiMembersOnly()).toBe(true);
69+
expect(ai.getAiIgnoreFiles()).toEqual(['a', 'b']);
70+
expect(ai.getAiIncludeReasoning()).toBe(false);
71+
expect(ai.getBugbotMinSeverity()).toBe('error');
72+
expect(ai.getBugbotCommentLimit()).toBe(5);
73+
expect(ai.getBugbotFixVerifyCommands()).toEqual(['npm run test']);
74+
});
75+
76+
it('defaults bugbotFixVerifyCommands to empty array when omitted', () => {
77+
const ai = new Ai(
78+
defaultArgs[0],
79+
defaultArgs[1],
80+
defaultArgs[2],
81+
defaultArgs[3],
82+
defaultArgs[4],
83+
defaultArgs[5],
84+
defaultArgs[6],
85+
defaultArgs[7],
86+
);
87+
88+
expect(ai.getBugbotFixVerifyCommands()).toEqual([]);
89+
});
90+
});
91+
92+
describe('getOpencodeModelParts', () => {
93+
it('returns provider and model when opencodeModel is "provider/model"', () => {
94+
const ai = createAi({ opencodeModel: 'anthropic/claude-3-opus' });
95+
96+
expect(ai.getOpencodeModelParts()).toEqual({
97+
providerID: 'anthropic',
98+
modelID: 'claude-3-opus',
99+
});
100+
});
101+
102+
it('trims whitespace from provider and model', () => {
103+
const ai = createAi({ opencodeModel: ' openai / gpt-4 ' });
104+
105+
expect(ai.getOpencodeModelParts()).toEqual({
106+
providerID: 'openai',
107+
modelID: 'gpt-4',
108+
});
109+
});
110+
111+
it('uses OPENCODE_DEFAULT_MODEL when opencodeModel is empty string', () => {
112+
const ai = createAi({ opencodeModel: '' });
113+
114+
expect(ai.getOpencodeModelParts()).toEqual({
115+
providerID: 'opencode',
116+
modelID: 'kimi-k2.5-free',
117+
});
118+
});
119+
120+
it('uses OPENCODE_DEFAULT_MODEL when opencodeModel is whitespace-only', () => {
121+
const ai = createAi({ opencodeModel: ' ' });
122+
123+
expect(ai.getOpencodeModelParts()).toEqual({
124+
providerID: 'opencode',
125+
modelID: 'kimi-k2.5-free',
126+
});
127+
});
128+
129+
it('when no slash returns providerID "opencode" and modelID as effective', () => {
130+
const ai = createAi({ opencodeModel: 'single-model-id' });
131+
132+
expect(ai.getOpencodeModelParts()).toEqual({
133+
providerID: 'opencode',
134+
modelID: 'single-model-id',
135+
});
136+
});
137+
138+
it('when slash at start (slash <= 0) uses opencode and effective as modelID', () => {
139+
const ai = createAi({ opencodeModel: '/only-model' });
140+
141+
expect(ai.getOpencodeModelParts()).toEqual({
142+
providerID: 'opencode',
143+
modelID: '/only-model',
144+
});
145+
});
146+
147+
it('when model is empty after trim uses default modelID from OPENCODE_DEFAULT_MODEL', () => {
148+
const ai = createAi({ opencodeModel: 'provider/ ' });
149+
150+
expect(ai.getOpencodeModelParts()).toEqual({
151+
providerID: 'provider',
152+
modelID: 'kimi-k2.5-free',
153+
});
154+
});
155+
156+
it('uses OPENCODE_DEFAULT_MODEL when opencodeModel is not set (falsy)', () => {
157+
const ai = createAi({ opencodeModel: '' });
158+
const parts = ai.getOpencodeModelParts();
159+
160+
expect(parts.providerID).toBe('opencode');
161+
expect(parts.modelID).toBe(OPENCODE_DEFAULT_MODEL.split('/')[1]);
162+
});
163+
164+
it('uses "opencode" when provider part is empty after trim', () => {
165+
const ai = createAi({ opencodeModel: 'x' });
166+
const originalTrim = String.prototype.trim;
167+
let trimCallCount = 0;
168+
jest.spyOn(String.prototype, 'trim').mockImplementation(function (this: string) {
169+
trimCallCount++;
170+
if (trimCallCount === 1) {
171+
return ' /model';
172+
}
173+
if (this === ' ') {
174+
return '';
175+
}
176+
return originalTrim.call(this);
177+
});
178+
179+
const parts = ai.getOpencodeModelParts();
180+
181+
expect(parts).toEqual({ providerID: 'opencode', modelID: 'model' });
182+
(String.prototype.trim as jest.Mock).mockRestore();
183+
});
184+
});
185+
});

src/manager/description/base/__tests__/content_interface.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,31 @@ describe('ContentInterface', () => {
5555
const desc = `pre\n${start}\norphan`;
5656
expect(handler.getContent(desc)).toBeUndefined();
5757
});
58+
59+
it('logs and rethrows when extraction throws', () => {
60+
const desc = `pre\n${start}\ninner\n${end}\npost`;
61+
const originalSplit = String.prototype.split;
62+
(jest.spyOn(String.prototype, 'split') as jest.Mock).mockImplementation(
63+
function (this: string, separator: unknown, limit?: number) {
64+
if (separator === start) {
65+
return ['only-one-element'];
66+
}
67+
return (originalSplit as (sep: string, limit?: number) => string[]).call(
68+
this,
69+
separator as string,
70+
limit,
71+
);
72+
},
73+
);
74+
const { logError } = require('../../../../utils/logger');
75+
76+
expect(() => handler.getContent(desc)).toThrow();
77+
expect(logError).toHaveBeenCalledWith(
78+
expect.stringMatching(/Error reading issue configuration/),
79+
);
80+
81+
(String.prototype.split as jest.Mock).mockRestore();
82+
});
5883
});
5984

6085
describe('updateContent', () => {
@@ -83,6 +108,33 @@ describe('ContentInterface', () => {
83108
const result = handler.updateContent(desc, 'new');
84109
expect(result).toBeUndefined();
85110
});
111+
112+
it('logs and returns undefined when update throws', () => {
113+
const desc = `pre\n${start}\nold\n${end}\npost`;
114+
const originalSplit = String.prototype.split;
115+
(jest.spyOn(String.prototype, 'split') as jest.Mock).mockImplementation(
116+
function (this: string, separator: unknown, limit?: number) {
117+
if (separator === start) {
118+
throw new Error('split failed');
119+
}
120+
return (originalSplit as (sep: string, limit?: number) => string[]).call(
121+
this,
122+
separator as string,
123+
limit,
124+
);
125+
},
126+
);
127+
const { logError } = require('../../../../utils/logger');
128+
129+
const result = handler.updateContent(desc, 'new');
130+
131+
expect(result).toBeUndefined();
132+
expect(logError).toHaveBeenCalledWith(
133+
expect.stringMatching(/Error updating issue description/),
134+
);
135+
136+
(String.prototype.split as jest.Mock).mockRestore();
137+
});
86138
});
87139
});
88140

0 commit comments

Comments
 (0)