Skip to content

Commit bb0c9cc

Browse files
authored
tests(explorer): add utils and examples using ECS (#2923)
## Problem * Current explorer tree tests are verbose and redundant * No tests for ECS explorer tree ## Solution * Add some utils * Add simple tests
1 parent e1bf57f commit bb0c9cc

File tree

4 files changed

+144
-1
lines changed

4 files changed

+144
-1
lines changed

src/ecs/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export class Service {
139139
}
140140
}
141141

142-
class Cluster {
142+
export class Cluster {
143143
public readonly id = this.cluster.clusterArn!
144144

145145
public constructor(private readonly client: DefaultEcsClient, private readonly cluster: ECS.Cluster) {}

src/test/ecs/model.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*!
2+
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { Cluster, Container, Service } from '../../ecs/model'
7+
import { DefaultEcsClient } from '../../shared/clients/ecsClient'
8+
import { assertChildren, assertTreeItem } from '../shared/treeview/testUtil'
9+
import { createCollectionFromPages } from '../utilities/collectionUtils'
10+
import { stub } from '../utilities/stubber'
11+
12+
const clusterData = {
13+
clusterArn: 'arn:aws:ecs:us-east-1:012345678910:cluster/my-cluster',
14+
clusterName: 'my-cluster',
15+
}
16+
17+
const serviceData = {
18+
serviceArn: 'arn:aws:ecs:us-east-1:012345678910:service/my-cluster/my-service',
19+
serviceName: 'my-service',
20+
taskDefinition: 'arn:aws:ecs:us-east-1:012345678910:task-definition/my-task:1',
21+
}
22+
23+
const containerData = {
24+
name: 'my-container',
25+
clusterArn: clusterData.clusterArn,
26+
taskRoleArn: 'arn:aws:iam::012345678910:role/my-role',
27+
}
28+
29+
const getClient = () => stub(DefaultEcsClient, { regionCode: 'us-east-1' })
30+
31+
describe('Container', function () {
32+
it('has a tree item', async function () {
33+
const container = new Container(getClient(), containerData)
34+
35+
await assertTreeItem(container, {
36+
label: containerData.name,
37+
contextValue: 'awsEcsContainerNodeExecDisabled',
38+
})
39+
})
40+
41+
it('has a tree item when "enableExecuteCommand" is set', async function () {
42+
const container = new Container(getClient(), { ...containerData, enableExecuteCommand: true })
43+
44+
await assertTreeItem(container, {
45+
label: containerData.name,
46+
contextValue: 'awsEcsContainerNodeExecEnabled',
47+
})
48+
})
49+
})
50+
51+
describe('Service', function () {
52+
it('has a tree item', async function () {
53+
const service = new Service(getClient(), serviceData)
54+
55+
await assertTreeItem(service, {
56+
label: serviceData.serviceName,
57+
contextValue: 'awsEcsServiceNode.DISABLED',
58+
})
59+
})
60+
61+
it('lists containers', async function () {
62+
const client = getClient()
63+
client.describeTaskDefinition.resolves({ taskDefinition: { containerDefinitions: [containerData] } })
64+
65+
const cluster = new Service(client, serviceData)
66+
await assertChildren(cluster, containerData.name)
67+
})
68+
})
69+
70+
describe('Cluster', function () {
71+
it('has a tree item', async function () {
72+
const cluster = new Cluster(getClient(), clusterData)
73+
74+
await assertTreeItem(cluster, {
75+
label: clusterData.clusterName,
76+
tooltip: clusterData.clusterArn,
77+
contextValue: 'awsEcsClusterNode',
78+
})
79+
})
80+
81+
it('lists services', async function () {
82+
const client = getClient()
83+
client.listServices.returns(createCollectionFromPages([serviceData]))
84+
85+
const cluster = new Cluster(client, clusterData)
86+
await assertChildren(cluster, serviceData.serviceName)
87+
})
88+
})
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 vscode from 'vscode'
7+
import * as assert from 'assert'
8+
import { selectFrom, keys } from '../../../shared/utilities/tsUtils'
9+
import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
10+
11+
type Nodeable = { toTreeNode: () => TreeNode }
12+
type Node = TreeNode | Nodeable
13+
type Matcher = string | { [P in keyof vscode.TreeItem]: vscode.TreeItem[P] }
14+
15+
const resolveNode = (node: Node) =>
16+
(node as Nodeable).toTreeNode !== undefined ? (node as Nodeable).toTreeNode() : (node as TreeNode)
17+
18+
const applyMatcher = (item: vscode.TreeItem, matcher: Matcher | undefined) =>
19+
!!matcher && typeof matcher !== 'string' ? selectFrom(item, ...keys(matcher)) : item.label
20+
21+
/**
22+
* Checks if the explorer tree node produces an item that matches the expected properties.
23+
*
24+
* Only fields specified will be checked. Everything else on the item is ignored.
25+
*/
26+
export async function assertTreeItem(model: Node, expected: Exclude<Matcher, string>): Promise<void | never> {
27+
const item = await resolveNode(model).getTreeItem()
28+
assert.deepStrictEqual(applyMatcher(item, expected), expected)
29+
}
30+
31+
/**
32+
* Checks if the explorer tree node produces the expected children.
33+
*
34+
* Children can be matched either by their label or by an object of expected properties.
35+
*/
36+
export async function assertChildren(model: Node, ...expected: Matcher[]): Promise<void | never> {
37+
const children = await resolveNode(model).getChildren?.()
38+
assert.ok(children, 'Expected tree node to have children')
39+
40+
const items = await Promise.all(children.map(child => child.getTreeItem()))
41+
const matched = items.map((item, i) => applyMatcher(item, expected[i]))
42+
assert.deepStrictEqual(matched, expected)
43+
}

src/test/utilities/collectionUtils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import { AsyncCollection, toCollection } from '../../shared/utilities/asyncCollection'
7+
68
// TODO: what is the point of this? should it live in src/shared/utilities/collectionUtils.ts ?
79
export async function* asyncGenerator<T>(items: T[]): AsyncIterableIterator<T> {
810
yield* items
911
}
12+
13+
export function createCollectionFromPages<T>(...pages: T[]): AsyncCollection<T> {
14+
return toCollection(async function* () {
15+
for (let i = 0; i < pages.length - 1; i++) {
16+
yield pages[i]
17+
}
18+
19+
return pages[pages.length - 1]
20+
})
21+
}

0 commit comments

Comments
 (0)