@@ -9,7 +9,13 @@ import * as path from 'path'
99import sinon from 'sinon'
1010import { waitUntil } from '../../../../shared/utilities/timeoutUtils'
1111import { ControllerSetup , createController , createSession , generateVirtualMemoryUri } from '../../utils'
12- import { CurrentWsFolders , DeletedFileInfo , NewFileInfo } from '../../../../amazonqFeatureDev/types'
12+ import {
13+ CurrentWsFolders ,
14+ DeletedFileInfo ,
15+ MetricDataOperationName ,
16+ MetricDataResult ,
17+ NewFileInfo ,
18+ } from '../../../../amazonqFeatureDev/types'
1319import { Session } from '../../../../amazonqFeatureDev/session/session'
1420import { Prompter } from '../../../../shared/ui/prompter'
1521import { assertTelemetry , toFile } from '../../../testUtil'
@@ -36,6 +42,7 @@ import { AuthUtil } from '../../../../codewhisperer'
3642import { featureDevScheme , featureName , messageWithConversationId } from '../../../../amazonqFeatureDev'
3743import { i18n } from '../../../../shared/i18n-helper'
3844import { FollowUpTypes } from '../../../../amazonq/commons/types'
45+ import { ToolkitError } from '../../../../shared'
3946
4047let mockGetCodeGeneration : sinon . SinonStub
4148describe ( 'Controller' , ( ) => {
@@ -395,7 +402,47 @@ describe('Controller', () => {
395402 } )
396403
397404 describe ( 'processUserChatMessage' , function ( ) {
398- async function fireChatMessage ( ) {
405+ // TODO: fix disablePreviousFileList error
406+ const runs = [
407+ { name : 'ContentLengthError' , error : new ContentLengthError ( ) } ,
408+ {
409+ name : 'MonthlyConversationLimitError' ,
410+ error : new MonthlyConversationLimitError ( 'Service Quota Exceeded' ) ,
411+ } ,
412+ {
413+ name : 'FeatureDevServiceErrorGuardrailsException' ,
414+ error : new FeatureDevServiceError (
415+ i18n ( 'AWS.amazonq.featureDev.error.codeGen.default' ) ,
416+ 'GuardrailsException'
417+ ) ,
418+ } ,
419+ {
420+ name : 'FeatureDevServiceErrorEmptyPatchException' ,
421+ error : new FeatureDevServiceError (
422+ i18n ( 'AWS.amazonq.featureDev.error.throttling' ) ,
423+ 'EmptyPatchException'
424+ ) ,
425+ } ,
426+ {
427+ name : 'FeatureDevServiceErrorThrottlingException' ,
428+ error : new FeatureDevServiceError (
429+ i18n ( 'AWS.amazonq.featureDev.error.codeGen.default' ) ,
430+ 'ThrottlingException'
431+ ) ,
432+ } ,
433+ { name : 'UploadCodeError' , error : new UploadCodeError ( '403: Forbiden' ) } ,
434+ { name : 'UserMessageNotFoundError' , error : new UserMessageNotFoundError ( ) } ,
435+ { name : 'TabIdNotFoundError' , error : new TabIdNotFoundError ( ) } ,
436+ { name : 'PrepareRepoFailedError' , error : new PrepareRepoFailedError ( ) } ,
437+ { name : 'PromptRefusalException' , error : new PromptRefusalException ( ) } ,
438+ { name : 'ZipFileError' , error : new ZipFileError ( ) } ,
439+ { name : 'CodeIterationLimitError' , error : new CodeIterationLimitError ( ) } ,
440+ { name : 'UploadURLExpired' , error : new UploadURLExpired ( ) } ,
441+ { name : 'NoChangeRequiredException' , error : new NoChangeRequiredException ( ) } ,
442+ { name : 'default' , error : new ToolkitError ( 'Default' , { code : 'Default' } ) } ,
443+ ]
444+
445+ async function fireChatMessage ( session : Session ) {
399446 const getSessionStub = sinon . stub ( controllerSetup . sessionStorage , 'getSession' ) . resolves ( session )
400447
401448 controllerSetup . emitters . processHumanChatMessage . fire ( {
@@ -410,44 +457,121 @@ describe('Controller', () => {
410457 } , { } )
411458 }
412459
413- describe ( 'processErrorChatMessage' , function ( ) {
414- // TODO: fix disablePreviousFileList error
415- const runs = [
416- { name : 'ContentLengthError' , error : new ContentLengthError ( ) } ,
417- {
418- name : 'MonthlyConversationLimitError' ,
419- error : new MonthlyConversationLimitError ( 'Service Quota Exceeded' ) ,
420- } ,
421- {
422- name : 'FeatureDevServiceError' ,
423- error : new FeatureDevServiceError (
424- i18n ( 'AWS.amazonq.featureDev.error.codeGen.default' ) ,
425- 'GuardrailsException'
426- ) ,
427- } ,
428- { name : 'UploadCodeError' , error : new UploadCodeError ( '403: Forbiden' ) } ,
429- { name : 'UserMessageNotFoundError' , error : new UserMessageNotFoundError ( ) } ,
430- { name : 'TabIdNotFoundError' , error : new TabIdNotFoundError ( ) } ,
431- { name : 'PrepareRepoFailedError' , error : new PrepareRepoFailedError ( ) } ,
432- { name : 'PromptRefusalException' , error : new PromptRefusalException ( ) } ,
433- { name : 'ZipFileError' , error : new ZipFileError ( ) } ,
434- { name : 'CodeIterationLimitError' , error : new CodeIterationLimitError ( ) } ,
435- { name : 'UploadURLExpired' , error : new UploadURLExpired ( ) } ,
436- { name : 'NoChangeRequiredException' , error : new NoChangeRequiredException ( ) } ,
437- { name : 'default' , error : new Error ( ) } ,
438- ]
460+ describe ( 'onCodeGeneration' , function ( ) {
461+ let session : any
462+ let sendMetricDataTelemetrySpy : sinon . SinonStub
463+
464+ const errorResultMapping = new Map ( [
465+ [ 'EmptyPatchException' , MetricDataResult . LlmFailure ] ,
466+ [ PromptRefusalException . name , MetricDataResult . Error ] ,
467+ [ NoChangeRequiredException . name , MetricDataResult . Error ] ,
468+ ] )
469+
470+ function getMetricResult ( error : ToolkitError ) : MetricDataResult {
471+ if ( error instanceof FeatureDevServiceError && error . code ) {
472+ return errorResultMapping . get ( error . code ) ?? MetricDataResult . Error
473+ }
474+ return errorResultMapping . get ( error . constructor . name ) ?? MetricDataResult . Fault
475+ }
476+
477+ async function createCodeGenState ( ) {
478+ mockGetCodeGeneration = sinon . stub ( ) . resolves ( { codeGenerationStatus : { status : 'Complete' } } )
479+
480+ const workspaceFolders = [ controllerSetup . workspaceFolder ] as CurrentWsFolders
481+ const testConfig = {
482+ conversationId : conversationID ,
483+ proxyClient : {
484+ createConversation : ( ) => sinon . stub ( ) ,
485+ createUploadUrl : ( ) => sinon . stub ( ) ,
486+ generatePlan : ( ) => sinon . stub ( ) ,
487+ startCodeGeneration : ( ) => sinon . stub ( ) ,
488+ getCodeGeneration : ( ) => mockGetCodeGeneration ( ) ,
489+ exportResultArchive : ( ) => sinon . stub ( ) ,
490+ } as unknown as FeatureDevClient ,
491+ workspaceRoots : [ '' ] ,
492+ uploadId : uploadID ,
493+ workspaceFolders,
494+ }
495+
496+ const codeGenState = new CodeGenState ( testConfig , getFilePaths ( controllerSetup ) , [ ] , [ ] , tabID , 0 , { } )
497+ const newSession = await createSession ( {
498+ messenger : controllerSetup . messenger ,
499+ sessionState : codeGenState ,
500+ conversationID,
501+ tabID,
502+ uploadID,
503+ scheme : featureDevScheme ,
504+ } )
505+ return newSession
506+ }
507+
508+ async function verifyException ( error : ToolkitError ) {
509+ sinon . stub ( session , 'send' ) . throws ( error )
510+
511+ await fireChatMessage ( session )
512+ await verifyMetricsCalled ( )
513+ assert . ok (
514+ sendMetricDataTelemetrySpy . calledWith (
515+ MetricDataOperationName . StartCodeGeneration ,
516+ MetricDataResult . Success
517+ )
518+ )
519+ const metricResult = getMetricResult ( error )
520+ assert . ok (
521+ sendMetricDataTelemetrySpy . calledWith ( MetricDataOperationName . EndCodeGeneration , metricResult )
522+ )
523+ }
524+
525+ async function verifyMetricsCalled ( ) {
526+ await waitUntil ( ( ) => Promise . resolve ( sendMetricDataTelemetrySpy . callCount >= 2 ) , { } )
527+ }
528+
529+ beforeEach ( async ( ) => {
530+ session = await createCodeGenState ( )
531+ sinon . stub ( session , 'preloader' ) . resolves ( )
532+ sendMetricDataTelemetrySpy = sinon . stub ( session , 'sendMetricDataTelemetry' )
533+ } )
534+
535+ it ( 'sends success operation telemetry' , async ( ) => {
536+ sinon . stub ( session , 'send' ) . resolves ( )
537+ sinon . stub ( session , 'sendLinesOfCodeGeneratedTelemetry' ) . resolves ( ) // Avoid sending extra telemetry
439538
539+ await fireChatMessage ( session )
540+ await verifyMetricsCalled ( )
541+
542+ assert . ok (
543+ sendMetricDataTelemetrySpy . calledWith (
544+ MetricDataOperationName . StartCodeGeneration ,
545+ MetricDataResult . Success
546+ )
547+ )
548+ assert . ok (
549+ sendMetricDataTelemetrySpy . calledWith (
550+ MetricDataOperationName . EndCodeGeneration ,
551+ MetricDataResult . Success
552+ )
553+ )
554+ } )
555+
556+ runs . forEach ( ( { name, error } ) => {
557+ it ( `sends failure operation telemetry on ${ name } ` , async ( ) => {
558+ await verifyException ( error )
559+ } )
560+ } )
561+ } )
562+
563+ describe ( 'processErrorChatMessage' , function ( ) {
440564 function createTestErrorMessage ( message : string ) {
441565 return createUserFacingErrorMessage ( `${ featureName } request failed: ${ message } ` )
442566 }
443567
444- async function verifyException ( error : Error ) {
568+ async function verifyException ( error : ToolkitError ) {
445569 sinon . stub ( session , 'preloader' ) . throws ( error )
446570 const sendAnswerSpy = sinon . stub ( controllerSetup . messenger , 'sendAnswer' )
447571 const sendErrorMessageSpy = sinon . stub ( controllerSetup . messenger , 'sendErrorMessage' )
448572 const sendMonthlyLimitErrorSpy = sinon . stub ( controllerSetup . messenger , 'sendMonthlyLimitError' )
449573
450- await fireChatMessage ( )
574+ await fireChatMessage ( session )
451575
452576 switch ( error . constructor . name ) {
453577 case ContentLengthError . name :
0 commit comments