Skip to content

Commit b73e9fb

Browse files
authored
Add e2e and int tests for guard diagnostics (#360)
1 parent 645a70b commit b73e9fb

7 files changed

Lines changed: 640 additions & 6 deletions

File tree

tst/e2e/Diagnostics.test.ts

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import { join } from 'path';
2+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
3+
import { TestExtension } from '../utils/TestExtension';
4+
import { WaitFor } from '../utils/Utils';
5+
6+
describe('Diagnostic Features', () => {
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+
});
32+
33+
beforeAll(async () => {
34+
await client.ready();
35+
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+
},
48+
});
49+
});
50+
51+
beforeEach(async () => {
52+
await client.reset();
53+
});
54+
55+
afterAll(async () => {
56+
await client.close();
57+
});
58+
59+
describe('Guard diagnostics while authoring', () => {
60+
it('should receive diagnostics during incremental typing', async () => {
61+
// Start with basic template that should trigger our custom guard rules
62+
const initialTemplate = `AWSTemplateFormatVersion: '2010-09-09'
63+
Resources:
64+
MyBucket:
65+
Type: AWS::S3::Bucket`;
66+
67+
const uri = await client.openYamlTemplate(initialTemplate);
68+
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);
75+
76+
expect(client.receivedDiagnostics.length).toBeGreaterThan(0);
77+
78+
const latestDiagnostics = client.receivedDiagnostics[client.receivedDiagnostics.length - 1];
79+
expect(latestDiagnostics.uri).toBe(uri);
80+
expect(latestDiagnostics.diagnostics.length).toBeGreaterThan(0);
81+
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);
85+
86+
await client.closeDocument({ textDocument: { uri } });
87+
});
88+
89+
it('should receive diagnostics when typing new resource incrementally', async () => {
90+
// Start with minimal template
91+
const initialTemplate = `AWSTemplateFormatVersion: '2010-09-09'
92+
Resources:`;
93+
94+
const uri = await client.openYamlTemplate(initialTemplate);
95+
await new Promise((resolve) => setTimeout(resolve, 300));
96+
97+
// Type resource name
98+
await client.changeDocument({
99+
textDocument: { uri, version: 2 },
100+
contentChanges: [
101+
{
102+
range: {
103+
start: { line: 2, character: 10 },
104+
end: { line: 2, character: 10 },
105+
},
106+
text: `
107+
MyBucket:`,
108+
},
109+
],
110+
});
111+
112+
await new Promise((resolve) => setTimeout(resolve, 200));
113+
114+
// Type resource type
115+
await client.changeDocument({
116+
textDocument: { uri, version: 3 },
117+
contentChanges: [
118+
{
119+
range: {
120+
start: { line: 3, character: 11 },
121+
end: { line: 3, character: 11 },
122+
},
123+
text: `
124+
Type: AWS::S3::Bucket`,
125+
},
126+
],
127+
});
128+
129+
// Wait for guard diagnostics after adding a resource type
130+
await WaitFor.waitFor(() => {
131+
const diags = client.receivedDiagnostics;
132+
const latest = diags[diags.length - 1];
133+
if (latest?.uri !== uri || latest.diagnostics.length === 0) {
134+
throw new Error('No diagnostics received yet for typed resource');
135+
}
136+
}, 5000);
137+
138+
const latest = client.receivedDiagnostics[client.receivedDiagnostics.length - 1];
139+
expect(latest.uri).toBe(uri);
140+
expect(latest.diagnostics.length).toBeGreaterThan(0);
141+
142+
const guardDiags = latest.diagnostics.filter((d: any) => d.source === 'cfn-guard');
143+
expect(guardDiags.length).toBeGreaterThan(0);
144+
145+
await client.closeDocument({ textDocument: { uri } });
146+
});
147+
148+
it('should receive diagnostics for public access violations', async () => {
149+
// Create bucket with public access explicitly disabled
150+
const template = `AWSTemplateFormatVersion: '2010-09-09'
151+
Resources:
152+
MyBucket:
153+
Type: AWS::S3::Bucket
154+
Properties:
155+
BucketName: public-bucket
156+
PublicAccessBlockConfiguration:
157+
BlockPublicAcls: false
158+
BlockPublicPolicy: false
159+
IgnorePublicAcls: false
160+
RestrictPublicBuckets: false`;
161+
162+
const uri = await client.openYamlTemplate(template);
163+
164+
// Wait for guard diagnostics about public access
165+
await WaitFor.waitFor(() => {
166+
const diags = client.receivedDiagnostics;
167+
const latest = diags[diags.length - 1];
168+
if (latest?.uri !== uri || latest.diagnostics.length === 0) {
169+
throw new Error('No diagnostics received yet');
170+
}
171+
}, 5000);
172+
173+
const latest = client.receivedDiagnostics[client.receivedDiagnostics.length - 1];
174+
expect(latest.uri).toBe(uri);
175+
176+
const guardDiags = latest.diagnostics.filter((d: any) => d.source === 'cfn-guard');
177+
expect(guardDiags.length).toBeGreaterThan(0);
178+
179+
// Should flag the public access configuration
180+
const publicAccessDiag = guardDiags.find((d: any) => /PublicAccessBlock|public/i.test(d.message));
181+
expect(publicAccessDiag).toBeDefined();
182+
183+
await client.closeDocument({ textDocument: { uri } });
184+
});
185+
186+
it('should clear diagnostics when document is closed', async () => {
187+
const template = `AWSTemplateFormatVersion: '2010-09-09'
188+
Resources:
189+
MyBucket:
190+
Type: AWS::S3::Bucket`;
191+
192+
const uri = await client.openYamlTemplate(template);
193+
194+
// Wait for diagnostics to arrive
195+
await WaitFor.waitFor(() => {
196+
const latest = client.receivedDiagnostics[client.receivedDiagnostics.length - 1];
197+
if (latest?.uri !== uri || latest.diagnostics.length === 0) {
198+
throw new Error('No diagnostics received yet');
199+
}
200+
}, 5000);
201+
202+
// Close the document
203+
await client.closeDocument({ textDocument: { uri } });
204+
205+
// Wait for empty diagnostics to be published for the closed URI
206+
await WaitFor.waitFor(() => {
207+
const clearEvent = client.receivedDiagnostics.find(
208+
(d: any) => d.uri === uri && d.diagnostics.length === 0,
209+
);
210+
if (!clearEvent) {
211+
throw new Error('Diagnostics not cleared after close');
212+
}
213+
}, 5000);
214+
215+
const clearEvent = client.receivedDiagnostics.find((d: any) => d.uri === uri && d.diagnostics.length === 0);
216+
expect(clearEvent).toBeDefined();
217+
expect(clearEvent.diagnostics).toHaveLength(0);
218+
});
219+
});
220+
});

0 commit comments

Comments
 (0)