Skip to content

Commit b627d4a

Browse files
committed
test(notifications): add e2e tests
1 parent 87ea4d9 commit b627d4a

File tree

12 files changed

+250
-21
lines changed

12 files changed

+250
-21
lines changed

packages/amazonq/src/extensionNode.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import { Auth, AuthUtils, getTelemetryMetadataForConn, isAnySsoConnection } from
1818
import api from './api'
1919
import { activate as activateCWChat } from './app/chat/activation'
2020
import { beta } from 'aws-core-vscode/dev'
21-
import { activate as activateNotifications } from 'aws-core-vscode/notifications'
21+
import {
22+
RemoteFetcher,
23+
activate as activateNotifications,
24+
deactivate as deactivateNotifications,
25+
} from 'aws-core-vscode/notifications'
2226
import { AuthState, AuthUtil } from 'aws-core-vscode/codewhisperer'
2327
import { telemetry, AuthUserState } from 'aws-core-vscode/telemetry'
2428

@@ -75,10 +79,13 @@ async function activateAmazonQNode(context: vscode.ExtensionContext) {
7579
...authState,
7680
})
7781

78-
await activateNotifications(context, authState, getAuthState)
82+
await activateNotifications(context, authState, getAuthState, {
83+
fetcher: new RemoteFetcher(),
84+
storageKey: 'aws.notifications',
85+
})
7986
}
8087

