@@ -9,7 +9,13 @@ import * as path from 'path'
9
9
import sinon from 'sinon'
10
10
import { waitUntil } from '../../../../shared/utilities/timeoutUtils'
11
11
import { 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'
13
19
import { Session } from '../../../../amazonqFeatureDev/session/session'
14
20
import { Prompter } from '../../../../shared/ui/prompter'
15
21
import { assertTelemetry , toFile } from '../../../testUtil'
@@ -36,6 +42,7 @@ import { AuthUtil } from '../../../../codewhisperer'
36
42
import { featureDevScheme , featureName , messageWithConversationId } from '../../../../amazonqFeatureDev'
37
43
import { i18n } from '../../../../shared/i18n-helper'
38
44
import { FollowUpTypes } from '../../../../amazonq/commons/types'
45
+ import { ToolkitError } from '../../../../shared'
39
46
40
47
let mockGetCodeGeneration : sinon . SinonStub
41
48
describe ( 'Controller' , ( ) => {
@@ -395,7 +402,47 @@ describe('Controller', () => {
395
402
} )
396
403
397
404
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 ) {
399
446
const getSessionStub = sinon . stub ( controllerSetup . sessionStorage , 'getSession' ) . resolves ( session )
400
447
401
448
controllerSetup . emitters . processHumanChatMessage . fire ( {
@@ -410,44 +457,121 @@ describe('Controller', () => {
410
457
} , { } )
411
458
}
412
459
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
439
538
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 ( ) {
440
564
function createTestErrorMessage ( message : string ) {
441
565
return createUserFacingErrorMessage ( `${ featureName } request failed: ${ message } ` )
442
566
}
443
567
444
- async function verifyException ( error : Error ) {
568
+ async function verifyException ( error : ToolkitError ) {
445
569
sinon . stub ( session , 'preloader' ) . throws ( error )
446
570
const sendAnswerSpy = sinon . stub ( controllerSetup . messenger , 'sendAnswer' )
447
571
const sendErrorMessageSpy = sinon . stub ( controllerSetup . messenger , 'sendErrorMessage' )
448
572
const sendMonthlyLimitErrorSpy = sinon . stub ( controllerSetup . messenger , 'sendMonthlyLimitError' )
449
573
450
- await fireChatMessage ( )
574
+ await fireChatMessage ( session )
451
575
452
576
switch ( error . constructor . name ) {
453
577
case ContentLengthError . name :
0 commit comments