Skip to content

Commit 6b38987

Browse files
committed
chore: add doc telemetry unit tests
1 parent ad7aa01 commit 6b38987

File tree

3 files changed

+614
-0
lines changed

3 files changed

+614
-0
lines changed
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import * as vscode from 'vscode'
6+
import sinon from 'sinon'
7+
import {
8+
assertTelemetry,
9+
ControllerSetup,
10+
createController,
11+
createExpectedEvent,
12+
createSession,
13+
EventMetrics,
14+
FollowUpSequences,
15+
generateVirtualMemoryUri,
16+
updateFilePaths,
17+
} from './utils'
18+
import { CurrentWsFolders, NewFileInfo } from '../../amazonqDoc/types'
19+
import { CodeGenState, docScheme, Session } from '../../amazonqDoc'
20+
import { AuthUtil } from '../../codewhisperer'
21+
import { FeatureDevClient } from '../../amazonqFeatureDev'
22+
import { waitUntil } from '../../shared'
23+
import { FollowUpTypes } from '../../amazonq/commons/types'
24+
import { FileSystem } from '../../shared/fs/fs'
25+
import { ReadmeBuilder } from './mockContent'
26+
27+
describe('Controller - Doc Generation', () => {
28+
const tabID = '123'
29+
const conversationID = '456'
30+
const uploadID = '789'
31+
32+
let controllerSetup: ControllerSetup
33+
let session: Session
34+
let sendDocTelemetrySpy: sinon.SinonStub
35+
let mockGetCodeGeneration: sinon.SinonStub
36+
let getSessionStub: sinon.SinonStub
37+
let modifiedReadme: string
38+
const generatedReadme = ReadmeBuilder.createBaseReadme()
39+
40+
const getFilePaths = (controllerSetup: ControllerSetup): NewFileInfo[] => [
41+
{
42+
zipFilePath: 'README.md',
43+
relativePath: 'README.md',
44+
fileContent: generatedReadme,
45+
rejected: false,
46+
virtualMemoryUri: generateVirtualMemoryUri(uploadID, 'README.md', docScheme),
47+
workspaceFolder: controllerSetup.workspaceFolder,
48+
changeApplied: false,
49+
},
50+
]
51+
52+
async function createCodeGenState() {
53+
mockGetCodeGeneration = sinon.stub().resolves({ codeGenerationStatus: { status: 'Complete' } })
54+
55+
const workspaceFolders = [controllerSetup.workspaceFolder] as CurrentWsFolders
56+
const testConfig = {
57+
conversationId: conversationID,
58+
proxyClient: {
59+
createConversation: () => sinon.stub(),
60+
createUploadUrl: () => sinon.stub(),
61+
generatePlan: () => sinon.stub(),
62+
startCodeGeneration: () => sinon.stub(),
63+
getCodeGeneration: () => mockGetCodeGeneration(),
64+
exportResultArchive: () => sinon.stub(),
65+
} as unknown as FeatureDevClient,
66+
workspaceRoots: [''],
67+
uploadId: uploadID,
68+
workspaceFolders,
69+
}
70+
71+
const codeGenState = new CodeGenState(testConfig, getFilePaths(controllerSetup), [], [], tabID, 0, {})
72+
return createSession({
73+
messenger: controllerSetup.messenger,
74+
sessionState: codeGenState,
75+
conversationID,
76+
tabID,
77+
uploadID,
78+
scheme: docScheme,
79+
})
80+
}
81+
async function fireFollowUps(followUpTypes: FollowUpTypes[]) {
82+
for (const type of followUpTypes) {
83+
controllerSetup.emitters.followUpClicked.fire({
84+
tabID,
85+
followUp: { type },
86+
})
87+
}
88+
}
89+
90+
async function waitForStub(stub: sinon.SinonStub) {
91+
await waitUntil(() => Promise.resolve(stub.callCount > 0), {})
92+
}
93+
94+
async function performAction(
95+
action: 'generate' | 'update' | 'makeChanges' | 'accept',
96+
getSessionStub: sinon.SinonStub,
97+
message?: string
98+
) {
99+
const sequences = {
100+
generate: FollowUpSequences.generateReadme,
101+
update: FollowUpSequences.updateReadme,
102+
makeChanges: FollowUpSequences.makeChanges,
103+
accept: FollowUpSequences.acceptContent,
104+
}
105+
106+
await fireFollowUps(sequences[action])
107+
108+
if (action === 'makeChanges' && message) {
109+
controllerSetup.emitters.processHumanChatMessage.fire({
110+
tabID,
111+
conversationID,
112+
message,
113+
})
114+
}
115+
116+
await waitForStub(getSessionStub)
117+
}
118+
119+
before(() => {
120+
sinon.stub(performance, 'now').returns(0)
121+
})
122+
123+
beforeEach(async () => {
124+
controllerSetup = await createController()
125+
session = await createCodeGenState()
126+
sendDocTelemetrySpy = sinon.stub(session, 'sendDocTelemetryEvent').resolves()
127+
sinon.stub(session, 'preloader').resolves()
128+
sinon.stub(session, 'send').resolves()
129+
Object.defineProperty(session, '_conversationId', {
130+
value: conversationID,
131+
writable: true,
132+
configurable: true,
133+
})
134+
135+
sinon.stub(AuthUtil.instance, 'getChatAuthState').resolves({
136+
codewhispererCore: 'connected',
137+
codewhispererChat: 'connected',
138+
amazonQ: 'connected',
139+
})
140+
sinon.stub(FileSystem.prototype, 'exists').resolves(true)
141+
getSessionStub = sinon.stub(controllerSetup.sessionStorage, 'getSession').resolves(session)
142+
modifiedReadme = ReadmeBuilder.createReadmeWithRepoStructure()
143+
sinon
144+
.stub(vscode.workspace, 'openTextDocument')
145+
.callsFake(async (options?: string | vscode.Uri | { language?: string; content?: string }) => {
146+
let path = ''
147+
if (typeof options === 'string') {
148+
path = options
149+
} else if (options && 'path' in options) {
150+
path = options.path
151+
}
152+
153+
return {
154+
getText: () => (path.includes('/tmp/aws-toolkit-vscode') ? generatedReadme : modifiedReadme),
155+
} as any
156+
})
157+
})
158+
afterEach(() => {
159+
sinon.restore()
160+
})
161+
162+
it('should emit generation telemetry for initial README generation', async () => {
163+
await performAction('generate', getSessionStub)
164+
165+
const expectedEvent = createExpectedEvent({
166+
type: 'generation',
167+
...EventMetrics.INITIAL_README,
168+
interactionType: 'GENERATE_README',
169+
conversationId: conversationID,
170+
})
171+
172+
await assertTelemetry({
173+
spy: sendDocTelemetrySpy,
174+
expectedEvent,
175+
type: 'generation',
176+
})
177+
})
178+
it('should emit another generation telemetry for make changes operation after initial README generation', async () => {
179+
await performAction('generate', getSessionStub)
180+
const firstExpectedEvent = createExpectedEvent({
181+
type: 'generation',
182+
...EventMetrics.INITIAL_README,
183+
interactionType: 'GENERATE_README',
184+
conversationId: conversationID,
185+
})
186+
187+
await assertTelemetry({
188+
spy: sendDocTelemetrySpy,
189+
expectedEvent: firstExpectedEvent,
190+
type: 'generation',
191+
})
192+
193+
await updateFilePaths(session, modifiedReadme, uploadID, docScheme, controllerSetup.workspaceFolder)
194+
await performAction('makeChanges', getSessionStub, 'add repository structure section')
195+
196+
const secondExpectedEvent = createExpectedEvent({
197+
type: 'generation',
198+
...EventMetrics.REPO_STRUCTURE,
199+
interactionType: 'GENERATE_README',
200+
conversationId: conversationID,
201+
})
202+
203+
await assertTelemetry({
204+
spy: sendDocTelemetrySpy,
205+
expectedEvent: secondExpectedEvent,
206+
type: 'generation',
207+
callIndex: 1,
208+
})
209+
})
210+
211+
it('should emit acceptance telemetry for README generation', async () => {
212+
await performAction('generate', getSessionStub)
213+
await new Promise((resolve) => setTimeout(resolve, 100))
214+
const expectedEvent = createExpectedEvent({
215+
type: 'acceptance',
216+
...EventMetrics.INITIAL_README,
217+
interactionType: 'GENERATE_README',
218+
conversationId: conversationID,
219+
})
220+
221+
await performAction('accept', getSessionStub)
222+
await assertTelemetry({
223+
spy: sendDocTelemetrySpy,
224+
expectedEvent,
225+
type: 'acceptance',
226+
callIndex: 1,
227+
})
228+
})
229+
it('should emit generation telemetry for README update', async () => {
230+
await performAction('update', getSessionStub)
231+
232+
const expectedEvent = createExpectedEvent({
233+
type: 'generation',
234+
...EventMetrics.REPO_STRUCTURE,
235+
interactionType: 'UPDATE_README',
236+
conversationId: conversationID,
237+
})
238+
239+
await assertTelemetry({
240+
spy: sendDocTelemetrySpy,
241+
expectedEvent,
242+
type: 'generation',
243+
})
244+
})
245+
it('should emit another generation telemetry for make changes operation after README update', async () => {
246+
await performAction('update', getSessionStub)
247+
await new Promise((resolve) => setTimeout(resolve, 100))
248+
249+
modifiedReadme = ReadmeBuilder.createReadmeWithDataFlow()
250+
await updateFilePaths(session, modifiedReadme, uploadID, docScheme, controllerSetup.workspaceFolder)
251+
252+
await performAction('makeChanges', getSessionStub, 'add data flow section')
253+
254+
const expectedEvent = createExpectedEvent({
255+
type: 'generation',
256+
...EventMetrics.DATA_FLOW,
257+
interactionType: 'UPDATE_README',
258+
conversationId: conversationID,
259+
callIndex: 1,
260+
})
261+
262+
await assertTelemetry({
263+
spy: sendDocTelemetrySpy,
264+
expectedEvent,
265+
type: 'generation',
266+
callIndex: 1,
267+
})
268+
})
269+
270+
it('should emit acceptance telemetry for README update', async () => {
271+
await performAction('update', getSessionStub)
272+
await new Promise((resolve) => setTimeout(resolve, 100))
273+
274+
const expectedEvent = createExpectedEvent({
275+
type: 'acceptance',
276+
...EventMetrics.REPO_STRUCTURE,
277+
interactionType: 'UPDATE_README',
278+
conversationId: conversationID,
279+
})
280+
281+
await performAction('accept', getSessionStub)
282+
await assertTelemetry({
283+
spy: sendDocTelemetrySpy,
284+
expectedEvent,
285+
type: 'acceptance',
286+
callIndex: 1,
287+
})
288+
})
289+
})
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
export const ReadmeSections = {
6+
HEADER: `# My Awesome Project
7+
8+
This is a demo project showcasing various features and capabilities.`,
9+
10+
GETTING_STARTED: `## Getting Started
11+
1. Clone the repository
12+
2. Run npm install
13+
3. Start the application`,
14+
15+
FEATURES: `## Features
16+
- Fast processing
17+
- Easy to use
18+
- Well documented`,
19+
20+
LICENSE: '## License\nMIT License',
21+
22+
REPO_STRUCTURE: `## Repository Structure
23+
/src
24+
/components
25+
/utils
26+
/tests
27+
/unit
28+
/docs`,
29+
30+
DATA_FLOW: `## Data Flow
31+
1. Input processing
32+
- Data validation
33+
- Format conversion
34+
2. Core processing
35+
- Business logic
36+
- Data transformation
37+
3. Output generation
38+
- Result formatting
39+
- Response delivery`,
40+
} as const
41+
42+
export class ReadmeBuilder {
43+
private sections: string[] = []
44+
45+
addSection(section: string): this {
46+
this.sections.push(section)
47+
return this
48+
}
49+
50+
build(): string {
51+
return this.sections.join('\n\n')
52+
}
53+
54+
static createBaseReadme(): string {
55+
return new ReadmeBuilder()
56+
.addSection(ReadmeSections.HEADER)
57+
.addSection(ReadmeSections.GETTING_STARTED)
58+
.addSection(ReadmeSections.FEATURES)
59+
.addSection(ReadmeSections.LICENSE)
60+
.build()
61+
}
62+
63+
static createReadmeWithRepoStructure(): string {
64+
return new ReadmeBuilder()
65+
.addSection(ReadmeSections.HEADER)
66+
.addSection(ReadmeSections.REPO_STRUCTURE)
67+
.addSection(ReadmeSections.GETTING_STARTED)
68+
.addSection(ReadmeSections.FEATURES)
69+
.addSection(ReadmeSections.LICENSE)
70+
.build()
71+
}
72+
73+
static createReadmeWithDataFlow(): string {
74+
return new ReadmeBuilder()
75+
.addSection(ReadmeSections.HEADER)
76+
.addSection(ReadmeSections.GETTING_STARTED)
77+
.addSection(ReadmeSections.FEATURES)
78+
.addSection(ReadmeSections.DATA_FLOW)
79+
.addSection(ReadmeSections.LICENSE)
80+
.build()
81+
}
82+
}

0 commit comments

Comments
 (0)