Contents of smoke-test.mjs:
1: import fetch from 'node-fetch';
2: import WebSocket from 'ws';
3: import assert from 'assert';
4: import { spawn } from 'child_process';
5: 
6: const API_URL = 'http://localhost:3000';
7: const WS_URL = 'ws://localhost:3000';
8: 
9: const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY;
10: const GEMINI_MODEL = process.env.GEMINI_MODEL || 'gemini-1.5-flash';
11: 
12: async function healthCheck() {
13:     console.log('Running health check...');
14:     const res = await fetch(`${API_URL}/api/incidents`);
15:     assert.strictEqual(res.status, 200, 'Health check failed: Expected 200 status');
16:     console.log('Health check passed.');
17: }
18: 
19: async function testIncidentsCrud() {
20:     console.log('Running incidents CRUD test...');
21:     // Create
22:     const newIncident = { title: 'Test Incident', description: 'This is a test incident.' };
23:     const createRes = await fetch(`${API_URL}/api/incidents`, {
24:         method: 'POST',
25:         headers: { 'Content-Type': 'application/json' },
26:         body: JSON.stringify(newIncident)
27:     });
28:     const createdIncident = await createRes.json();
29:     assert.strictEqual(createRes.status, 201, 'Create incident failed');
30:     assert.strictEqual(createdIncident.title, newIncident.title, 'Created incident title mismatch');
31:     console.log('Incident created.');
32: 
33:     // Read
34:     const getRes = await fetch(`${API_URL}/api/incidents`);
35:     const incidents = await getRes.json();
36:     assert.strictEqual(getRes.status, 200, 'Get incidents failed');
37:     assert.ok(incidents.some(inc => inc.id === createdIncident.id), 'Created incident not found in list');
38:     console.log('Incidents read.');
39: 
40:     // Update
41:     const updatedTitle = 'Updated Test Incident';
42:     const updateRes = await fetch(`${API_URL}/api/incidents/${createdIncident.id}`, {
43:         method: 'PUT',
44:         headers: { 'Content-Type': 'application/json' },
45:         body: JSON.stringify({ title: updatedTitle, status: 'resolved' })
46:     });
47:     const updatedIncident = await updateRes.json();
48:     assert.strictEqual(updateRes.status, 200, 'Update incident failed');
49:     assert.strictEqual(updatedIncident.title, updatedTitle, 'Updated incident title mismatch');
50:     assert.strictEqual(updatedIncident.status, 'resolved', 'Updated incident status mismatch');
51:     console.log('Incident updated.');
52: 
53:     // Delete
54:     const deleteRes = await fetch(`${API_URL}/api/incidents/${createdIncident.id}`, {
55:         method: 'DELETE'
56:     });
57:     assert.strictEqual(deleteRes.status, 204, 'Delete incident failed');
58:     const getAfterDeleteRes = await fetch(`${API_URL}/api/incidents`);
59:     const incidentsAfterDelete = await getAfterDeleteRes.json();
60:     assert.ok(!incidentsAfterDelete.some(inc => inc.id === createdIncident.id), 'Deleted incident found in list');
61:     console.log('Incident deleted.');
62:     console.log('Incidents CRUD test passed.');
63: }
64: 
65: async function testJobs() {
66:     console.log('Running jobs test...');
67:     const newJob = { title: 'Test Job', description: 'This is a test job.' };
68:     const createRes = await fetch(`${API_URL}/api/jobs`, {
69:         method: 'POST',
70:         headers: { 'Content-Type': 'application/json' },
71:         body: JSON.stringify(newJob)
72:     });
73:     const createdJob = await createRes.json();
74:     assert.strictEqual(createRes.status, 201, 'Create job failed');
75:     assert.strictEqual(createdJob.status, 'pending', 'Job initial status mismatch');
76:     console.log('Job created.');
77: 
78:     // Wait for job to complete (simulated 5 seconds)
79:     console.log('Waiting for job to complete...');
80:     await new Promise(resolve => setTimeout(resolve, 6000)); // 5s job + buffer
81: 
82:     const getRes = await fetch(`${API_URL}/api/jobs`);
83:     const jobs = await getRes.json();
84:     const completedJob = jobs.find(job => job.id === createdJob.id);
85:     assert.ok(completedJob, 'Completed job not found');
86:     assert.strictEqual(completedJob.status, 'completed', 'Job did not complete');
87:     console.log('Job completed test passed.');
88: }
89: 
90: async function testWebSocket() {
91:     console.log('Running WebSocket test...');
92:     return new Promise((resolve, reject) => {
93:         const ws = new WebSocket(WS_URL);
94:         let incidentCreated = false;
95:         let jobCreated = false;
96: 
97:         ws.onopen = async () => {
98:             console.log('WebSocket connected.');
99:             // Create an incident to trigger a broadcast
100:             await fetch(`${API_URL}/api/incidents`, {
101:                 method: 'POST',
102:                 headers: { 'Content-Type': 'application/json' },
103:                 body: JSON.stringify({ title: 'WS Incident', description: 'Test WS' })
104:             });
105:             // Create a job to trigger a broadcast
106:             await fetch(`${API_URL}/api/jobs`, {
107:                 method: 'POST',
108:                 headers: { 'Content-Type': 'application/json' },
109:                 body: JSON.stringify({ title: 'WS Job', description: 'Test WS Job' })
110:             });
111:         };
112: 
113:         ws.onmessage = (event) => {
114:             const data = JSON.parse(event.data);
115:             if (data.type === 'incidentCreated' && data.incident.title === 'WS Incident') {
116:                 incidentCreated = true;
117:                 console.log('WebSocket incidentCreated received.');
118:             }
119:             if (data.type === 'jobCreated' && data.job.title === 'WS Job') {
120:                 jobCreated = true;
121:                 console.log('WebSocket jobCreated received.');
122:             }
123: 
124:             if (incidentCreated && jobCreated) {
125:                 console.log('WebSocket test passed.');
126:                 ws.close();
127:                 resolve();
128:             }
129:         };
130: 
131:         ws.onerror = (error) => {
132:             console.error('WebSocket error:', error);
133:             reject(error);
134:         };
135: 
136:         ws.onclose = () => {
137:             if (!incidentCreated || !jobCreated) {
138:                 reject(new Error('WebSocket test failed: Did not receive all expected messages.'));
139:             }
140:         };
141:     });
142: }
143: 
144: async function testGeminiSummarize() {
145:     console.log('Running Gemini summarization test...');
146:     if (!GOOGLE_API_KEY) {
147:         console.log('Skipping Gemini summarization test: GOOGLE_API_KEY not set.');
148:         return;
149:     }
150: 
151:     const textToSummarize = 'The system experienced a critical outage due to a database connection failure. This impacted all user-facing services for approximately 30 minutes. Root cause analysis is ongoing.';
152:     const res = await fetch(`${API_URL}/api/assist/summarize`, {
153:         method: 'POST',
154:         headers: { 'Content-Type': 'application/json' },
155:         body: JSON.stringify({ text: textToSummarize })
156:     });
157:     const data = await res.json();
158: 
159:     assert.strictEqual(res.status, 200, `Gemini summarization failed: Expected 200, got ${res.status}`);
160:     assert.ok(data.summary, 'Summary field missing');
161:     assert.ok(typeof data.summary === 'string', 'Summary is not a string');
162:     assert.ok(data.summary.length > 10, 'Summary is too short');
163:     console.log('Gemini summarization test passed.');
164: }
165: 
166: async function runTests() {
167:     try {
168:         // Start the server in a child process (assuming `npm start` runs it)
169:         const serverProcess = spawn('npm', ['start']);
170: 
171:         serverProcess.stdout.on('data', (data) => {
172:             console.log(`Server: ${data}`);
173:         });
174: 
175:         serverProcess.stderr.on('data', (data) => {
176:             console.error(`Server Error: ${data}`);
177:         });
178: 
179:         // Give the server some time to start
180:         console.log('Waiting for server to start...');
181:         await new Promise(resolve => setTimeout(resolve, 5000));
182: 
183:         await healthCheck();
184:         await testIncidentsCrud();
185:         await testJobs();
186:         await testWebSocket();
187:         await testGeminiSummarize();
188: 
189:         console.log('All smoke tests passed!');
190:     } catch (error) {
191:         console.error('Smoke test failed:', error);
192:         process.exit(1);
193:     } finally {
194:         // Ensure the server process is killed
195:         if (serverProcess) {
196:             serverProcess.kill();
197:             console.log('Server process terminated.');
198:         }
199:     }
200: }
201: 
202: runTests();
203: 