Skip to content

Commit 3ca596a

Browse files
authored
tests: add codewhisperer integration tests (#3176)
PR adding CodeWhisperer integration tests. Adds integration test scenarios listed under 'Testing scenarios' in client side ops doc here. Note: * To run tests, use VSC launch config (select Integration Tests option and click RUN AND DEBUG - see screenshot below). Tests do not work with Integration Tests (current file) option. * User must have a valid CodeWhisperer auth connection prior to running tests. If the user does not have a valid auth connection or if the tests are run using npm scripts, these integration tests will skip.
1 parent 40a7aa6 commit 3ca596a

File tree

5 files changed

+494
-1
lines changed

5 files changed

+494
-1
lines changed

src/codewhisperer/util/authUtil.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { getCodeCatalystDevEnvId } from '../../shared/vscode/env'
2828

2929
export const awsBuilderIdSsoProfile = createBuilderIdProfile()
3030
// No connections are valid within C9
31-
const isValidCodeWhispererConnection = (conn: Connection): conn is SsoConnection =>
31+
export const isValidCodeWhispererConnection = (conn: Connection): conn is SsoConnection =>
3232
!isCloud9() && isSsoConnection(conn) && hasScopes(conn, codewhispererScopes)
3333

3434
export class AuthUtil {
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*!
2+
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as assert from 'assert'
7+
import * as codewhispererClient from '../../../codewhisperer/client/codewhisperer'
8+
import { ConfigurationEntry } from '../../../codewhisperer/models/model'
9+
import { setValidConnection, skiptTestIfNoValidConn } from '../../util/codewhispererUtil'
10+
import { RecommendationHandler } from '../../../codewhisperer/service/recommendationHandler'
11+
import { createMockTextEditor, resetCodeWhispererGlobalVariables } from '../../../test/codewhisperer/testUtil'
12+
import { invokeRecommendation } from '../../../codewhisperer/commands/invokeRecommendation'
13+
14+
/*
15+
New model deployment may impact references returned.
16+
17+
These tests:
18+
1) are not required for github approval flow
19+
2) will be auto-skipped until fix for manual runs is posted.
20+
*/
21+
22+
const leftContext = `InAuto.GetContent(
23+
InAuto.servers.auto, "vendors.json",
24+
function (data) {
25+
let block = '';
26+
for(let i = 0; i < data.length; i++) {
27+
block += '<a href="' + data`
28+
29+
const rightContext = `[i].title + '">' + cars[i].title + '</a>';
30+
}
31+
$('#cars').html(block);
32+
});`
33+
34+
describe('CodeWhisperer service invocation', async function () {
35+
let validConnection: boolean
36+
const client = new codewhispererClient.DefaultCodeWhispererClient()
37+
const configWithRefs: ConfigurationEntry = {
38+
isShowMethodsEnabled: true,
39+
isManualTriggerEnabled: true,
40+
isAutomatedTriggerEnabled: true,
41+
isSuggestionsWithCodeReferencesEnabled: true,
42+
}
43+
const configWithNoRefs: ConfigurationEntry = {
44+
isShowMethodsEnabled: true,
45+
isManualTriggerEnabled: true,
46+
isAutomatedTriggerEnabled: true,
47+
isSuggestionsWithCodeReferencesEnabled: false,
48+
}
49+
50+
before(async function () {
51+
validConnection = await setValidConnection()
52+
})
53+
54+
beforeEach(function () {
55+
resetCodeWhispererGlobalVariables()
56+
RecommendationHandler.instance.clearRecommendations()
57+
//TODO: remove this line (this.skip()) when these tests no longer auto-skipped
58+
this.skip()
59+
//valid connection required to run tests
60+
skiptTestIfNoValidConn(validConnection, this)
61+
62+
})
63+
64+
it('trigger known to return recs with references returns rec with reference', async function () {
65+
//check that handler is empty before invocation
66+
const requestIdBefore = RecommendationHandler.instance.requestId
67+
const sessionIdBefore = RecommendationHandler.instance.sessionId
68+
const validRecsBefore = RecommendationHandler.instance.isValidResponse()
69+
70+
assert.ok(requestIdBefore.length === 0)
71+
assert.ok(sessionIdBefore.length === 0)
72+
assert.ok(!validRecsBefore)
73+
74+
const doc = leftContext + rightContext
75+
const filename = 'test.js'
76+
const language = 'javascript'
77+
const line = 5
78+
const character = 39
79+
const mockEditor = createMockTextEditor(doc, filename, language, line, character)
80+
81+
await invokeRecommendation(mockEditor, client, configWithRefs)
82+
83+
const requestId = RecommendationHandler.instance.requestId
84+
const sessionId = RecommendationHandler.instance.sessionId
85+
const validRecs = RecommendationHandler.instance.isValidResponse()
86+
const references = RecommendationHandler.instance.recommendations[0].references
87+
88+
assert.ok(requestId.length > 0)
89+
assert.ok(sessionId.length > 0)
90+
assert.ok(validRecs)
91+
assert.ok(references !== undefined)
92+
//TODO: uncomment this assert when this test is no longer auto-skipped
93+
// assert.ok(references.length > 0)
94+
})
95+
96+
//This test will fail if user is logged in with IAM identity center
97+
it('trigger known to return rec with references does not return rec with references when reference tracker setting is off', async function () {
98+
//check that handler is empty before invocation
99+
const requestIdBefore = RecommendationHandler.instance.requestId
100+
const sessionIdBefore = RecommendationHandler.instance.sessionId
101+
const validRecsBefore = RecommendationHandler.instance.isValidResponse()
102+
103+
assert.ok(requestIdBefore.length === 0)
104+
assert.ok(sessionIdBefore.length === 0)
105+
assert.ok(!validRecsBefore)
106+
107+
const doc = leftContext + rightContext
108+
const filename = 'test.js'
109+
const language = 'javascript'
110+
const line = 5
111+
const character = 39
112+
const mockEditor = createMockTextEditor(doc, filename, language, line, character)
113+
114+
await invokeRecommendation(mockEditor, client, configWithNoRefs)
115+
116+
const requestId = RecommendationHandler.instance.requestId
117+
const sessionId = RecommendationHandler.instance.sessionId
118+
const validRecs = RecommendationHandler.instance.isValidResponse()
119+
120+
assert.ok(requestId.length > 0)
121+
assert.ok(sessionId.length > 0)
122+
//no recs returned because example request returns 1 rec with reference, so no recs returned when references off
123+
assert.ok(!validRecs)
124+
})
125+
})
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/*!
2+
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as assert from 'assert'
7+
import * as vscode from 'vscode'
8+
import * as codewhispererClient from '../../../codewhisperer/client/codewhisperer'
9+
import * as CodeWhispererConstants from '../../../codewhisperer/models/constants'
10+
import * as path from 'path'
11+
import * as testutil from '../../../test/testUtil'
12+
import * as fs from 'fs-extra'
13+
import { setValidConnection, skiptTestIfNoValidConn } from '../../util/codewhispererUtil'
14+
import { resetCodeWhispererGlobalVariables } from '../../../test/codewhisperer/testUtil'
15+
import { getTestWorkspaceFolder } from '../../integrationTestsUtilities'
16+
import { closeAllEditors } from '../../../test/testUtil'
17+
import { DependencyGraphFactory } from '../../../codewhisperer/util/dependencyGraph/dependencyGraphFactory'
18+
import { statSync } from 'fs'
19+
import {
20+
getPresignedUrlAndUpload,
21+
createScanJob,
22+
pollScanJobStatus,
23+
listScanResults,
24+
} from '../../../codewhisperer/service/securityScanHandler'
25+
import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities'
26+
27+
const filePromptWithSecurityIssues = `from flask import app
28+
29+
def execute_input_noncompliant():
30+
from flask import request
31+
module_version = request.args.get("module_version")
32+
# Noncompliant: executes unsanitized inputs.
33+
exec("import urllib%s as urllib" % module_version)
34+
35+
def execute_input_compliant():
36+
from flask import request
37+
module_version = request.args.get("module_version")
38+
# Compliant: executes sanitized inputs.
39+
exec("import urllib%d as urllib" % int(module_version))`
40+
41+
const largePrompt = 'a'.repeat(CodeWhispererConstants.codeScanPythonPayloadSizeLimitBytes + 1)
42+
43+
const javaPromptNoBuild = `class HelloWorld {
44+
public static void main(String[] args) {
45+
System.out.println("Hello World!");
46+
}
47+
}`
48+
49+
describe('CodeWhisperer security scan', async function () {
50+
let validConnection: boolean
51+
let tempFolder: string
52+
const client = new codewhispererClient.DefaultCodeWhispererClient()
53+
const workspaceFolder = getTestWorkspaceFolder()
54+
55+
before(async function () {
56+
validConnection = await setValidConnection()
57+
})
58+
59+
beforeEach(function () {
60+
resetCodeWhispererGlobalVariables()
61+
//valid connection required to run tests
62+
skiptTestIfNoValidConn(validConnection, this)
63+
})
64+
65+
afterEach(async function () {
66+
if(tempFolder !== undefined){
67+
await fs.remove(tempFolder)
68+
}
69+
})
70+
71+
after(function () {
72+
closeAllEditors()
73+
})
74+
75+
const openTestFile = async (filePath: string) => {
76+
const doc = await vscode.workspace.openTextDocument(filePath)
77+
return await vscode.window.showTextDocument(doc, {
78+
selection: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 1)),
79+
})
80+
}
81+
82+
function getDependencyGraph(languageId: string) {
83+
return DependencyGraphFactory.getDependencyGraph(languageId)
84+
}
85+
86+
/*
87+
securityJobSetup: combines steps 1 and 2 in startSecurityScan:
88+
89+
Step 1: Generate context truncations
90+
Step 2: Get presigned Url, upload and clean up
91+
92+
returns artifactMap and projectPath
93+
*/
94+
async function securityJobSetup(editor: vscode.TextEditor) {
95+
const dependencyGraph = getDependencyGraph(editor.document.languageId)
96+
if (dependencyGraph === undefined) {
97+
throw new Error(`"${editor.document.languageId}" is not supported for security scan.`)
98+
}
99+
const uri = dependencyGraph.getRootFile(editor)
100+
101+
if (dependencyGraph.reachSizeLimit(statSync(uri.fsPath).size)) {
102+
throw new Error(
103+
`Selected file larger than ${dependencyGraph.getReadableSizeLimit()}. Try a different file.`
104+
)
105+
}
106+
const projectPath = dependencyGraph.getProjectPath(uri)
107+
const truncation = await dependencyGraph.generateTruncationWithTimeout(
108+
uri,
109+
CodeWhispererConstants.contextTruncationTimeoutSeconds
110+
)
111+
112+
let artifactMap
113+
try {
114+
artifactMap = await getPresignedUrlAndUpload(client, truncation)
115+
} finally {
116+
dependencyGraph.removeTmpFiles(truncation)
117+
}
118+
return {
119+
artifactMap: artifactMap,
120+
projectPath: projectPath,
121+
}
122+
}
123+
124+
it('codescan request with valid input params and no security issues completes scan and returns no recommendations', async function () {
125+
//set up file and editor
126+
const appRoot = path.join(workspaceFolder, 'python3.7-plain-sam-app')
127+
const appCodePath = path.join(appRoot, 'hello_world', 'app.py')
128+
const editor = await openTestFile(appCodePath)
129+
130+
//run security scan
131+
const securityJobSetupResult = await securityJobSetup(editor)
132+
const artifactMap = securityJobSetupResult.artifactMap
133+
const projectPath = securityJobSetupResult.projectPath
134+
135+
//get job status and result
136+
const scanJob = await createScanJob(client, artifactMap, editor.document.languageId)
137+
const jobStatus = await pollScanJobStatus(client, scanJob.jobId)
138+
const securityRecommendationCollection = await listScanResults(
139+
client,
140+
scanJob.jobId,
141+
CodeWhispererConstants.codeScanFindingsSchema,
142+
projectPath
143+
)
144+
145+
assert.deepStrictEqual(jobStatus, 'Completed')
146+
assert.ok(securityRecommendationCollection.length === 0)
147+
})
148+
149+
it('codescan request with valid input params and security issues completes scan and returns recommendations', async function () {
150+
//set up file and editor
151+
tempFolder = await makeTemporaryToolkitFolder()
152+
const tempFile = path.join(tempFolder, 'test.py')
153+
testutil.toFile(filePromptWithSecurityIssues, tempFile)
154+
const editor = await openTestFile(tempFile)
155+
156+
//run security scan
157+
const securityJobSetupResult = await securityJobSetup(editor)
158+
const artifactMap = securityJobSetupResult.artifactMap
159+
const projectPath = securityJobSetupResult.projectPath
160+
const scanJob = await createScanJob(client, artifactMap, editor.document.languageId)
161+
162+
//get job status and result
163+
const jobStatus = await pollScanJobStatus(client, scanJob.jobId)
164+
const securityRecommendationCollection = await listScanResults(
165+
client,
166+
scanJob.jobId,
167+
CodeWhispererConstants.codeScanFindingsSchema,
168+
projectPath
169+
)
170+
171+
assert.deepStrictEqual(jobStatus, 'Completed')
172+
assert.ok(securityRecommendationCollection.length === 1)
173+
})
174+
175+
it('codescan request on file that is too large causes scan job setup to fail', async function () {
176+
tempFolder = await makeTemporaryToolkitFolder()
177+
const tempFile = path.join(tempFolder, 'test2.py')
178+
testutil.toFile(largePrompt, tempFile)
179+
const editor = await openTestFile(tempFile)
180+
181+
await assert.rejects(() => securityJobSetup(editor))
182+
})
183+
184+
it('codescan request on java file with no build causes scan job setup to fail', async function () {
185+
tempFolder = await makeTemporaryToolkitFolder()
186+
const tempFile = path.join(tempFolder, 'test.java')
187+
testutil.toFile(javaPromptNoBuild, tempFile)
188+
const editor = await openTestFile(tempFile)
189+
190+
await assert.rejects(() => securityJobSetup(editor))
191+
})
192+
193+
it('codescan request for file in unsupported language fails to generate dependency graph and causes scan setup to fail', async function () {
194+
const appRoot = path.join(workspaceFolder, 'go1-plain-sam-app')
195+
const appCodePath = path.join(appRoot, 'hello-world', 'main.go')
196+
const editor = await openTestFile(appCodePath)
197+
const dependencyGraph = getDependencyGraph(editor.document.languageId)
198+
199+
assert.strictEqual(dependencyGraph, undefined)
200+
await assert.rejects(() => securityJobSetup(editor))
201+
})
202+
})

0 commit comments

Comments
 (0)