Skip to content

Commit 4c30933

Browse files
committed
Update testing to validate messaging
1 parent 27b4cfd commit 4c30933

File tree

3 files changed

+93
-51
lines changed

3 files changed

+93
-51
lines changed

tst/e2e/Diagnostics.test.ts

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,55 @@
1+
import { join } from 'path';
12
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
23
import { TestExtension } from '../utils/TestExtension';
4+
import { WaitFor } from '../utils/Utils';
35

46
describe('Diagnostic Features', () => {
5-
const client = new TestExtension();
6-
const diagnosticsReceived: any[] = [];
7+
const client = new TestExtension({
8+
initializeParams: {
9+
initializationOptions: {
10+
aws: {
11+
clientInfo: {
12+
extension: {
13+
name: 'Test CloudFormation Language Server',
14+
version: '1.0.0-test',
15+
},
16+
clientId: 'test-client',
17+
},
18+
},
19+
settings: {
20+
diagnostics: {
21+
cfnGuard: {
22+
enabled: true,
23+
rulesFile: join(__dirname, '../resources/guard/test-guard-rules.guard'),
24+
delayMs: 100,
25+
validateOnChange: true,
26+
},
27+
},
28+
},
29+
},
30+
},
31+
});
732

833
beforeAll(async () => {
934
await client.ready();
1035

11-
// Listen for diagnostic notifications
12-
(client as any).clientConnection.onNotification('textDocument/publishDiagnostics', (params: any) => {
13-
diagnosticsReceived.push(params);
36+
// Configure guard with custom rules file
37+
await client.changeConfiguration({
38+
settings: {
39+
diagnostics: {
40+
cfnGuard: {
41+
enabled: true,
42+
rulesFile: join(__dirname, '../resources/guard/test-guard-rules.guard'),
43+
delayMs: 100,
44+
validateOnChange: true,
45+
},
46+
},
47+
},
1448
});
1549
});
1650

1751
beforeEach(async () => {
1852
await client.reset();
19-
diagnosticsReceived.length = 0; // Clear previous diagnostics
2053
});
2154

2255
afterAll(async () => {
@@ -25,59 +58,30 @@ describe('Diagnostic Features', () => {
2558

2659
describe('Guard diagnostics while authoring', () => {
2760
it('should receive diagnostics during incremental typing', async () => {
28-
// Start with basic template
61+
// Start with basic template that should trigger our custom guard rules
2962
const initialTemplate = `AWSTemplateFormatVersion: '2010-09-09'
3063
Resources:
3164
MyBucket:
3265
Type: AWS::S3::Bucket`;
3366

3467
const uri = await client.openYamlTemplate(initialTemplate);
3568

36-
// Wait for initial diagnostics
37-
await new Promise((resolve) => setTimeout(resolve, 500));
38-
39-
// Simulate typing Properties section incrementally
40-
await client.changeDocument({
41-
textDocument: { uri, version: 2 },
42-
contentChanges: [
43-
{
44-
range: {
45-
start: { line: 3, character: 25 },
46-
end: { line: 3, character: 25 },
47-
},
48-
text: `
49-
Properties:`,
50-
},
51-
],
52-
});
53-
54-
// Wait for diagnostics after adding BucketName
55-
await new Promise((resolve) => setTimeout(resolve, 300));
69+
// Wait for diagnostics from our custom guard rules
70+
await WaitFor.waitFor(() => {
71+
if (client.receivedDiagnostics.length === 0) {
72+
throw new Error('No diagnostics received yet');
73+
}
74+
}, 5000);
5675

57-
// Now add encryption property incrementally (fixing potential guard violation)
58-
await client.changeDocument({
59-
textDocument: { uri, version: 4 },
60-
contentChanges: [
61-
{
62-
range: {
63-
start: { line: 5, character: 23 },
64-
end: { line: 5, character: 23 },
65-
},
66-
text: `
67-
BucketEncryption:
68-
ServerSideEncryptionConfiguration:
69-
- ServerSideEncryptionByDefault:
70-
SSEAlgorithm: AES256`,
71-
},
72-
],
73-
});
76+
expect(client.receivedDiagnostics.length).toBeGreaterThan(0);
7477

75-
// Wait for final diagnostics
76-
await new Promise((resolve) => setTimeout(resolve, 500));
78+
const latestDiagnostics = client.receivedDiagnostics[client.receivedDiagnostics.length - 1];
79+
expect(latestDiagnostics.uri).toBe(uri);
80+
expect(latestDiagnostics.diagnostics.length).toBeGreaterThan(0);
7781

78-
// Test validates that incremental document changes trigger diagnostic notifications
79-
expect(uri).toBeDefined();
80-
// In a real environment with guard enabled, we would validate diagnostic content here
82+
// Verify we got our custom guard diagnostics
83+
const guardDiagnostics = latestDiagnostics.diagnostics.filter((d: any) => d.source === 'cfn-guard');
84+
expect(guardDiagnostics.length).toBeGreaterThan(0);
8185

8286
await client.closeDocument({ textDocument: { uri } });
8387
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Test Guard Rules for E2E Testing
2+
# Simple rules that will trigger on common CloudFormation patterns
3+
4+
rule test_bucket_must_have_encryption {
5+
Resources.*[ Type == "AWS::S3::Bucket" ] {
6+
Properties.BucketEncryption exists <<
7+
S3 buckets must have encryption configured
8+
>>
9+
}
10+
}
11+
12+
rule test_bucket_name_required {
13+
Resources.*[ Type == "AWS::S3::Bucket" ] {
14+
Properties.BucketName exists <<
15+
S3 buckets must have a BucketName specified
16+
>>
17+
}
18+
}

tst/utils/TestExtension.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ type TestExtensionConfig = {
8181
export class TestExtension implements Closeable {
8282
private readonly awsMetadata: AwsMetadata;
8383
private readonly initializeParams: ExtendedInitializeParams;
84+
private readonly diagnosticsReceived: any[] = [];
85+
private readonly mockWorkspaceConfig: Record<string, unknown> = {};
8486

8587
private readonly readStream = new PassThrough();
8688
private readonly writeStream = new PassThrough();
@@ -179,8 +181,12 @@ export class TestExtension implements Closeable {
179181
);
180182

181183
// Handle workspace/configuration requests from the server
182-
this.clientConnection.onRequest('workspace/configuration', () => {
183-
return config.workspaceConfig ?? [{}];
184+
this.clientConnection.onRequest('workspace/configuration', (params: any) => {
185+
// Handle both array and single section requests
186+
if (Array.isArray(params)) {
187+
return params.map((item: any) => this.mockWorkspaceConfig[item.section] ?? {});
188+
}
189+
return [this.mockWorkspaceConfig];
184190
});
185191

186192
this.serverConnection.listen();
@@ -204,6 +210,11 @@ export class TestExtension implements Closeable {
204210
await this.clientConnection.sendRequest(InitializeRequest.type, this.initializeParams);
205211
await this.clientConnection.sendNotification(InitializedNotification.type, {});
206212

213+
// Set up diagnostic listener
214+
this.clientConnection.onNotification('textDocument/publishDiagnostics', (params: any) => {
215+
this.diagnosticsReceived.push(params);
216+
});
217+
207218
await WaitFor.waitFor(() => {
208219
const store = this.external.schemaStore;
209220
const pbSchemas = store?.getPublicSchemas(DefaultSettings.profile.region);
@@ -229,6 +240,7 @@ export class TestExtension implements Closeable {
229240
this.core.awsCredentials.handleIamCredentialsDelete();
230241
this.core.usageTracker.clear();
231242
this.core.validationManager.clear();
243+
this.diagnosticsReceived.length = 0;
232244
}
233245

234246
async send(method: string, params: any) {
@@ -342,13 +354,21 @@ export class TestExtension implements Closeable {
342354
}
343355

344356
changeConfiguration(params: DidChangeConfigurationParams) {
357+
// Update mock workspace config and notify
358+
if (params.settings) {
359+
Object.assign(this.mockWorkspaceConfig, params.settings);
360+
}
345361
return this.notify(DidChangeConfigurationNotification.method, params);
346362
}
347363

348364
changeWorkspaceFolders(params: DidChangeWorkspaceFoldersParams) {
349365
return this.notify(DidChangeWorkspaceFoldersNotification.method, params);
350366
}
351367

368+
get receivedDiagnostics() {
369+
return this.diagnosticsReceived;
370+
}
371+
352372
updateIamCredentials(params: UpdateCredentialsParams) {
353373
return this.send(IamCredentialsUpdateRequest.method, params);
354374
}

0 commit comments

Comments
 (0)