@@ -8,41 +8,40 @@ import * as assert from 'assert'
8
8
import * as path from 'path'
9
9
import sinon from 'sinon'
10
10
import { waitUntil } from '../../../../shared/utilities/timeoutUtils'
11
- import { ControllerSetup , createController } from '../../utils'
12
- import { ChatControllerEventEmitters } from '../../../../amazonqFeatureDev/controllers/chat/controller'
11
+ import { ControllerSetup , createController , createSession } from '../../utils'
13
12
import { FollowUpTypes , createUri } from '../../../../amazonqFeatureDev/types'
14
13
import { Session } from '../../../../amazonqFeatureDev/session/session'
15
14
import { Prompter } from '../../../../shared/ui/prompter'
16
- import { toFile } from '../../../testUtil'
15
+ import { assertTelemetry , toFile } from '../../../testUtil'
17
16
import { SelectedFolderNotInWorkspaceFolderError } from '../../../../amazonqFeatureDev/errors'
17
+ import { PrepareRefinementState } from '../../../../amazonqFeatureDev/session/sessionState'
18
+ import { FeatureDevClient } from '../../../../amazonqFeatureDev/client/featureDev'
18
19
19
20
describe ( 'Controller' , ( ) => {
20
21
const tabID = '123'
21
22
const conversationID = '456'
22
23
const uploadID = '789'
23
24
25
+ let session : Session
24
26
let controllerSetup : ControllerSetup
25
27
28
+ before ( ( ) => {
29
+ sinon . stub ( performance , 'now' ) . returns ( 0 )
30
+ } )
31
+
26
32
beforeEach ( async ( ) => {
27
- controllerSetup = await createController ( {
28
- conversationID,
29
- tabID,
30
- uploadID,
31
- } )
33
+ controllerSetup = await createController ( )
34
+ session = await createSession ( { messenger : controllerSetup . messenger , conversationID, tabID, uploadID } )
32
35
} )
33
36
34
37
afterEach ( ( ) => {
35
38
sinon . restore ( )
36
39
} )
37
40
38
41
describe ( 'openDiff' , async ( ) => {
39
- async function openDiff (
40
- controllerEventEmitter : ChatControllerEventEmitters ,
41
- filePath : string ,
42
- deleted = false
43
- ) {
42
+ async function openDiff ( filePath : string , deleted = false ) {
44
43
const executeDiff = sinon . stub ( vscode . commands , 'executeCommand' ) . returns ( Promise . resolve ( undefined ) )
45
- controllerEventEmitter . openDiff . fire ( { tabID, filePath, deleted } )
44
+ controllerSetup . emitters . openDiff . fire ( { tabID, filePath, deleted } )
46
45
47
46
// Wait until the controller has time to process the event
48
47
await waitUntil ( ( ) => {
@@ -53,7 +52,8 @@ describe('Controller', () => {
53
52
}
54
53
55
54
it ( 'uses empty file when file is not found locally' , async ( ) => {
56
- const executedDiff = await openDiff ( controllerSetup . emitters , path . join ( 'src' , 'mynewfile.js' ) )
55
+ sinon . stub ( controllerSetup . sessionStorage , 'getSession' ) . resolves ( session )
56
+ const executedDiff = await openDiff ( path . join ( 'src' , 'mynewfile.js' ) )
57
57
assert . strictEqual (
58
58
executedDiff . calledWith (
59
59
'vscode.diff' ,
@@ -62,12 +62,15 @@ describe('Controller', () => {
62
62
) ,
63
63
true
64
64
)
65
+
66
+ assertTelemetry ( 'amazonq_isReviewedChanges' , { amazonqConversationId : conversationID , enabled : true } )
65
67
} )
66
68
67
69
it ( 'uses file location when file is found locally and /src is not available' , async ( ) => {
70
+ sinon . stub ( controllerSetup . sessionStorage , 'getSession' ) . resolves ( session )
68
71
const newFileLocation = path . join ( controllerSetup . workspaceFolder . uri . fsPath , 'mynewfile.js' )
69
72
toFile ( '' , newFileLocation )
70
- const executedDiff = await openDiff ( controllerSetup . emitters , 'mynewfile.js' )
73
+ const executedDiff = await openDiff ( 'mynewfile.js' )
71
74
assert . strictEqual (
72
75
executedDiff . calledWith (
73
76
'vscode.diff' ,
@@ -76,12 +79,15 @@ describe('Controller', () => {
76
79
) ,
77
80
true
78
81
)
82
+
83
+ assertTelemetry ( 'amazonq_isReviewedChanges' , { amazonqConversationId : conversationID , enabled : true } )
79
84
} )
80
85
81
86
it ( 'uses file location when file is found locally and /src is available' , async ( ) => {
87
+ sinon . stub ( controllerSetup . sessionStorage , 'getSession' ) . resolves ( session )
82
88
const newFileLocation = path . join ( controllerSetup . workspaceFolder . uri . fsPath , 'src' , 'mynewfile.js' )
83
89
toFile ( '' , newFileLocation )
84
- const executedDiff = await openDiff ( controllerSetup . emitters , path . join ( 'src' , 'mynewfile.js' ) )
90
+ const executedDiff = await openDiff ( path . join ( 'src' , 'mynewfile.js' ) )
85
91
assert . strictEqual (
86
92
executedDiff . calledWith (
87
93
'vscode.diff' ,
@@ -90,18 +96,17 @@ describe('Controller', () => {
90
96
) ,
91
97
true
92
98
)
99
+
100
+ assertTelemetry ( 'amazonq_isReviewedChanges' , { amazonqConversationId : conversationID , enabled : true } )
93
101
} )
94
102
95
103
it ( 'uses file location when file is found locally and source folder was picked' , async ( ) => {
104
+ sinon . stub ( controllerSetup . sessionStorage , 'getSession' ) . resolves ( session )
96
105
const newFileLocation = path . join ( controllerSetup . workspaceFolder . uri . fsPath , 'foo' , 'fi' , 'mynewfile.js' )
97
106
toFile ( '' , newFileLocation )
98
107
sinon . stub ( vscode . workspace , 'getWorkspaceFolder' ) . returns ( controllerSetup . workspaceFolder )
99
- controllerSetup . session . config . sourceRoot = path . join (
100
- controllerSetup . workspaceFolder . uri . fsPath ,
101
- 'foo' ,
102
- 'fi'
103
- )
104
- const executedDiff = await openDiff ( controllerSetup . emitters , path . join ( 'foo' , 'fi' , 'mynewfile.js' ) )
108
+ session . config . sourceRoot = path . join ( controllerSetup . workspaceFolder . uri . fsPath , 'foo' , 'fi' )
109
+ const executedDiff = await openDiff ( path . join ( 'foo' , 'fi' , 'mynewfile.js' ) )
105
110
assert . strictEqual (
106
111
executedDiff . calledWith (
107
112
'vscode.diff' ,
@@ -110,17 +115,15 @@ describe('Controller', () => {
110
115
) ,
111
116
true
112
117
)
118
+
119
+ assertTelemetry ( 'amazonq_isReviewedChanges' , { amazonqConversationId : conversationID , enabled : true } )
113
120
} )
114
121
} )
115
122
116
123
describe ( 'modifyDefaultSourceFolder' , ( ) => {
117
- async function modifyDefaultSourceFolder (
118
- controllerEventEmitter : ChatControllerEventEmitters ,
119
- session : Session ,
120
- sourceRoot : string
121
- ) {
124
+ async function modifyDefaultSourceFolder ( sourceRoot : string ) {
122
125
const promptStub = sinon . stub ( Prompter . prototype , 'prompt' ) . resolves ( vscode . Uri . file ( sourceRoot ) )
123
- controllerEventEmitter . followUpClicked . fire ( {
126
+ controllerSetup . emitters . followUpClicked . fire ( {
124
127
tabID,
125
128
followUp : {
126
129
type : FollowUpTypes . ModifyDefaultSourceFolder ,
@@ -131,14 +134,12 @@ describe('Controller', () => {
131
134
await waitUntil ( ( ) => {
132
135
return Promise . resolve ( promptStub . callCount > 0 )
133
136
} , { } )
134
-
135
- return session
136
137
}
137
138
138
139
it ( 'fails if selected folder is not under a workspace folder' , async ( ) => {
139
140
sinon . stub ( vscode . workspace , 'getWorkspaceFolder' ) . returns ( undefined )
140
141
const messengerSpy = sinon . spy ( controllerSetup . messenger , 'sendAnswer' )
141
- await modifyDefaultSourceFolder ( controllerSetup . emitters , controllerSetup . session , '../../' )
142
+ await modifyDefaultSourceFolder ( '../../' )
142
143
assert . deepStrictEqual (
143
144
messengerSpy . calledWith ( {
144
145
tabID,
@@ -158,23 +159,84 @@ describe('Controller', () => {
158
159
} )
159
160
160
161
it ( 'accepts valid source folders under a workspace root' , async ( ) => {
161
- const controllerSetup = await createController ( {
162
+ sinon . stub ( controllerSetup . sessionStorage , 'getSession' ) . resolves ( session )
163
+ sinon . stub ( vscode . workspace , 'getWorkspaceFolder' ) . returns ( controllerSetup . workspaceFolder )
164
+ const expectedSourceRoot = path . join ( controllerSetup . workspaceFolder . uri . fsPath , 'src' )
165
+ await modifyDefaultSourceFolder ( expectedSourceRoot )
166
+ assert . strictEqual ( session . config . sourceRoot , expectedSourceRoot )
167
+ assert . strictEqual ( session . config . workspaceRoot , controllerSetup . workspaceFolder . uri . fsPath )
168
+ } )
169
+ } )
170
+
171
+ describe ( 'processChatItemVotedMessage' , ( ) => {
172
+ async function processChatItemVotedMessage ( vote : 'upvote' | 'downvote' ) {
173
+ const initialState = new PrepareRefinementState (
174
+ {
175
+ conversationId : conversationID ,
176
+ proxyClient : new FeatureDevClient ( ) ,
177
+ sourceRoot : '' ,
178
+ workspaceRoot : '' ,
179
+ } ,
180
+ '' ,
181
+ tabID
182
+ )
183
+ const newSession = await createSession ( {
184
+ messenger : controllerSetup . messenger ,
185
+ sessionState : initialState ,
162
186
conversationID,
163
187
tabID,
164
188
uploadID,
165
189
} )
166
- sinon . stub ( vscode . workspace , 'getWorkspaceFolder' ) . returns ( controllerSetup . workspaceFolder )
167
- const expectedSourceRoot = path . join ( controllerSetup . workspaceFolder . uri . fsPath , 'src' )
168
- const modifiedSourceFolderSession = await modifyDefaultSourceFolder (
169
- controllerSetup . emitters ,
170
- controllerSetup . session ,
171
- expectedSourceRoot
172
- )
173
- assert . strictEqual ( modifiedSourceFolderSession . config . sourceRoot , expectedSourceRoot )
174
- assert . strictEqual (
175
- modifiedSourceFolderSession . config . workspaceRoot ,
176
- controllerSetup . workspaceFolder . uri . fsPath
177
- )
190
+ const getSessionStub = sinon . stub ( controllerSetup . sessionStorage , 'getSession' ) . resolves ( newSession )
191
+ controllerSetup . emitters . processChatItemVotedMessage . fire ( {
192
+ tabID,
193
+ messageID : '' ,
194
+ vote,
195
+ } )
196
+
197
+ // Wait until the controller has time to process the event
198
+ await waitUntil ( ( ) => {
199
+ return Promise . resolve ( getSessionStub . callCount > 0 )
200
+ } , { } )
201
+ }
202
+
203
+ it ( 'incoming upvoted message sends telemetry' , async ( ) => {
204
+ await processChatItemVotedMessage ( 'upvote' )
205
+
206
+ assertTelemetry ( 'amazonq_approachThumbsUp' , { amazonqConversationId : conversationID , result : 'Succeeded' } )
207
+ } )
208
+
209
+ it ( 'incoming downvoted message sends telemetry' , async ( ) => {
210
+ await processChatItemVotedMessage ( 'downvote' )
211
+
212
+ assertTelemetry ( 'amazonq_approachThumbsDown' , {
213
+ amazonqConversationId : conversationID ,
214
+ result : 'Succeeded' ,
215
+ } )
216
+ } )
217
+ } )
218
+
219
+ describe ( 'newPlan' , ( ) => {
220
+ async function newPlanClicked ( ) {
221
+ const getSessionStub = sinon . stub ( controllerSetup . sessionStorage , 'getSession' ) . resolves ( session )
222
+
223
+ controllerSetup . emitters . followUpClicked . fire ( {
224
+ tabID,
225
+ followUp : {
226
+ type : FollowUpTypes . NewPlan ,
227
+ } ,
228
+ } )
229
+
230
+ // Wait until the controller has time to process the event
231
+ await waitUntil ( ( ) => {
232
+ return Promise . resolve ( getSessionStub . callCount > 0 )
233
+ } , { } )
234
+ }
235
+
236
+ it ( 'end chat telemetry is sent' , async ( ) => {
237
+ await newPlanClicked ( )
238
+
239
+ assertTelemetry ( 'amazonq_endChat' , { amazonqConversationId : conversationID , result : 'Succeeded' } )
178
240
} )
179
241
} )
180
242
} )
0 commit comments