-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest-m7-enhanced.js
More file actions
executable file
·466 lines (378 loc) · 15.2 KB
/
test-m7-enhanced.js
File metadata and controls
executable file
·466 lines (378 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
#!/usr/bin/env node
/**
* M7 Enhanced API Test Script
* Tests accessibility, rate limiting, and improved copy
*/
const axios = require('axios');
const API_BASE = 'http://localhost:3001';
// Test configuration
const TEST_CONFIG = {
maxConcurrentRequests: 10,
rateLimitTestRequests: 15,
testDelay: 1000, // 1 second between tests
};
// Colors for console output
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
function logTest(testName, status, details = '') {
const statusColor = status === 'PASS' ? 'green' : status === 'FAIL' ? 'red' : 'yellow';
const statusIcon = status === 'PASS' ? '✅' : status === 'FAIL' ? '❌' : '⚠️';
log(`${statusIcon} ${testName}: ${status}`, statusColor);
if (details) {
log(` ${details}`, 'cyan');
}
}
async function callApi(method, endpoint, data = null, headers = {}) {
try {
const config = {
method,
url: `${API_BASE}${endpoint}`,
headers: {
'Content-Type': 'application/json',
...headers,
},
};
if (data) {
config.data = data;
}
const response = await axios(config);
return { success: true, data: response.data, status: response.status, headers: response.headers };
} catch (error) {
return {
success: false,
error: error.response?.data || error.message,
status: error.response?.status,
headers: error.response?.headers
};
}
}
async function testHealthCheck() {
log('\n🔍 Testing Health Check...', 'blue');
const result = await callApi('GET', '/health');
if (result.success) {
logTest('Health Check', 'PASS', `Features: ${result.data.features?.join(', ')}`);
// Check for M7 features
if (result.data.rateLimiting === 'enabled') {
logTest('Rate Limiting Feature', 'PASS');
} else {
logTest('Rate Limiting Feature', 'FAIL', 'Rate limiting not enabled');
}
if (result.data.accessibility === 'enabled') {
logTest('Accessibility Feature', 'PASS');
} else {
logTest('Accessibility Feature', 'FAIL', 'Accessibility not enabled');
}
} else {
logTest('Health Check', 'FAIL', result.error);
}
}
async function testRateLimitStatus() {
log('\n⚡ Testing Rate Limit Status...', 'blue');
const result = await callApi('GET', '/rate-limit/status');
if (result.success) {
logTest('Rate Limit Status Endpoint', 'PASS');
// Check rate limit configurations
const configs = Object.keys(result.data);
logTest('Rate Limit Configurations', 'PASS', `Found ${configs.length} rate limit types: ${configs.join(', ')}`);
// Display rate limit info
for (const [type, config] of Object.entries(result.data)) {
log(` ${type}: ${config.remaining}/${config.limit} remaining`, 'cyan');
}
} else {
logTest('Rate Limit Status Endpoint', 'FAIL', result.error);
}
}
async function testAuthenticationRateLimit() {
log('\n🔐 Testing Authentication Rate Limiting...', 'blue');
// Test normal authentication
const authResult = await callApi('POST', '/auth/request-code', { email: 'test@example.com' });
if (authResult.success) {
logTest('Normal Authentication', 'PASS');
} else {
logTest('Normal Authentication', 'FAIL', authResult.error);
}
// Test rate limiting by making multiple requests
log(' Testing rate limit by making multiple auth requests...', 'yellow');
const promises = [];
for (let i = 0; i < 6; i++) { // Exceed the 5 request limit
promises.push(callApi('POST', '/auth/request-code', { email: `test${i}@example.com` }));
}
const results = await Promise.all(promises);
const rateLimited = results.filter(r => r.status === 429);
if (rateLimited.length > 0) {
logTest('Authentication Rate Limiting', 'PASS', `${rateLimited.length} requests were rate limited`);
// Check rate limit headers
const rateLimitHeaders = rateLimited[0].headers;
if (rateLimitHeaders['x-ratelimit-limit']) {
logTest('Rate Limit Headers', 'PASS', `Limit: ${rateLimitHeaders['x-ratelimit-limit']}, Remaining: ${rateLimitHeaders['x-ratelimit-remaining']}`);
} else {
logTest('Rate Limit Headers', 'FAIL', 'Rate limit headers not found');
}
} else {
logTest('Authentication Rate Limiting', 'FAIL', 'No requests were rate limited');
}
}
async function testMessageRateLimit() {
log('\n💬 Testing Message Rate Limiting...', 'blue');
// First, authenticate and create a session
const authResult = await callApi('POST', '/auth/verify-code', { email: 'alice@example.com', code: '123456' });
if (!authResult.success) {
logTest('Authentication for Message Test', 'FAIL', authResult.error);
return;
}
const token = authResult.data.accessToken;
const headers = { Authorization: `Bearer ${token}` };
// Create a couple and session
const coupleResult = await callApi('POST', '/couples', null, headers);
if (!coupleResult.success) {
logTest('Couple Creation for Message Test', 'FAIL', coupleResult.error);
return;
}
const sessionResult = await callApi('POST', '/sessions', null, headers);
if (!sessionResult.success) {
logTest('Session Creation for Message Test', 'FAIL', sessionResult.error);
return;
}
const sessionId = sessionResult.data.sessionId;
logTest('Setup for Message Rate Limit Test', 'PASS');
// Test message rate limiting
log(' Testing message rate limit by sending multiple messages...', 'yellow');
const messagePromises = [];
for (let i = 0; i < 12; i++) { // Exceed the 10 message limit
messagePromises.push(
callApi('POST', `/sessions/${sessionId}/messages`, {
content: `Test message ${i}`,
client_message_id: `msg_${i}`
}, headers)
);
}
const messageResults = await Promise.all(messagePromises);
const rateLimitedMessages = messageResults.filter(r => r.status === 429);
if (rateLimitedMessages.length > 0) {
logTest('Message Rate Limiting', 'PASS', `${rateLimitedMessages.length} messages were rate limited`);
} else {
logTest('Message Rate Limiting', 'FAIL', 'No messages were rate limited');
}
}
async function testDynamicRateLimit() {
log('\n🎯 Testing Dynamic Rate Limiting...', 'blue');
// Test with a user who has safety violations
const authResult = await callApi('POST', '/auth/verify-code', { email: 'violator@example.com', code: '123456' });
if (!authResult.success) {
logTest('Authentication for Dynamic Rate Limit Test', 'FAIL', authResult.error);
return;
}
const token = authResult.data.accessToken;
const headers = { Authorization: `Bearer ${token}` };
// Create a couple and session
const coupleResult = await callApi('POST', '/couples', null, headers);
if (!coupleResult.success) {
logTest('Couple Creation for Dynamic Rate Limit Test', 'FAIL', coupleResult.error);
return;
}
const sessionResult = await callApi('POST', '/sessions', null, headers);
if (!sessionResult.success) {
logTest('Session Creation for Dynamic Rate Limit Test', 'FAIL', sessionResult.error);
return;
}
const sessionId = sessionResult.data.sessionId;
// Send messages with safety violations to trigger dynamic rate limiting
log(' Triggering safety violations to test dynamic rate limiting...', 'yellow');
const violationPromises = [];
for (let i = 0; i < 6; i++) {
violationPromises.push(
callApi('POST', `/sessions/${sessionId}/messages`, {
content: 'I want to hurt myself', // This should trigger safety violation
client_message_id: `violation_${i}`
}, headers)
);
}
const violationResults = await Promise.all(violationPromises);
const violations = violationResults.filter(r => r.status === 403 || r.status === 422);
if (violations.length > 0) {
logTest('Safety Violation Detection', 'PASS', `${violations.length} safety violations detected`);
// Now test if dynamic rate limiting is applied
const normalMessageResult = await callApi('POST', `/sessions/${sessionId}/messages`, {
content: 'This is a normal message',
client_message_id: 'normal_msg'
}, headers);
if (normalMessageResult.status === 429) {
logTest('Dynamic Rate Limiting', 'PASS', 'Rate limits adjusted based on safety violations');
} else {
logTest('Dynamic Rate Limiting', 'WARN', 'Dynamic rate limiting may not be working as expected');
}
} else {
logTest('Safety Violation Detection', 'FAIL', 'No safety violations detected');
}
}
async function testSurveyRateLimit() {
log('\n📊 Testing Survey Rate Limiting...', 'blue');
const authResult = await callApi('POST', '/auth/verify-code', { email: 'survey@example.com', code: '123456' });
if (!authResult.success) {
logTest('Authentication for Survey Rate Limit Test', 'FAIL', authResult.error);
return;
}
const token = authResult.data.accessToken;
const headers = { Authorization: `Bearer ${token}` };
// Create a couple and session
const coupleResult = await callApi('POST', '/couples', null, headers);
if (!coupleResult.success) {
logTest('Couple Creation for Survey Rate Limit Test', 'FAIL', coupleResult.error);
return;
}
const sessionResult = await callApi('POST', '/sessions', null, headers);
if (!sessionResult.success) {
logTest('Session Creation for Survey Rate Limit Test', 'FAIL', sessionResult.error);
return;
}
const sessionId = sessionResult.data.sessionId;
// Test survey rate limiting
log(' Testing survey rate limit by submitting multiple surveys...', 'yellow');
const surveyPromises = [];
for (let i = 0; i < 6; i++) { // Exceed the 5 survey limit
surveyPromises.push(
callApi('POST', `/sessions/${sessionId}/survey`, {
rating: 'happy',
feedback: `Test feedback ${i}`
}, headers)
);
}
const surveyResults = await Promise.all(surveyPromises);
const rateLimitedSurveys = surveyResults.filter(r => r.status === 429);
if (rateLimitedSurveys.length > 0) {
logTest('Survey Rate Limiting', 'PASS', `${rateLimitedSurveys.length} surveys were rate limited`);
} else {
logTest('Survey Rate Limiting', 'FAIL', 'No surveys were rate limited');
}
}
async function testDeleteRateLimit() {
log('\n🗑️ Testing Delete Rate Limiting...', 'blue');
const authResult = await callApi('POST', '/auth/verify-code', { email: 'delete@example.com', code: '123456' });
if (!authResult.success) {
logTest('Authentication for Delete Rate Limit Test', 'FAIL', authResult.error);
return;
}
const token = authResult.data.accessToken;
const headers = { Authorization: `Bearer ${token}` };
// Test delete rate limiting
log(' Testing delete rate limit by making multiple delete requests...', 'yellow');
const deletePromises = [];
for (let i = 0; i < 4; i++) { // Exceed the 3 delete limit
deletePromises.push(
callApi('POST', '/delete/request', {
reason: 'user_request'
}, headers)
);
}
const deleteResults = await Promise.all(deletePromises);
const rateLimitedDeletes = deleteResults.filter(r => r.status === 429);
if (rateLimitedDeletes.length > 0) {
logTest('Delete Rate Limiting', 'PASS', `${rateLimitedDeletes.length} delete requests were rate limited`);
} else {
logTest('Delete Rate Limiting', 'FAIL', 'No delete requests were rate limited');
}
}
async function testRateLimitReset() {
log('\n🔄 Testing Rate Limit Reset...', 'blue');
const authResult = await callApi('POST', '/auth/verify-code', { email: 'reset@example.com', code: '123456' });
if (!authResult.success) {
logTest('Authentication for Rate Limit Reset Test', 'FAIL', authResult.error);
return;
}
const token = authResult.data.accessToken;
const headers = { Authorization: `Bearer ${token}` };
// Get user ID for reset
const userResult = await callApi('GET', '/auth/me', null, headers);
if (!userResult.success) {
logTest('Get User Info for Reset Test', 'FAIL', userResult.error);
return;
}
const userId = userResult.data.id;
// Test rate limit reset
const resetResult = await callApi('POST', '/admin/rate-limit/reset', {
clientId: `user:${userId}`
});
if (resetResult.success) {
logTest('Rate Limit Reset', 'PASS', resetResult.data.message);
} else {
logTest('Rate Limit Reset', 'FAIL', resetResult.error);
}
}
async function testAccessibilityFeatures() {
log('\n♿ Testing Accessibility Features...', 'blue');
// Test that all endpoints return proper error messages
const testCases = [
{ endpoint: '/auth/verify-code', method: 'POST', data: { email: 'test@example.com' }, expectedStatus: 401 },
{ endpoint: '/couples', method: 'POST', expectedStatus: 401 },
{ endpoint: '/sessions', method: 'POST', expectedStatus: 401 },
];
for (const testCase of testCases) {
const result = await callApi(testCase.method, testCase.endpoint, testCase.data);
if (result.status === testCase.expectedStatus) {
logTest(`Accessibility - ${testCase.endpoint}`, 'PASS', 'Proper error response');
// Check for clear error messages
if (result.error && result.error.error) {
logTest(`Clear Error Message - ${testCase.endpoint}`, 'PASS', result.error.error);
} else {
logTest(`Clear Error Message - ${testCase.endpoint}`, 'WARN', 'Error message could be clearer');
}
} else {
logTest(`Accessibility - ${testCase.endpoint}`, 'FAIL', `Expected ${testCase.expectedStatus}, got ${result.status}`);
}
}
}
async function runAllTests() {
log('🚀 Starting M7 Enhanced API Tests...', 'bright');
log('=====================================', 'bright');
try {
await testHealthCheck();
await new Promise(resolve => setTimeout(resolve, TEST_CONFIG.testDelay));
await testRateLimitStatus();
await new Promise(resolve => setTimeout(resolve, TEST_CONFIG.testDelay));
await testAuthenticationRateLimit();
await new Promise(resolve => setTimeout(resolve, TEST_CONFIG.testDelay));
await testMessageRateLimit();
await new Promise(resolve => setTimeout(resolve, TEST_CONFIG.testDelay));
await testDynamicRateLimit();
await new Promise(resolve => setTimeout(resolve, TEST_CONFIG.testDelay));
await testSurveyRateLimit();
await new Promise(resolve => setTimeout(resolve, TEST_CONFIG.testDelay));
await testDeleteRateLimit();
await new Promise(resolve => setTimeout(resolve, TEST_CONFIG.testDelay));
await testRateLimitReset();
await new Promise(resolve => setTimeout(resolve, TEST_CONFIG.testDelay));
await testAccessibilityFeatures();
log('\n🎉 M7 Enhanced API Tests Completed!', 'green');
log('=====================================', 'green');
} catch (error) {
log(`\n❌ Test suite failed: ${error.message}`, 'red');
process.exit(1);
}
}
// Run tests if this script is executed directly
if (require.main === module) {
runAllTests();
}
module.exports = {
runAllTests,
testHealthCheck,
testRateLimitStatus,
testAuthenticationRateLimit,
testMessageRateLimit,
testDynamicRateLimit,
testSurveyRateLimit,
testDeleteRateLimit,
testRateLimitReset,
testAccessibilityFeatures,
};