81-
async function getAuthState(): Promise<Omit<AuthUserState, 'source'>> {
88+
export async function getAuthState(): Promise<Omit<AuthUserState, 'source'>> {
8289
let authState: AuthState = 'disconnected'
8390
try {
8491
// May call connection validate functions that try to refresh the token.
@@ -147,4 +154,5 @@ async function setupDevMode(context: vscode.ExtensionContext) {
147154
export async function deactivate() {
148155
// Run concurrently to speed up execution. stop() does not throw so it is safe
149156
await Promise.all([(await CrashMonitoring.instance())?.shutdown(), deactivateCommon()])
157+
deactivateNotifications()
150158
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0 fort
4+
*/
5+
6+
import { getNotificationsE2ESuite } from 'aws-core-vscode/test'
7+
import { getAuthState } from '../../../src/extensionNode'
8+
9+
getNotificationsE2ESuite(getAuthState)

packages/core/src/extensionNode.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import * as beta from './dev/beta'
3939
import { activate as activateApplicationComposer } from './applicationcomposer/activation'
4040
import { activate as activateRedshift } from './awsService/redshift/activation'
4141
import { activate as activateIamPolicyChecks } from './awsService/accessanalyzer/activation'
42-
import { activate as activateNotifications } from './notifications/activation'
42+
import { activate as activateNotifications, deactivate as deactivateNotifications } from './notifications/activation'
4343
import { SchemaService } from './shared/schemas'
4444
import { AwsResourceManager } from './dynamicResources/awsResourceManager'
4545
import globals from './shared/extensionGlobals'
@@ -60,6 +60,7 @@ import { activate as activateThreatComposerEditor } from './threatComposer/activ
6060
import { isSsoConnection, hasScopes } from './auth/connection'
6161
import { CrashMonitoring, setContext } from './shared'
6262
import { AuthFormId } from './login/webview/vue/types'
63+
import { RemoteFetcher } from './notifications/controller'
6364

6465
let localize: nls.LocalizeFunc
6566

@@ -246,7 +247,10 @@ export async function activate(context: vscode.ExtensionContext) {
246247
...authState,
247248
})
248249

249-
await activateNotifications(context, authState, getAuthState)
250+
await activateNotifications(context, authState, getAuthState, {
251+
fetcher: new RemoteFetcher(),
252+
storageKey: 'aws.notifications',
253+
})
250254
} catch (error) {
251255
const stacktrace = (error as Error).stack?.split('\n')
252256
// truncate if the stacktrace is unusually long
@@ -270,6 +274,7 @@ export async function deactivate() {
270274
// Run concurrently to speed up execution. stop() does not throw so it is safe
271275
await Promise.all([await (await CrashMonitoring.instance())?.shutdown(), deactivateCommon(), deactivateEc2()])
272276
await globals.resourceManager.dispose()
277+
deactivateNotifications()
273278
}
274279

275280
async function handleAmazonQInstall() {
@@ -338,7 +343,7 @@ function recordToolkitInitialization(activationStartedOn: number, settingsValid:
338343
}
339344
}
340345

341-
async function getAuthState(): Promise<Omit<AuthUserState, 'source'>> {
346+
export async function getAuthState(): Promise<Omit<AuthUserState, 'source'>> {
342347
let authStatus: AuthStatus = 'notConnected'
343348
const enabledConnections: Set<AuthFormId> = new Set()
344349
const enabledScopes: Set<string> = new Set()

packages/core/src/notifications/activation.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import * as vscode from 'vscode'
77
import { DevSettings } from '../shared/settings'
8-
import { NotificationsController } from './controller'
8+
import { NotificationsController, ControllerOptions } from './controller'
99
import { NotificationsNode } from './panelNode'
1010
import { RuleEngine, getRuleContext } from './rules'
1111
import globals from '../shared/extensionGlobals'
@@ -15,6 +15,7 @@ import { oneMinute } from '../shared/datetime'
1515

1616
/** Time in MS to poll for emergency notifications */
1717
const emergencyPollTime = oneMinute * 10
18+
let interval: NodeJS.Timer
1819

1920
/**
2021
* Activate the in-IDE notifications module and begin receiving notifications.
@@ -26,7 +27,8 @@ const emergencyPollTime = oneMinute * 10
2627
export async function activate(
2728
context: vscode.ExtensionContext,
2829
initialState: AuthState,
29-
authStateFn: () => Promise<AuthState>
30+
authStateFn: () => Promise<AuthState>,
31+
options: Omit<ControllerOptions, 'node'>
3032
) {
3133
// TODO: Currently gated behind feature-flag.
3234
if (!DevSettings.instance.get('notifications', false)) {
@@ -36,16 +38,25 @@ export async function activate(
3638
const panelNode = NotificationsNode.instance
3739
panelNode.registerView(context)
3840

39-
const controller = new NotificationsController(panelNode)
41+
const controller = new NotificationsController({ node: panelNode, ...options })
4042
const engine = new RuleEngine(await getRuleContext(context, initialState))
4143

4244
await controller.pollForStartUp(engine)
4345
await controller.pollForEmergencies(engine)
4446

45-
globals.clock.setInterval(async () => {
47+
if (interval !== undefined) {
48+
globals.clock.clearInterval(interval)
49+
}
50+
51+
interval = globals.clock.setInterval(async () => {
4652
const ruleContext = await getRuleContext(context, await authStateFn())
4753
await controller.pollForEmergencies(new RuleEngine(ruleContext))
4854
}, emergencyPollTime)
4955

5056
getLogger('notifications').debug('Activated in-IDE notifications polling module')
5157
}
58+
59+
export function deactivate() {
60+
globals.clock.clearInterval(interval)
61+
getLogger('notifications').debug('Deactivated in-IDE notifications polling module')
62+
}

packages/core/src/notifications/controller.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ import { FileResourceFetcher } from '../shared/resourcefetcher/fileResourceFetch
2626
import { isAmazonQ } from '../shared/extensionUtilities'
2727
import { telemetry } from '../shared/telemetry/telemetry'
2828

29+
export type ControllerOptions = {
30+
node: NotificationsNode
31+
storageKey: globalKey
32+
fetcher: NotificationFetcher
33+
}
34+
2935
/**
3036
* Handles fetching and maintaining the state of in-IDE notifications.
3137
* Notifications are constantly polled from a known endpoint and then stored in global state.
@@ -40,23 +46,25 @@ import { telemetry } from '../shared/telemetry/telemetry'
4046
*/
4147
export class NotificationsController {
4248
public static readonly suggestedPollIntervalMs = 1000 * 60 * 10 // 10 minutes
49+
public readonly storageKey: globalKey
4350

4451
/** Internal memory state that is written to global state upon modification. */
4552
private readonly state: NotificationsState
53+
private readonly notificationsNode: NotificationsNode
54+
private readonly fetcher: NotificationFetcher
4655

4756
static #instance: NotificationsController | undefined
4857

49-
constructor(
50-
private readonly notificationsNode: NotificationsNode,
51-
private readonly fetcher: NotificationFetcher = new RemoteFetcher(),
52-
public readonly storageKey: globalKey = 'aws.notifications'
53-
) {
58+
constructor(options: ControllerOptions) {
5459
if (!NotificationsController.#instance) {
5560
// Register on first creation only.
5661
registerDismissCommand()
5762
}
5863
NotificationsController.#instance = this
5964

65+
this.notificationsNode = options.node
66+
this.storageKey = options.storageKey
67+
this.fetcher = options.fetcher
6068
this.state = this.getDefaultState()
6169
}
6270

packages/core/src/notifications/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
export { activate } from './activation'
6+
export { activate, deactivate } from './activation'
7+
export { RemoteFetcher } from './controller'

packages/core/src/notifications/rules.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,13 @@ export class RuleEngine {
117117
if (condition.additionalCriteria) {
118118
for (const criteria of condition.additionalCriteria) {
119119
if (!this.evaluateRule(criteria)) {
120-
logger.verbose('notification id: (%s) did NOT pass criteria check: %O', id, criteria)
120+
// We want to see nested objects. It is not deep.
121+
// eslint-disable-next-line aws-toolkits/no-json-stringify-in-log
122+
logger.verbose(
123+
'notification id: (%s) did NOT pass criteria check: %s',
124+
id,
125+
JSON.stringify(criteria)
126+
)
121127
return false
122128
}
123129
logger.debug('notification id: (%s) passed criteria check: %O', id, criteria)
@@ -201,7 +207,9 @@ export async function getRuleContext(context: vscode.ExtensionContext, authState
201207
}
202208

203209
const { activeExtensions, ...loggableRuleContext } = ruleContext
204-
logger.debug('getRuleContext() determined rule context: %O', loggableRuleContext)
210+
// We want to see the nested objects. It is not deep.
211+
// eslint-disable-next-line aws-toolkits/no-json-stringify-in-log
212+
logger.debug('getRuleContext() determined rule context: %s', JSON.stringify(loggableRuleContext))
205213

206214
return ruleContext
207215
}

packages/core/src/test/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export { getTestLogger } from './globalSetup.test'
1919
export { testCommand } from './shared/vscode/testUtils'
2020
export { FakeAwsContext } from './utilities/fakeAwsContext'
2121
export { getTestWorkspaceFolder } from '../testInteg/integrationTestsUtilities'
22+
export { getNotificationsE2ESuite } from '../testE2E/notifications/notifications.test'
2223
export * from './codewhisperer/testUtil'
2324
export * from './credentials/testUtil'
2425
export * from './testUtil'

packages/core/src/test/notifications/controller.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,13 @@ import {
2626
import { HttpResourceFetcher } from '../../shared/resourcefetcher/httpResourceFetcher'
2727
import { NotificationsNode } from '../../notifications/panelNode'
2828
import { RuleEngine } from '../../notifications/rules'
29+
import { globalKey } from '../../shared/globalState'
2930

3031
// one test node to use across different tests
3132
export const panelNode: NotificationsNode = NotificationsNode.instance
3233

34+
const storageKey = 'aws.notifications.test' as globalKey
35+
3336
describe('Notifications Controller', function () {
3437
const ruleEngine: RuleEngine = new RuleEngine({
3538
ideVersion: '1.83.0',
@@ -90,7 +93,7 @@ describe('Notifications Controller', function () {
9093
beforeEach(async function () {
9194
await panelNode.setNotifications([], [])
9295
fetcher = new TestFetcher()
93-
controller = new NotificationsController(panelNode, fetcher, '_aws.test.notification' as any)
96+
controller = new NotificationsController({ node: panelNode, fetcher, storageKey })
9497

9598
ruleEngineSpy = sinon.spy(ruleEngine, 'shouldDisplayNotification')
9699
focusPanelSpy = sinon.spy(panelNode, 'focusPanel')
@@ -475,7 +478,9 @@ describe('Notifications Controller', function () {
475478
throw new Error('test error')
476479
}
477480
})()
478-
assert.doesNotThrow(() => new NotificationsController(panelNode, fetcher).pollForStartUp(ruleEngine))
481+
assert.doesNotThrow(() =>
482+
new NotificationsController({ node: panelNode, fetcher, storageKey }).pollForStartUp(ruleEngine)
483+
)
479484
assert.ok(wasCalled)
480485
})
481486

0 commit comments

Comments
 (0)