Skip to content

Commit fce9f84

Browse files
dhasani23karanA-aws
authored andcommitted
test(amazonq): more tests for /transform aws#6183
1 parent ca3ba22 commit fce9f84

File tree

10 files changed

+434
-40
lines changed

10 files changed

+434
-40
lines changed

packages/amazonq/test/e2e/amazonq/framework/messenger.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ export class Messenger {
5959
this.mynahUIProps.onFollowUpClicked(this.tabID, lastChatItem?.messageId ?? '', option[0])
6060
}
6161

62+
clickCustomFormButton(action: { id: string; text?: string; formItemValues?: Record<string, string> }) {
63+
if (!this.mynahUIProps.onCustomFormAction) {
64+
assert.fail('onCustomFormAction must be defined to use it in the tests')
65+
}
66+
67+
this.mynahUIProps.onCustomFormAction(this.tabID, action)
68+
}
69+
6270
clickFileActionButton(filePath: string, actionName: string) {
6371
if (!this.mynahUIProps.onFileActionClick) {
6472
assert.fail('onFileActionClick must be defined to use it in the tests')
@@ -173,7 +181,9 @@ export class Messenger {
173181

174182
// Do another check just in case the waitUntil time'd out
175183
if (!event()) {
176-
assert.fail(`Event has not finished loading in: ${this.waitTimeoutInMs} ms`)
184+
assert.fail(
185+
`Event has not finished loading in: ${waitOverrides ? waitOverrides.waitTimeoutInMs : this.waitTimeoutInMs} ms`
186+
)
177187
}
178188
}
179189

packages/amazonq/test/e2e/amazonq/transformByQ.test.ts

+347
Large diffs are not rendered by default.

packages/core/src/amazonqGumby/chat/controller/controller.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ import {
5656
} from '../../../shared/telemetry/telemetry'
5757
import { MetadataResult } from '../../../shared/telemetry/telemetryClient'
5858
import { CodeTransformTelemetryState } from '../../telemetry/codeTransformTelemetryState'
59-
import { getAuthType } from '../../../codewhisperer/service/transformByQ/transformApiHandler'
6059
import DependencyVersions from '../../models/dependencies'
6160
import { getStringHash } from '../../../shared/utilities/textUtilities'
6261
import { getVersionData } from '../../../codewhisperer/service/transformByQ/transformMavenHandler'
6362
import AdmZip from 'adm-zip'
6463
import { AuthError } from '../../../auth/sso/server'
64+
import { getAuthType } from '../../../auth/utils'
6565

6666
// These events can be interactions within the chat,
6767
// or elsewhere in the IDE

packages/core/src/amazonqGumby/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66
export { activate } from './activation'
77
export { default as DependencyVersions } from './models/dependencies'
88
export { default as MessengerUtils } from './chat/controller/messenger/messengerUtils'
9+
export { GumbyController } from './chat/controller/controller'
10+
export { TabsStorage } from '../amazonq/webview/ui/storages/tabsStorage'
11+
export * as startTransformByQ from '../../src/codewhisperer/commands/startTransformByQ'
912
export * from './errors'

packages/core/src/auth/utils.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { formatError, ToolkitError } from '../shared/errors'
1818
import { asString } from './providers/credentials'
1919
import { TreeNode } from '../shared/treeview/resourceTreeDataProvider'
2020
import { createInputBox } from '../shared/ui/inputPrompter'
21-
import { telemetry } from '../shared/telemetry/telemetry'
21+
import { CredentialSourceId, telemetry } from '../shared/telemetry/telemetry'
2222
import { createCommonButtons, createExitButton, createHelpButton, createRefreshButton } from '../shared/ui/buttons'
2323
import { getIdeProperties, isAmazonQ, isCloud9 } from '../shared/extensionUtilities'
2424
import { addScopes, getDependentAuths } from './secondaryAuth'
@@ -45,7 +45,7 @@ import { Commands, placeholder } from '../shared/vscode/commands2'
4545
import { Auth } from './auth'
4646
import { validateIsNewSsoUrl, validateSsoUrlFormat } from './sso/validation'
4747
import { getLogger } from '../shared/logger'
48-
import { isValidAmazonQConnection, isValidCodeWhispererCoreConnection } from '../codewhisperer/util/authUtil'
48+
import { AuthUtil, isValidAmazonQConnection, isValidCodeWhispererCoreConnection } from '../codewhisperer/util/authUtil'
4949
import { AuthFormId } from '../login/webview/vue/types'
5050
import { extensionVersion } from '../shared/vscode/env'
5151
import { ExtStartUpSources } from '../shared/telemetry'
@@ -798,3 +798,13 @@ export function initializeCredentialsProviderManager() {
798798
manager.addProviderFactory(new SharedCredentialsProviderFactory())
799799
manager.addProviders(new Ec2CredentialsProvider(), new EcsCredentialsProvider(), new EnvVarsCredentialsProvider())
800800
}
801+
802+
export async function getAuthType() {
803+
let authType: CredentialSourceId | undefined = undefined
804+
if (AuthUtil.instance.isEnterpriseSsoInUse() && AuthUtil.instance.isConnectionValid()) {
805+
authType = 'iamIdentityCenter'
806+
} else if (AuthUtil.instance.isBuilderIdInUse() && AuthUtil.instance.isConnectionValid()) {
807+
authType = 'awsId'
808+
}
809+
return authType
810+
}

packages/core/src/codewhisperer/commands/startTransformByQ.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,9 @@ export async function parseBuildFile() {
328328
export async function preTransformationUploadCode() {
329329
await vscode.commands.executeCommand('aws.amazonq.transformationHub.focus')
330330

331-
void vscode.window.showInformationMessage(CodeWhispererConstants.jobStartedNotification)
331+
void vscode.window.showInformationMessage(CodeWhispererConstants.jobStartedNotification, {
332+
title: CodeWhispererConstants.jobStartedTitle,
333+
})
332334

333335
let uploadId = ''
334336
throwIfCancelled()
@@ -848,7 +850,9 @@ export async function postTransformationJob() {
848850
}
849851

850852
if (transformByQState.isSucceeded()) {
851-
void vscode.window.showInformationMessage(CodeWhispererConstants.jobCompletedNotification(diffMessage))
853+
void vscode.window.showInformationMessage(CodeWhispererConstants.jobCompletedNotification(diffMessage), {
854+
title: CodeWhispererConstants.transformationCompletedTitle,
855+
})
852856
} else if (transformByQState.isPartiallySucceeded()) {
853857
void vscode.window
854858
.showInformationMessage(

packages/core/src/codewhisperer/models/constants.ts

+4
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,8 @@ export const noOngoingJobMessage = 'No ongoing job.'
554554

555555
export const nothingToShowMessage = 'Nothing to show'
556556

557+
export const jobStartedTitle = 'Transformation started'
558+
557559
export const jobStartedNotification =
558560
'Amazon Q is transforming your code. It can take 10 to 30 minutes to upgrade your code, depending on the size of your project. To monitor progress, go to the Transformation Hub.'
559561

@@ -636,6 +638,8 @@ export const jobCancelledChatMessage =
636638

637639
export const jobCancelledNotification = 'You cancelled the transformation.'
638640

641+
export const transformationCompletedTitle = 'Transformation complete'
642+
639643
export const diffMessage = (multipleDiffs: boolean) => {
640644
return multipleDiffs
641645
? 'You can review the diffs to see my proposed changes and accept or reject them. You will be able to accept changes from one diff at a time. If you reject changes in one diff, you will not be able to view or accept changes in the other diffs.'

packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts

+2-12
Original file line numberDiff line numberDiff line change
@@ -34,38 +34,28 @@ import {
3434
import { sleep } from '../../../shared/utilities/timeoutUtils'
3535
import AdmZip from 'adm-zip'
3636
import globals from '../../../shared/extensionGlobals'
37-
import { CredentialSourceId, telemetry } from '../../../shared/telemetry/telemetry'
37+
import { telemetry } from '../../../shared/telemetry/telemetry'
3838
import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState'
3939
import { calculateTotalLatency } from '../../../amazonqGumby/telemetry/codeTransformTelemetry'
4040
import { MetadataResult } from '../../../shared/telemetry/telemetryClient'
4141
import request from '../../../shared/request'
4242
import { JobStoppedError, ZipExceedsSizeLimitError } from '../../../amazonqGumby/errors'
4343
import { writeLogs } from './transformFileHandler'
44-
import { AuthUtil } from '../../util/authUtil'
4544
import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient'
4645
import { downloadExportResultArchive } from '../../../shared/utilities/download'
4746
import { ExportIntent, TransformationDownloadArtifactType } from '@amzn/codewhisperer-streaming'
4847
import fs from '../../../shared/fs/fs'
4948
import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession'
5049
import { encodeHTML } from '../../../shared/utilities/textUtilities'
5150
import { convertToTimeString } from '../../../shared/datetime'
51+
import { getAuthType } from '../../../auth/utils'
5252

5353
export function getSha256(buffer: Buffer) {
5454
const hasher = crypto.createHash('sha256')
5555
hasher.update(buffer)
5656
return hasher.digest('base64')
5757
}
5858

59-
export async function getAuthType() {
60-
let authType: CredentialSourceId | undefined = undefined
61-
if (AuthUtil.instance.isEnterpriseSsoInUse() && AuthUtil.instance.isConnectionValid()) {
62-
authType = 'iamIdentityCenter'
63-
} else if (AuthUtil.instance.isBuilderIdInUse() && AuthUtil.instance.isConnectionValid()) {
64-
authType = 'awsId'
65-
}
66-
return authType
67-
}
68-
6959
export function throwIfCancelled() {
7060
if (transformByQState.isCancelled()) {
7161
throw new TransformByQStoppedError()

packages/core/src/test/codewhisperer/commands/transformByQ.test.ts

+43-3
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
import assert, { fail } from 'assert'
77
import * as vscode from 'vscode'
88
import * as sinon from 'sinon'
9-
import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities'
109
import { DB, transformByQState, TransformByQStoppedError } from '../../../codewhisperer/models/model'
1110
import {
11+
finalizeTransformationJob,
1212
parseBuildFile,
13+
setMaven,
1314
stopTransformByQ,
1415
validateSQLMetadataFile,
1516
} from '../../../codewhisperer/commands/startTransformByQ'
@@ -40,14 +41,14 @@ import {
4041
} from '../../../codewhisperer/service/transformByQ/transformProjectValidationHandler'
4142
import { TransformationCandidateProject, ZipManifest } from '../../../codewhisperer/models/model'
4243
import globals from '../../../shared/extensionGlobals'
43-
import { fs } from '../../../shared'
44+
import { env, fs } from '../../../shared'
4445
import { convertDateToTimestamp, convertToTimeString } from '../../../shared/datetime'
4546

4647
describe('transformByQ', function () {
4748
let tempDir: string
4849

4950
beforeEach(async function () {
50-
tempDir = await makeTemporaryToolkitFolder()
51+
tempDir = (await TestFolder.create()).path
5152
transformByQState.setToNotStarted()
5253
})
5354

@@ -149,6 +150,22 @@ describe('transformByQ', function () {
149150
sinon.assert.calledWithExactly(stopJobStub, { transformationJobId: 'dummyId' })
150151
})
151152

153+
it('WHEN stopTransformByQ called with job that has already terminated THEN stop API not called', async function () {
154+
const stopJobStub = sinon.stub(codeWhisperer.codeWhispererClient, 'codeModernizerStopCodeTransformation')
155+
transformByQState.setToSucceeded()
156+
await stopTransformByQ('abc-123')
157+
sinon.assert.notCalled(stopJobStub)
158+
})
159+
160+
it('WHEN finalizeTransformationJob on failed job THEN error thrown and error message fields are set', async function () {
161+
await assert.rejects(async () => {
162+
await finalizeTransformationJob('FAILED')
163+
})
164+
assert.notStrictEqual(transformByQState.getJobFailureErrorChatMessage(), undefined)
165+
assert.notStrictEqual(transformByQState.getJobFailureErrorNotification(), undefined)
166+
transformByQState.setJobDefaults() // reset error messages to undefined
167+
})
168+
152169
it('WHEN polling completed job THEN returns status as completed', async function () {
153170
const mockJobResponse = {
154171
$response: {
@@ -208,6 +225,16 @@ describe('transformByQ', function () {
208225
assert.deepStrictEqual(actual, expected)
209226
})
210227

228+
it(`WHEN transforming a project with a Windows Maven executable THEN mavenName set correctly`, async function () {
229+
sinon.stub(env, 'isWin').returns(true)
230+
const tempFileName = 'mvnw.cmd'
231+
const tempFilePath = path.join(tempDir, tempFileName)
232+
await toFile('', tempFilePath)
233+
transformByQState.setProjectPath(tempDir)
234+
await setMaven()
235+
assert.strictEqual(transformByQState.getMavenName(), '.\\mvnw.cmd')
236+
})
237+
211238
it(`WHEN zip created THEN manifest.json contains test-compile custom build command`, async function () {
212239
const tempFileName = `testfile-${globals.clock.Date.now()}.zip`
213240
transformByQState.setProjectPath(tempDir)
@@ -234,6 +261,19 @@ describe('transformByQ', function () {
234261
})
235262
})
236263

264+
it('WHEN zipCode THEN ZIP contains all expected files and no unexpected files', async function () {
265+
const zipFilePath = path.join(tempDir, 'test.zip')
266+
const zip = new AdmZip()
267+
await fs.writeFile(path.join(tempDir, 'pom.xml'), 'dummy pom.xml')
268+
zip.addLocalFile(path.join(tempDir, 'pom.xml'))
269+
zip.addFile('manifest.json', Buffer.from(JSON.stringify({ version: '1.0' })))
270+
zip.writeZip(zipFilePath)
271+
const zipFiles = new AdmZip(zipFilePath).getEntries()
272+
const zipFileNames = zipFiles.map((file) => file.name)
273+
assert.strictEqual(zipFileNames.length, 2) // expecting only pom.xml and manifest.json
274+
assert.strictEqual(zipFileNames.includes('pom.xml') && zipFileNames.includes('manifest.json'), true)
275+
})
276+
237277
it(`WHEN zip created THEN dependencies contains no .sha1 or .repositories files`, async function () {
238278
const m2Folders = [
239279
'com/groupid1/artifactid1/version1',

packages/core/src/testE2E/amazonqGumby/transformByQ.test.ts renamed to packages/core/src/testInteg/amazonQTransform/transformByQ.test.ts

+5-19
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ import * as codeWhisperer from '../../codewhisperer/client/codewhisperer'
1010
import assert from 'assert'
1111
import { getSha256, uploadArtifactToS3, zipCode } from '../../codewhisperer/service/transformByQ/transformApiHandler'
1212
import request from '../../shared/request'
13-
import AdmZip from 'adm-zip'
14-
import { setValidConnection } from '../util/connection'
1513
import { transformByQState, ZipManifest } from '../../codewhisperer/models/model'
1614
import globals from '../../shared/extensionGlobals'
1715
import { fs } from '../../shared'
16+
import { setValidConnection } from '../../testE2E/util/connection'
1817

1918
describe('transformByQ', async function () {
2019
let tempDir = ''
@@ -35,10 +34,6 @@ describe('transformByQ', async function () {
3534
await fs.writeFile(tempFilePath, 'sample content for the test file')
3635
transformByQState.setProjectPath(tempDir)
3736
const zipCodeResult = await zipCode({
38-
dependenciesFolder: {
39-
path: tempFilePath,
40-
name: tempFileName,
41-
},
4237
projectPath: tempDir,
4338
zipManifest: new ZipManifest(),
4439
})
@@ -87,7 +82,7 @@ describe('transformByQ', async function () {
8782
)
8883
})
8984

90-
it('WHEN createUploadUrl THEN URL uses HTTPS and sets 60 second expiration', async function () {
85+
it('WHEN createUploadUrl THEN URL uses HTTPS and sets 30 minute expiration', async function () {
9186
const buffer = Buffer.from(await fs.readFileBytes(zippedCodePath))
9287
const sha256 = getSha256(buffer)
9388
const response = await codeWhisperer.codeWhispererClient.createUploadUrl({
@@ -96,17 +91,8 @@ describe('transformByQ', async function () {
9691
uploadIntent: CodeWhispererConstants.uploadIntent,
9792
})
9893
const uploadUrl = response.uploadUrl
99-
const usesHttpsAndExpiresAfter60Seconds = uploadUrl.includes('https') && uploadUrl.includes('X-Amz-Expires=60')
100-
assert.strictEqual(usesHttpsAndExpiresAfter60Seconds, true)
101-
})
102-
103-
it('WHEN zipCode THEN ZIP contains all expected files and no unexpected files', async function () {
104-
const zipFiles = new AdmZip(zippedCodePath).getEntries()
105-
const zipFileNames: string[] = []
106-
zipFiles.forEach((file) => {
107-
zipFileNames.push(file.name)
108-
})
109-
assert.strictEqual(zipFileNames.length, 2) // expecting only a dummy txt file and a manifest.json
110-
assert.strictEqual(zipFileNames.includes(tempFileName) && zipFileNames.includes('manifest.json'), true)
94+
const usesHttpsAndExpiresAfter30Minutes =
95+
uploadUrl.includes('https') && uploadUrl.includes('X-Amz-Expires=1800')
96+
assert.strictEqual(usesHttpsAndExpiresAfter30Minutes, true)
11197
})
11298
})

0 commit comments

Comments
 (0)