Skip to content

Commit b539eb0

Browse files
tests(codecatalyst): start basic integration suite (#3207)
## Problem We want to ensure that Code Catalyst apis work as expected. If only we had integration tests that could do this. ## Solution This commit creates integ tests that test the CC APIs this extension uses: * creating a cc project * creating a cc dev env * connecting to the dev env through ssh One thing to note is that the builderid/sso configuration is required to be setup prior to this test running (this test will fail until that component is implemented in our CI, but is being worked on separately).
1 parent aea503e commit b539eb0

File tree

3 files changed

+242
-3
lines changed

3 files changed

+242
-3
lines changed

src/codecatalyst/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class CodeCatalystAuthStorage {
3636
}
3737
}
3838

39-
const isValidCodeCatalystConnection = (conn: Connection): conn is SsoConnection =>
39+
export const isValidCodeCatalystConnection = (conn: Connection): conn is SsoConnection =>
4040
isBuilderIdConnection(conn) && hasScopes(conn, codecatalystScopes)
4141

4242
export class CodeCatalystAuthenticationProvider {

src/codecatalyst/commands.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { ToolkitError } from '../shared/errors'
2222
import { telemetry } from '../shared/telemetry/telemetry'
2323
import { showConfirmationMessage } from '../shared/utilities/messages'
2424
import { AccountStatus } from '../shared/telemetry/telemetryClient'
25-
import { CreateDevEnvironmentRequest } from 'aws-sdk/clients/codecatalyst'
25+
import { CreateDevEnvironmentRequest, UpdateDevEnvironmentRequest } from 'aws-sdk/clients/codecatalyst'
2626

2727
/** "List CodeCatalyst Commands" command. */
2828
export async function listCommands(): Promise<void> {
@@ -111,10 +111,15 @@ export type DevEnvironmentSettings = Pick<
111111
'alias' | 'instanceType' | 'inactivityTimeoutMinutes' | 'persistentStorage'
112112
>
113113

114+
export type UpdateDevEnvironmentSettings = Pick<
115+
UpdateDevEnvironmentRequest,
116+
'alias' | 'instanceType' | 'inactivityTimeoutMinutes'
117+
>
118+
114119
export async function updateDevEnv(
115120
client: CodeCatalystClient,
116121
devenv: DevEnvironmentId,
117-
settings: DevEnvironmentSettings
122+
settings: UpdateDevEnvironmentSettings
118123
) {
119124
return client.updateDevEnvironment({
120125
...settings,
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*!
2+
* Copyright 2023 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 {
8+
CodeCatalystClient,
9+
CodeCatalystOrg,
10+
CodeCatalystProject,
11+
createClient as createCodeCatalystClient,
12+
DevEnvironment,
13+
} from '../../shared/clients/codecatalystClient'
14+
import { DeleteDevEnvironmentResponse } from 'aws-sdk/clients/codecatalyst'
15+
import { prepareDevEnvConnection } from '../../codecatalyst/model'
16+
import { Auth, SsoConnection } from '../../credentials/auth'
17+
import { CodeCatalystAuthenticationProvider, isValidCodeCatalystConnection } from '../../codecatalyst/auth'
18+
import { CodeCatalystCommands, DevEnvironmentSettings } from '../../codecatalyst/commands'
19+
import globals from '../../shared/extensionGlobals'
20+
21+
let spaceName: CodeCatalystOrg['name']
22+
let projectName: CodeCatalystProject['name']
23+
let auth: Auth
24+
25+
/**
26+
* Key Information:
27+
*
28+
* This test can be run locally under the following conditions:
29+
*
30+
* - You have previously configured BuilderId using the vscode extension.
31+
* This is because this test uses BuilderId/SSO information
32+
* that the extension would have naturally created in that process.
33+
*
34+
* - A Space already exists in your Code Catalyst account.
35+
* We cannot currently automate this due to space creation
36+
* not being available in the Code Catalyst API.
37+
*
38+
* TODO: Create a new space instead of using an existing one,
39+
* if the api eventually allows us to do so.
40+
*
41+
* - You do not have a Code Catalyst project with the same name
42+
* as {@link projectName}. This project may be modified when
43+
* this test is run.
44+
*
45+
* The test project cannot be torn down.
46+
*
47+
* - The code catalyst api does not provide a way to delete
48+
* a Project. So the project will exist in the Space unless
49+
* deleted manually.
50+
*
51+
* TODO: Delete the project when the test is done,
52+
* if the api eventually allows us to do so.
53+
*
54+
* This provides a best effort to test the api functionality.
55+
*
56+
* - There are more complexities involved in testing api functionality
57+
* as if we were using the vscode UI, so this tests the underlying CC
58+
* api functionliaty.
59+
*
60+
* TODO: We can borrow some components from `src/test/globalSetup.test.ts`
61+
* to be able to leverage `getTestWindow()` and then use that
62+
* for UI actions testing.
63+
*
64+
* TODO: Create separate tests that spin up a vscode extensionHost
65+
* integ tests, but using the ssh hostname that we get from
66+
* {@link prepareDevEnvConnection}.
67+
*/
68+
describe('Test how this codebase uses the Code Catalyst API', function () {
69+
let client: CodeCatalystClient
70+
let commands: CodeCatalystCommands
71+
72+
before(async function () {
73+
if (process.env['AWS_TOOLKIT_AUTOMATION'] !== 'local') {
74+
this.skip()
75+
}
76+
77+
commands = await createTestCodeCatalystCommands()
78+
79+
auth = Auth.instance
80+
client = await createTestCodeCatalystClient()
81+
spaceName = (await getCurrentUsersSpace()).name
82+
projectName = (await tryCreateTestProject(spaceName)).name
83+
await deleteExistingDevEnvs(projectName)
84+
})
85+
86+
/**
87+
* Returns the existing Sso connection that has been
88+
* verified to work with Code Catalyst.
89+
*
90+
* This relies on SSO information already being configured.
91+
*/
92+
async function getCodeCatalystSsoConnection(): Promise<SsoConnection> {
93+
const builderIdSsoConnection = (await auth.listConnections()).find(isValidCodeCatalystConnection)
94+
assert.ok(builderIdSsoConnection, 'To fix, setup Builder Id as if you were a user of the extension.')
95+
return builderIdSsoConnection
96+
}
97+
98+
/**
99+
* Creates a code catalyst commands instance.
100+
*
101+
* This holds the underlying functions that are triggered
102+
* when the user interacts with the vscode UI model.
103+
*
104+
* The goal is to test using this object as much as we can,
105+
* so we can test as close as we can to the user experience.
106+
* This can have interactions with vscode UI model.
107+
*/
108+
async function createTestCodeCatalystCommands(): Promise<CodeCatalystCommands> {
109+
const ccAuthProvider = CodeCatalystAuthenticationProvider.fromContext(globals.context)
110+
return new CodeCatalystCommands(ccAuthProvider)
111+
}
112+
113+
/**
114+
* Creates a code catalyst api client.
115+
*/
116+
async function createTestCodeCatalystClient(): Promise<CodeCatalystClient> {
117+
const conn = await getCodeCatalystSsoConnection()
118+
return await createCodeCatalystClient(conn)
119+
}
120+
121+
/**
122+
* Creates a specific CC Project if it does not already exist in the space.
123+
*/
124+
async function tryCreateTestProject(spaceName: CodeCatalystOrg['name']): Promise<CodeCatalystProject> {
125+
// IMPORTANT: Be careful changing this if you plan to run locally.
126+
const projectName = 'aws-vscode-toolkit-integ-test-project'
127+
128+
return client.createProject({
129+
spaceName,
130+
displayName: projectName,
131+
description: 'This project is autogenerated by the AWS Toolkit VSCode Integ Test.',
132+
})
133+
}
134+
135+
/**
136+
* Gets the first code catalyst space it finds.
137+
*
138+
* The intention for this is to require no setup of a Space by the
139+
* user if they want to run this test locally.
140+
*/
141+
async function getCurrentUsersSpace(): Promise<CodeCatalystOrg> {
142+
const firstPageOfSpaces = (await client.listSpaces().iterator().next()).value
143+
144+
if (firstPageOfSpaces === undefined || firstPageOfSpaces.length === 0) {
145+
// Space must already exist due to CC not providing an api to create a space.
146+
throw new Error(
147+
'No spaces found in account. A Code Catalyst Space must be created manually before running this test.'
148+
)
149+
}
150+
151+
const firstSpaceFound = firstPageOfSpaces[0]
152+
return firstSpaceFound
153+
}
154+
155+
/**
156+
* Deletes any existing dev envs from the given project.
157+
*/
158+
async function deleteExistingDevEnvs(projectName: CodeCatalystProject['name']): Promise<void> {
159+
const currentDevEnvs = await client
160+
.listDevEnvironments({ name: projectName, org: { name: spaceName }, type: 'project' })
161+
.flatten()
162+
.promise()
163+
await Promise.all(currentDevEnvs.map(async devEnv => deleteDevEnv(devEnv.id)))
164+
}
165+
166+
/**
167+
* Deletes a specific dev env
168+
*/
169+
function deleteDevEnv(id: DevEnvironment['id']): Promise<DeleteDevEnvironmentResponse> {
170+
return client.deleteDevEnvironment({
171+
spaceName,
172+
projectName,
173+
id: id,
174+
})
175+
}
176+
177+
describe('Dev Environment apis', function () {
178+
let devEnv: DevEnvironment
179+
let devEnvSettings: DevEnvironmentSettings
180+
181+
function createDevEnv(): Promise<DevEnvironment> {
182+
devEnvSettings = {
183+
instanceType: 'dev.standard1.small',
184+
persistentStorage: { sizeInGiB: 16 },
185+
alias: `test-alias-${Date.now()}`,
186+
}
187+
return client.createDevEnvironment({
188+
spaceName,
189+
projectName,
190+
ides: [{ name: 'VSCode' }],
191+
...devEnvSettings,
192+
})
193+
}
194+
195+
before(async function () {
196+
devEnv = await createDevEnv()
197+
})
198+
199+
after(async function () {
200+
await deleteDevEnv(devEnv.id)
201+
})
202+
203+
it('can succesfully create a Dev Environment', async function () {
204+
assert.strictEqual(devEnv.project.name, projectName)
205+
assert.strictEqual(devEnv.org.name, spaceName)
206+
})
207+
208+
it('can ssh in to a Dev Environment', async function () {
209+
// Get necessary objects to run the ssh command.
210+
const { SessionProcess, hostname, sshPath } = await prepareDevEnvConnection(client, { ...devEnv })
211+
212+
// Through ssh, run 'ls' command in the dev env.
213+
const lsOutput = (await new SessionProcess(sshPath, [hostname, 'ls', '/projects']).run()).stdout
214+
215+
// Assert that a certain file exists in the dev env.
216+
const expectedFile = 'devfile.yaml'
217+
assert(lsOutput.split('\n').includes(expectedFile)) // File automatically created by CC
218+
})
219+
220+
it('can update an existing dev environment', async function () {
221+
// Ensure current alias name is expected
222+
assert.strictEqual(devEnv.alias, devEnvSettings.alias)
223+
assert.strictEqual(devEnv.instanceType, devEnvSettings.instanceType)
224+
225+
// Update dev env
226+
const devEnvSettingsToUpdate = { alias: `test-alias-${Date.now()}`, instanceType: 'dev.standard1.medium' }
227+
const updatedDevEnv = await commands.updateDevEnv(devEnv, devEnvSettingsToUpdate)
228+
229+
// Ensure our update succeeded by checking for new properties
230+
assert.strictEqual(updatedDevEnv?.alias, devEnvSettingsToUpdate.alias)
231+
assert.strictEqual(updatedDevEnv?.instanceType, devEnvSettingsToUpdate.instanceType)
232+
})
233+
})
234+
})

0 commit comments

Comments
 (0)