Skip to content

Commit 570eaf3

Browse files
refactor(q-feature-dev): remove errorName from Error classes #5769
## Problem - Static `errorName` is unnecessary in feature dev error classes. It also causes error code mismatches some of errorName which result in wrong state. ## Solution - Remove `errorName` from error classes - use class name match to ensure error handled correctly. - Add unit tests to cover all the error cases.
1 parent 3b6197b commit 570eaf3

File tree

4 files changed

+138
-50
lines changed

4 files changed

+138
-50
lines changed

packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,15 @@ export class FeatureDevController {
214214
)
215215

216216
let defaultMessage
217-
const isDenyListedError = denyListedErrors.some((err) => errorMessage.includes(err))
217+
const isDenyListedError = denyListedErrors.some((denyListedError) => err.message.includes(denyListedError))
218218

219-
switch (err.code) {
220-
case ContentLengthError.errorName:
219+
switch (err.constructor.name) {
220+
case ContentLengthError.name:
221221
this.messenger.sendAnswer({
222222
type: 'answer',
223223
tabID: message.tabID,
224224
message: err.message + messageWithConversationId(session?.conversationIdUnsafe),
225+
canBeVoted: true,
225226
})
226227
this.messenger.sendAnswer({
227228
type: 'system-prompt',
@@ -235,26 +236,26 @@ export class FeatureDevController {
235236
],
236237
})
237238
break
238-
case MonthlyConversationLimitError.errorName:
239+
case MonthlyConversationLimitError.name:
239240
this.messenger.sendMonthlyLimitError(message.tabID)
240241
break
241-
case FeatureDevServiceError.errorName:
242-
case UploadCodeError.errorName:
243-
case UserMessageNotFoundError.errorName:
244-
case TabIdNotFoundError.errorName:
245-
case PrepareRepoFailedError.errorName:
242+
case FeatureDevServiceError.name:
243+
case UploadCodeError.name:
244+
case UserMessageNotFoundError.name:
245+
case TabIdNotFoundError.name:
246+
case PrepareRepoFailedError.name:
246247
this.messenger.sendErrorMessage(
247248
errorMessage,
248249
message.tabID,
249250
this.retriesRemaining(session),
250251
session?.conversationIdUnsafe
251252
)
252253
break
253-
case PromptRefusalException.errorName:
254-
case ZipFileError.errorName:
254+
case PromptRefusalException.name:
255+
case ZipFileError.name:
255256
this.messenger.sendErrorMessage(errorMessage, message.tabID, 0, session?.conversationIdUnsafe, true)
256257
break
257-
case NoChangeRequiredException.errorName:
258+
case NoChangeRequiredException.name:
258259
this.messenger.sendAnswer({
259260
type: 'answer',
260261
tabID: message.tabID,
@@ -263,11 +264,12 @@ export class FeatureDevController {
263264
})
264265
// Allow users to re-work the task description.
265266
return this.newTask(message)
266-
case CodeIterationLimitError.errorName:
267+
case CodeIterationLimitError.name:
267268
this.messenger.sendAnswer({
268269
type: 'answer',
269270
tabID: message.tabID,
270271
message: err.message + messageWithConversationId(session?.conversationIdUnsafe),
272+
canBeVoted: true,
271273
})
272274
this.messenger.sendAnswer({
273275
type: 'system-prompt',
@@ -282,7 +284,7 @@ export class FeatureDevController {
282284
],
283285
})
284286
break
285-
case UploadURLExpired.errorName:
287+
case UploadURLExpired.name:
286288
this.messenger.sendAnswer({
287289
type: 'answer',
288290
tabID: message.tabID,

packages/core/src/amazonqFeatureDev/controllers/chat/messenger/messenger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export class Messenger {
8585
type: 'answer',
8686
tabID: tabID,
8787
message: showDefaultMessage ? errorMessage : i18n('AWS.amazonq.featureDev.error.technicalDifficulties'),
88+
canBeVoted: true,
8889
})
8990
this.sendFeedback(tabID)
9091
return

packages/core/src/amazonqFeatureDev/errors.ts

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,13 @@ export class ConversationIdNotFoundError extends ToolkitError {
1717
}
1818

1919
export class TabIdNotFoundError extends ToolkitError {
20-
static errorName = 'TabIdNotFoundError'
21-
2220
constructor() {
2321
super(i18n('AWS.amazonq.featureDev.error.tabIdNotFoundError'), {
2422
code: 'TabIdNotFound',
2523
})
2624
}
2725
}
2826

29-
export class PanelLoadError extends ToolkitError {
30-
constructor() {
31-
super(`${featureName} UI panel failed to load`, { code: 'PanelLoadFailed' })
32-
}
33-
}
34-
3527
export class WorkspaceFolderNotFoundError extends ToolkitError {
3628
constructor() {
3729
super(i18n('AWS.amazonq.featureDev.error.workspaceFolderNotFoundError'), {
@@ -41,7 +33,6 @@ export class WorkspaceFolderNotFoundError extends ToolkitError {
4133
}
4234

4335
export class UserMessageNotFoundError extends ToolkitError {
44-
static errorName = 'UserMessageNotFoundError'
4536
constructor() {
4637
super(i18n('AWS.amazonq.featureDev.error.userMessageNotFoundError'), {
4738
code: 'MessageNotFound',
@@ -58,7 +49,6 @@ export class SelectedFolderNotInWorkspaceFolderError extends ToolkitError {
5849
}
5950

6051
export class PromptRefusalException extends ToolkitError {
61-
static errorName = 'PromptRefusalException'
6252
constructor() {
6353
super(i18n('AWS.amazonq.featureDev.error.promptRefusalException'), {
6454
code: 'PromptRefusalException',
@@ -67,7 +57,6 @@ export class PromptRefusalException extends ToolkitError {
6757
}
6858

6959
export class NoChangeRequiredException extends ToolkitError {
70-
static errorName = 'NoChangeRequiredException'
7160
constructor() {
7261
super(i18n('AWS.amazonq.featureDev.error.noChangeRequiredException'), {
7362
code: 'NoChangeRequiredException',
@@ -76,14 +65,12 @@ export class NoChangeRequiredException extends ToolkitError {
7665
}
7766

7867
export class FeatureDevServiceError extends ToolkitError {
79-
static errorName = 'FeatureDevServiceError'
8068
constructor(message: string, code: string) {
8169
super(message, { code })
8270
}
8371
}
8472

8573
export class PrepareRepoFailedError extends ToolkitError {
86-
static errorName = 'PrepareRepoFailedError'
8774
constructor() {
8875
super(i18n('AWS.amazonq.featureDev.error.prepareRepoFailedError'), {
8976
code: 'PrepareRepoFailed',
@@ -92,14 +79,12 @@ export class PrepareRepoFailedError extends ToolkitError {
9279
}
9380

9481
export class UploadCodeError extends ToolkitError {
95-
static errorName = 'UploadCodeError'
9682
constructor(statusCode: string) {
9783
super(uploadCodeError, { code: `UploadCode-${statusCode}` })
9884
}
9985
}
10086

10187
export class UploadURLExpired extends ToolkitError {
102-
static errorName = 'UploadURLExpired'
10388
constructor() {
10489
super(i18n('AWS.amazonq.featureDev.error.uploadURLExpired'), { code: 'UploadURLExpired' })
10590
}
@@ -112,30 +97,26 @@ export class IllegalStateTransition extends ToolkitError {
11297
}
11398

11499
export class ContentLengthError extends ToolkitError {
115-
static errorName = 'ContentLengthError'
116100
constructor() {
117-
super(i18n('AWS.amazonq.featureDev.error.contentLengthError'), { code: ContentLengthError.errorName })
101+
super(i18n('AWS.amazonq.featureDev.error.contentLengthError'), { code: ContentLengthError.name })
118102
}
119103
}
120104

121105
export class ZipFileError extends ToolkitError {
122-
static errorName = 'ZipFileError'
123106
constructor() {
124-
super(i18n('AWS.amazonq.featureDev.error.zipFileError'), { code: ZipFileError.errorName })
107+
super(i18n('AWS.amazonq.featureDev.error.zipFileError'), { code: ZipFileError.name })
125108
}
126109
}
127110

128111
export class CodeIterationLimitError extends ToolkitError {
129-
static errorName = 'CodeIterationLimitError'
130112
constructor() {
131-
super(i18n('AWS.amazonq.featureDev.error.codeIterationLimitError'), { code: CodeIterationLimitError.errorName })
113+
super(i18n('AWS.amazonq.featureDev.error.codeIterationLimitError'), { code: CodeIterationLimitError.name })
132114
}
133115
}
134116

135117
export class MonthlyConversationLimitError extends ToolkitError {
136-
static errorName = 'MonthlyConversationLimitError'
137118
constructor(message: string) {
138-
super(message, { code: MonthlyConversationLimitError.errorName })
119+
super(message, { code: MonthlyConversationLimitError.name })
139120
}
140121
}
141122

packages/core/src/test/amazonqFeatureDev/controllers/chat/controller.test.ts

Lines changed: 117 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,27 @@ import { Session } from '../../../../amazonqFeatureDev/session/session'
1414
import { Prompter } from '../../../../shared/ui/prompter'
1515
import { assertTelemetry, toFile } from '../../../testUtil'
1616
import {
17+
CodeIterationLimitError,
18+
ContentLengthError,
19+
createUserFacingErrorMessage,
20+
FeatureDevServiceError,
21+
MonthlyConversationLimitError,
1722
NoChangeRequiredException,
23+
PrepareRepoFailedError,
24+
PromptRefusalException,
1825
SelectedFolderNotInWorkspaceFolderError,
26+
TabIdNotFoundError,
27+
UploadCodeError,
28+
UploadURLExpired,
29+
UserMessageNotFoundError,
30+
ZipFileError,
1931
} from '../../../../amazonqFeatureDev/errors'
2032
import { CodeGenState, PrepareCodeGenState } from '../../../../amazonqFeatureDev/session/sessionState'
2133
import { FeatureDevClient } from '../../../../amazonqFeatureDev/client/featureDev'
2234
import { createAmazonQUri } from '../../../../amazonq/commons/diff'
2335
import { AuthUtil } from '../../../../codewhisperer'
36+
import { featureName, messageWithConversationId } from '../../../../amazonqFeatureDev'
37+
import { i18n } from '../../../../shared/i18n-helper'
2438

2539
let mockGetCodeGeneration: sinon.SinonStub
2640
describe('Controller', () => {
@@ -209,7 +223,6 @@ describe('Controller', () => {
209223
})
210224

211225
it('accepts valid source folders under a workspace root', async () => {
212-
const controllerSetup = await createController()
213226
sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
214227
sinon.stub(vscode.workspace, 'getWorkspaceFolder').returns(controllerSetup.workspaceFolder)
215228
const expectedSourceRoot = path.join(controllerSetup.workspaceFolder.uri.fsPath, 'src')
@@ -385,22 +398,113 @@ describe('Controller', () => {
385398
}
386399

387400
describe('processErrorChatMessage', function () {
388-
it('should handle NoChangeRequiredException', async function () {
389-
const noChangeRequiredException = new NoChangeRequiredException()
390-
sinon.stub(session, 'preloader').throws(noChangeRequiredException)
401+
const runs = [
402+
{ name: 'ContentLengthError', error: new ContentLengthError() },
403+
{
404+
name: 'MonthlyConversationLimitError',
405+
error: new MonthlyConversationLimitError('Service Quota Exceeded'),
406+
},
407+
{
408+
name: 'FeatureDevServiceError',
409+
error: new FeatureDevServiceError(
410+
i18n('AWS.amazonq.featureDev.error.codeGen.default'),
411+
'GuardrailsException'
412+
),
413+
},
414+
{ name: 'UploadCodeError', error: new UploadCodeError('403: Forbiden') },
415+
{ name: 'UserMessageNotFoundError', error: new UserMessageNotFoundError() },
416+
{ name: 'TabIdNotFoundError', error: new TabIdNotFoundError() },
417+
{ name: 'PrepareRepoFailedError', error: new PrepareRepoFailedError() },
418+
{ name: 'PromptRefusalException', error: new PromptRefusalException() },
419+
{ name: 'ZipFileError', error: new ZipFileError() },
420+
{ name: 'CodeIterationLimitError', error: new CodeIterationLimitError() },
421+
{ name: 'UploadURLExpired', error: new UploadURLExpired() },
422+
{ name: 'NoChangeRequiredException', error: new NoChangeRequiredException() },
423+
{ name: 'default', error: new Error() },
424+
]
425+
426+
function createTestErrorMessage(message: string) {
427+
return createUserFacingErrorMessage(`${featureName} request failed: ${message}`)
428+
}
429+
430+
async function verifyException(error: Error) {
431+
sinon.stub(session, 'preloader').throws(error)
391432
const sendAnswerSpy = sinon.stub(controllerSetup.messenger, 'sendAnswer')
433+
const sendErrorMessageSpy = sinon.stub(controllerSetup.messenger, 'sendErrorMessage')
434+
const sendMonthlyLimitErrorSpy = sinon.stub(controllerSetup.messenger, 'sendMonthlyLimitError')
392435

393436
await fireChatMessage()
394437

395-
assert.strictEqual(
396-
sendAnswerSpy.calledWith({
397-
type: 'answer',
398-
tabID,
399-
message: noChangeRequiredException.message,
400-
canBeVoted: true,
401-
}),
402-
true
403-
)
438+
switch (error.constructor.name) {
439+
case ContentLengthError.name:
440+
assert.ok(
441+
sendAnswerSpy.calledWith({
442+
type: 'answer',
443+
tabID,
444+
message: error.message + messageWithConversationId(session?.conversationIdUnsafe),
445+
canBeVoted: true,
446+
})
447+
)
448+
break
449+
case MonthlyConversationLimitError.name:
450+
assert.ok(sendMonthlyLimitErrorSpy.calledWith(tabID))
451+
break
452+
case FeatureDevServiceError.name:
453+
case UploadCodeError.name:
454+
case UserMessageNotFoundError.name:
455+
case TabIdNotFoundError.name:
456+
case PrepareRepoFailedError.name:
457+
assert.ok(
458+
sendErrorMessageSpy.calledWith(
459+
createTestErrorMessage(error.message),
460+
tabID,
461+
session?.retries,
462+
session?.conversationIdUnsafe
463+
)
464+
)
465+
break
466+
case PromptRefusalException.name:
467+
case ZipFileError.name:
468+
assert.ok(
469+
sendErrorMessageSpy.calledWith(
470+
createTestErrorMessage(error.message),
471+
tabID,
472+
0,
473+
session?.conversationIdUnsafe,
474+
true
475+
)
476+
)
477+
break
478+
case NoChangeRequiredException.name:
479+
case CodeIterationLimitError.name:
480+
case UploadURLExpired.name:
481+
assert.ok(
482+
sendAnswerSpy.calledWith({
483+
type: 'answer',
484+
tabID,
485+
message: error.message,
486+
canBeVoted: true,
487+
})
488+
)
489+
break
490+
default:
491+
assert.ok(
492+
sendErrorMessageSpy.calledWith(
493+
i18n('AWS.amazonq.featureDev.error.codeGen.default'),
494+
tabID,
495+
session?.retries,
496+
session?.conversationIdUnsafe,
497+
true
498+
)
499+
)
500+
break
501+
}
502+
}
503+
504+
runs.forEach((run) => {
505+
it(`should handle ${run.name}`, async function () {
506+
await verifyException(run.error)
507+
})
404508
})
405509
})
406510
})

0 commit comments

Comments
 (0)