Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,15 @@ export class FeatureDevController {
)

let defaultMessage
const isDenyListedError = denyListedErrors.some((err) => errorMessage.includes(err))
const isDenyListedError = denyListedErrors.some((denyListedError) => err.message.includes(denyListedError))

switch (err.code) {
case ContentLengthError.errorName:
switch (err.constructor.name) {
case ContentLengthError.name:
this.messenger.sendAnswer({
type: 'answer',
tabID: message.tabID,
message: err.message + messageWithConversationId(session?.conversationIdUnsafe),
canBeVoted: true,
})
this.messenger.sendAnswer({
type: 'system-prompt',
Expand All @@ -235,26 +236,26 @@ export class FeatureDevController {
],
})
break
case MonthlyConversationLimitError.errorName:
case MonthlyConversationLimitError.name:
this.messenger.sendMonthlyLimitError(message.tabID)
break
case FeatureDevServiceError.errorName:
case UploadCodeError.errorName:
case UserMessageNotFoundError.errorName:
case TabIdNotFoundError.errorName:
case PrepareRepoFailedError.errorName:
case FeatureDevServiceError.name:
case UploadCodeError.name:
case UserMessageNotFoundError.name:
case TabIdNotFoundError.name:
case PrepareRepoFailedError.name:
this.messenger.sendErrorMessage(
errorMessage,
message.tabID,
this.retriesRemaining(session),
session?.conversationIdUnsafe
)
break
case PromptRefusalException.errorName:
case ZipFileError.errorName:
case PromptRefusalException.name:
case ZipFileError.name:
this.messenger.sendErrorMessage(errorMessage, message.tabID, 0, session?.conversationIdUnsafe, true)
break
case NoChangeRequiredException.errorName:
case NoChangeRequiredException.name:
this.messenger.sendAnswer({
type: 'answer',
tabID: message.tabID,
Expand All @@ -263,11 +264,12 @@ export class FeatureDevController {
})
// Allow users to re-work the task description.
return this.newTask(message)
case CodeIterationLimitError.errorName:
case CodeIterationLimitError.name:
this.messenger.sendAnswer({
type: 'answer',
tabID: message.tabID,
message: err.message + messageWithConversationId(session?.conversationIdUnsafe),
canBeVoted: true,
})
this.messenger.sendAnswer({
type: 'system-prompt',
Expand All @@ -282,7 +284,7 @@ export class FeatureDevController {
],
})
break
case UploadURLExpired.errorName:
case UploadURLExpired.name:
this.messenger.sendAnswer({
type: 'answer',
tabID: message.tabID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export class Messenger {
type: 'answer',
tabID: tabID,
message: showDefaultMessage ? errorMessage : i18n('AWS.amazonq.featureDev.error.technicalDifficulties'),
canBeVoted: true,
})
this.sendFeedback(tabID)
return
Expand Down
27 changes: 4 additions & 23 deletions packages/core/src/amazonqFeatureDev/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,13 @@ export class ConversationIdNotFoundError extends ToolkitError {
}

export class TabIdNotFoundError extends ToolkitError {
static errorName = 'TabIdNotFoundError'

constructor() {
super(i18n('AWS.amazonq.featureDev.error.tabIdNotFoundError'), {
code: 'TabIdNotFound',
})
}
}

export class PanelLoadError extends ToolkitError {
constructor() {
super(`${featureName} UI panel failed to load`, { code: 'PanelLoadFailed' })
}
}

export class WorkspaceFolderNotFoundError extends ToolkitError {
constructor() {
super(i18n('AWS.amazonq.featureDev.error.workspaceFolderNotFoundError'), {
Expand All @@ -41,7 +33,6 @@ export class WorkspaceFolderNotFoundError extends ToolkitError {
}

export class UserMessageNotFoundError extends ToolkitError {
static errorName = 'UserMessageNotFoundError'
constructor() {
super(i18n('AWS.amazonq.featureDev.error.userMessageNotFoundError'), {
code: 'MessageNotFound',
Expand All @@ -58,7 +49,6 @@ export class SelectedFolderNotInWorkspaceFolderError extends ToolkitError {
}

export class PromptRefusalException extends ToolkitError {
static errorName = 'PromptRefusalException'
constructor() {
super(i18n('AWS.amazonq.featureDev.error.promptRefusalException'), {
code: 'PromptRefusalException',
Expand All @@ -67,7 +57,6 @@ export class PromptRefusalException extends ToolkitError {
}

export class NoChangeRequiredException extends ToolkitError {
static errorName = 'NoChangeRequiredException'
constructor() {
super(i18n('AWS.amazonq.featureDev.error.noChangeRequiredException'), {
code: 'NoChangeRequiredException',
Expand All @@ -76,14 +65,12 @@ export class NoChangeRequiredException extends ToolkitError {
}

export class FeatureDevServiceError extends ToolkitError {
static errorName = 'FeatureDevServiceError'
constructor(message: string, code: string) {
super(message, { code })
}
}

export class PrepareRepoFailedError extends ToolkitError {
static errorName = 'PrepareRepoFailedError'
constructor() {
super(i18n('AWS.amazonq.featureDev.error.prepareRepoFailedError'), {
code: 'PrepareRepoFailed',
Expand All @@ -92,14 +79,12 @@ export class PrepareRepoFailedError extends ToolkitError {
}

export class UploadCodeError extends ToolkitError {
static errorName = 'UploadCodeError'
constructor(statusCode: string) {
super(uploadCodeError, { code: `UploadCode-${statusCode}` })
}
}

export class UploadURLExpired extends ToolkitError {
static errorName = 'UploadURLExpired'
constructor() {
super(i18n('AWS.amazonq.featureDev.error.uploadURLExpired'), { code: 'UploadURLExpired' })
}
Expand All @@ -112,30 +97,26 @@ export class IllegalStateTransition extends ToolkitError {
}

export class ContentLengthError extends ToolkitError {
static errorName = 'ContentLengthError'
constructor() {
super(i18n('AWS.amazonq.featureDev.error.contentLengthError'), { code: ContentLengthError.errorName })
super(i18n('AWS.amazonq.featureDev.error.contentLengthError'), { code: ContentLengthError.name })
}
}

export class ZipFileError extends ToolkitError {
static errorName = 'ZipFileError'
constructor() {
super(i18n('AWS.amazonq.featureDev.error.zipFileError'), { code: ZipFileError.errorName })
super(i18n('AWS.amazonq.featureDev.error.zipFileError'), { code: ZipFileError.name })
}
}

export class CodeIterationLimitError extends ToolkitError {
static errorName = 'CodeIterationLimitError'
constructor() {
super(i18n('AWS.amazonq.featureDev.error.codeIterationLimitError'), { code: CodeIterationLimitError.errorName })
super(i18n('AWS.amazonq.featureDev.error.codeIterationLimitError'), { code: CodeIterationLimitError.name })
}
}

export class MonthlyConversationLimitError extends ToolkitError {
static errorName = 'MonthlyConversationLimitError'
constructor(message: string) {
super(message, { code: MonthlyConversationLimitError.errorName })
super(message, { code: MonthlyConversationLimitError.name })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,27 @@ import { Session } from '../../../../amazonqFeatureDev/session/session'
import { Prompter } from '../../../../shared/ui/prompter'
import { assertTelemetry, toFile } from '../../../testUtil'
import {
CodeIterationLimitError,
ContentLengthError,
createUserFacingErrorMessage,
FeatureDevServiceError,
MonthlyConversationLimitError,
NoChangeRequiredException,
PrepareRepoFailedError,
PromptRefusalException,
SelectedFolderNotInWorkspaceFolderError,
TabIdNotFoundError,
UploadCodeError,
UploadURLExpired,
UserMessageNotFoundError,
ZipFileError,
} from '../../../../amazonqFeatureDev/errors'
import { CodeGenState, PrepareCodeGenState } from '../../../../amazonqFeatureDev/session/sessionState'
import { FeatureDevClient } from '../../../../amazonqFeatureDev/client/featureDev'
import { createAmazonQUri } from '../../../../amazonq/commons/diff'
import { AuthUtil } from '../../../../codewhisperer'
import { featureName, messageWithConversationId } from '../../../../amazonqFeatureDev'
import { i18n } from '../../../../shared/i18n-helper'

let mockGetCodeGeneration: sinon.SinonStub
describe('Controller', () => {
Expand Down Expand Up @@ -209,7 +223,6 @@ describe('Controller', () => {
})

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

describe('processErrorChatMessage', function () {
it('should handle NoChangeRequiredException', async function () {
const noChangeRequiredException = new NoChangeRequiredException()
sinon.stub(session, 'preloader').throws(noChangeRequiredException)
const runs = [
{ name: 'ContentLengthError', error: new ContentLengthError() },
{
name: 'MonthlyConversationLimitError',
error: new MonthlyConversationLimitError('Service Quota Exceeded'),
},
{
name: 'FeatureDevServiceError',
error: new FeatureDevServiceError(
i18n('AWS.amazonq.featureDev.error.codeGen.default'),
'GuardrailsException'
),
},
{ name: 'UploadCodeError', error: new UploadCodeError('403: Forbiden') },
{ name: 'UserMessageNotFoundError', error: new UserMessageNotFoundError() },
{ name: 'TabIdNotFoundError', error: new TabIdNotFoundError() },
{ name: 'PrepareRepoFailedError', error: new PrepareRepoFailedError() },
{ name: 'PromptRefusalException', error: new PromptRefusalException() },
{ name: 'ZipFileError', error: new ZipFileError() },
{ name: 'CodeIterationLimitError', error: new CodeIterationLimitError() },
{ name: 'UploadURLExpired', error: new UploadURLExpired() },
{ name: 'NoChangeRequiredException', error: new NoChangeRequiredException() },
{ name: 'default', error: new Error() },
]

function createTestErrorMessage(message: string) {
return createUserFacingErrorMessage(`${featureName} request failed: ${message}`)
}

async function verifyException(error: Error) {
sinon.stub(session, 'preloader').throws(error)
const sendAnswerSpy = sinon.stub(controllerSetup.messenger, 'sendAnswer')
const sendErrorMessageSpy = sinon.stub(controllerSetup.messenger, 'sendErrorMessage')
const sendMonthlyLimitErrorSpy = sinon.stub(controllerSetup.messenger, 'sendMonthlyLimitError')

await fireChatMessage()

assert.strictEqual(
sendAnswerSpy.calledWith({
type: 'answer',
tabID,
message: noChangeRequiredException.message,
canBeVoted: true,
}),
true
)
switch (error.constructor.name) {
case ContentLengthError.name:
assert.ok(
sendAnswerSpy.calledWith({
type: 'answer',
tabID,
message: error.message + messageWithConversationId(session?.conversationIdUnsafe),
canBeVoted: true,
})
)
break
case MonthlyConversationLimitError.name:
assert.ok(sendMonthlyLimitErrorSpy.calledWith(tabID))
break
case FeatureDevServiceError.name:
case UploadCodeError.name:
case UserMessageNotFoundError.name:
case TabIdNotFoundError.name:
case PrepareRepoFailedError.name:
assert.ok(
sendErrorMessageSpy.calledWith(
createTestErrorMessage(error.message),
tabID,
session?.retries,
session?.conversationIdUnsafe
)
)
break
case PromptRefusalException.name:
case ZipFileError.name:
assert.ok(
sendErrorMessageSpy.calledWith(
createTestErrorMessage(error.message),
tabID,
0,
session?.conversationIdUnsafe,
true
)
)
break
case NoChangeRequiredException.name:
case CodeIterationLimitError.name:
case UploadURLExpired.name:
assert.ok(
sendAnswerSpy.calledWith({
type: 'answer',
tabID,
message: error.message,
canBeVoted: true,
})
)
break
default:
assert.ok(
sendErrorMessageSpy.calledWith(
i18n('AWS.amazonq.featureDev.error.codeGen.default'),
tabID,
session?.retries,
session?.conversationIdUnsafe,
true
)
)
break
}
}

runs.forEach((run) => {
it(`should handle ${run.name}`, async function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference this is one way of de-duplicating tests, but it would also be fine to have a bunch of manally-declared it() blocks which call a common function.

await verifyException(run.error)
})
})
})
})
Expand Down
Loading