-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest-e2e.ts
More file actions
294 lines (248 loc) · 13.3 KB
/
Copy pathtest-e2e.ts
File metadata and controls
294 lines (248 loc) · 13.3 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
import http from 'http';
import server from './lib/httpServerExporter';
import init from './lib/index';
import dbConnection from './database/connection';
import Task from './database/databaseModels/Task';
import Post from './database/databaseModels/Post';
import Product from './database/databaseModels/Product';
import Message from './database/databaseModels/Message';
import PORT from './PORT';
// Helper to make HTTP requests
function makeRequest(
method: string,
path: string,
headers: Record<string, string> = {},
body?: any
): Promise<{ statusCode?: number; headers: http.IncomingHttpHeaders; body: string }> {
return new Promise((resolve, reject) => {
const payload = body ? JSON.stringify(body) : undefined;
const options: http.RequestOptions = {
hostname: '127.0.0.1',
port: PORT,
path: path,
method: method,
headers: {
...headers,
...(payload ? {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload)
} : {})
}
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
resolve({
statusCode: res.statusCode,
headers: res.headers,
body: data
});
});
});
req.on('error', (err) => {
reject(err);
});
if (payload) {
req.write(payload);
}
req.end();
});
}
// Visual Assertion Logger
const stats = { passed: 0, failed: 0 };
function assert(condition: boolean, message: string) {
if (condition) {
console.log(` ✅ PASS: ${message}`);
stats.passed++;
} else {
console.error(` ❌ FAIL: ${message}`);
stats.failed++;
}
}
async function runTests() {
console.log('\n==================================================');
console.log('🚀 Starting WebSpeed CMS Framework E2E Test Suite');
console.log('==================================================\n');
// 1. Start Server
console.log('Initializing WebSpeed framework server...');
await init();
// Wait a brief moment to ensure startup completes
await new Promise(resolve => setTimeout(resolve, 500));
try {
// 2. Database Cleanup & Seeding
console.log('\n--- Database Cleanup & Seeding ---');
console.log('Clearing existing Tasks, Posts, Products, and Messages...');
await Task.query().delete();
await Post.query().delete();
await Product.query().delete();
await Message.query().delete();
console.log('Seeding products...');
const prod = new Product({
name: 'Test Gadget',
description: 'A test product for E2E validation.',
price: 19.99,
category: 'Electronics'
});
await prod.__saveToDatabase();
console.log('Seeding blog posts...');
const p1 = new Post({
title: 'Introducing WebSpeed CMS',
content: 'WebSpeed is a modern high-performance CMS framework built on Node.js.',
slug: 'intro-webspeed'
});
await p1.__saveToDatabase();
const p2 = new Post({
title: 'ActiveRecord in Node',
content: 'ActiveRecord pattern makes database interactions elegant and simple.',
slug: 'active-record-node'
});
await p2.__saveToDatabase();
const seedPosts = await Post.query().get();
assert(seedPosts.length === 2, 'Should have exactly 2 blog posts seeded in the database.');
// 3. E2E API Tests (CRUD Tasks)
console.log('\n--- Testing E2E Task REST API (CRUD) ---');
// Test GET Empty
let res = await makeRequest('GET', '/api/tasks');
assert(res.statusCode === 200, 'GET /api/tasks returns status 200');
let tasks = JSON.parse(res.body);
assert(Array.isArray(tasks) && tasks.length === 0, 'GET /api/tasks returns empty array initially');
// Test POST (Create Task)
const newTaskPayload = { title: 'Finish WebSpeed CMS', description: 'Write examples and tests', completed: false };
res = await makeRequest('POST', '/api/tasks', {}, newTaskPayload);
assert(res.statusCode === 201, 'POST /api/tasks returns status 201');
let createdTask = JSON.parse(res.body);
assert(createdTask.id !== undefined, 'Created task should have a database ID');
assert(createdTask.title === newTaskPayload.title, 'Created task has correct title');
const taskId = createdTask.id;
// Test GET Single
res = await makeRequest('GET', `/api/tasks/${taskId}`);
assert(res.statusCode === 200, 'GET /api/tasks/:id returns status 200');
let fetchedTask = JSON.parse(res.body);
assert(fetchedTask.id === taskId, 'Fetched task matches the created task ID');
// Test PUT (Update Task)
const updatePayload = { completed: true };
res = await makeRequest('PUT', `/api/tasks/${taskId}`, {}, updatePayload);
assert(res.statusCode === 200, 'PUT /api/tasks/:id returns status 200');
let updatedTask = JSON.parse(res.body);
assert(updatedTask.completed === true, 'Updated task completed property is true');
// Test DELETE (Delete Task)
res = await makeRequest('DELETE', `/api/tasks/${taskId}`);
assert(res.statusCode === 200, 'DELETE /api/tasks/:id returns status 200');
let deleteResult = JSON.parse(res.body);
assert(deleteResult.success === true, 'DELETE call returned success confirmation');
// Verify Task is gone from database
const dbTasks = await Task.query().where('id', taskId).get();
assert(dbTasks.length === 0, 'Task is no longer present in the database');
// 4. CMS Blog Page & Template Rendering Tests
console.log('\n--- Testing CMS HTML Blog Rendering & Assets ---');
// Test GET Blog Index
res = await makeRequest('GET', '/blog');
assert(res.statusCode === 200, 'GET /blog returns status 200');
assert(!!(res.headers['content-type']?.includes('text/html')), 'GET /blog response Content-Type is text/html');
assert(res.body.includes('WebSpeed CMS Blog'), 'Blog index rendered with correct title');
assert(res.body.includes('Introducing WebSpeed CMS'), 'Blog index rendered seeded post 1 title');
assert(res.body.includes('ActiveRecord in Node'), 'Blog index rendered seeded post 2 title');
// Test GET Blog Post Detail (Seeded Post 1)
res = await makeRequest('GET', '/blog/intro-webspeed');
assert(res.statusCode === 200, 'GET /blog/intro-webspeed returns status 200');
assert(res.body.includes('Introducing WebSpeed CMS'), 'Post detail page rendered title');
assert(res.body.includes('WebSpeed is a modern high-performance CMS'), 'Post detail page rendered content body');
// Test GET Non-Existent Blog Post
res = await makeRequest('GET', '/blog/invalid-slug');
assert(res.statusCode === 404, 'GET /blog/invalid-slug returns 404 status');
// Test Static CSS File serving
res = await makeRequest('GET', '/style.css');
assert(res.statusCode === 200, 'GET /style.css returns status 200');
assert(!!(res.headers['content-type']?.includes('text/css')), 'GET /style.css response Content-Type is text/css');
assert(res.body.includes('--bg-color: #0b0f19'), 'CSS content verified');
// Test 404 redirection
res = await makeRequest('GET', '/non-existent-route-random');
assert(res.statusCode === 302, 'Non-existent route redirected to /404 with 302 status');
// Test GET Real-Time Collab Board page
res = await makeRequest('GET', '/collab');
assert(res.statusCode === 200, 'GET /collab returns status 200');
assert(res.body.includes('WebSpeed Real-Time Collaboration'), 'Collab page rendered with correct title');
// Test GET E-Commerce Store page
res = await makeRequest('GET', '/store');
assert(res.statusCode === 200, 'GET /store returns status 200');
assert(res.body.includes('WebSpeed E-Commerce'), 'Store page rendered with correct title');
// Test GET E-Commerce Store Products JSON API
res = await makeRequest('GET', '/store/api/products');
assert(res.statusCode === 200, 'GET /store/api/products returns status 200');
assert(!!(res.headers['content-type']?.includes('application/json')), 'GET /store/api/products Content-Type is application/json');
let products = JSON.parse(res.body);
assert(Array.isArray(products) && products.length === 1, 'GET /store/api/products returns exactly 1 seeded product');
assert(products[0].name === 'Test Gadget', 'Seeded product has correct name');
// Test 4. Validation and Authentication Scaffolding
console.log('\n--- Testing E2E Validation & Authentication Scaffolding ---');
// A. Test Task Validation failure (Title is required / too short)
res = await makeRequest('POST', '/api/tasks', {}, { description: 'Missing title' });
assert(res.statusCode === 400, 'POST /api/tasks returns status 400 when title is missing');
assert(res.body.includes('Validation failed'), 'Error body indicates validation failure');
// B. Test Login Validation failure
res = await makeRequest('POST', '/api/auth/login', {}, { username: 'ad', password: '12' });
assert(res.statusCode === 400, 'POST /api/auth/login returns status 400 for short user/pass');
// C. Test Successful Login and JWT generation
res = await makeRequest('POST', '/api/auth/login', {}, { username: 'admin', password: 'password' });
assert(res.statusCode === 200, 'POST /api/auth/login returns status 200 for valid credentials');
let authBody = JSON.parse(res.body);
assert(!!authBody.token, 'Response contains auth JWT token');
const token = authBody.token;
// D. Test Authenticated access to profile (Unauthorized)
res = await makeRequest('GET', '/api/auth/me');
assert(res.statusCode === 401, 'GET /api/auth/me returns status 401 without auth header');
// E. Test Authenticated access to profile (Success)
res = await makeRequest('GET', '/api/auth/me', { 'Authorization': `Bearer ${token}` });
assert(res.statusCode === 200, 'GET /api/auth/me returns status 200 with valid token');
let profileBody = JSON.parse(res.body);
assert(profileBody.user.username === 'admin', 'Returned profile username matches logged in user');
assert(profileBody.user.role === 'admin', 'Returned profile role matches expected');
// Test 5. WebSpeed Premium Admin Console
console.log('\n--- Testing E2E Premium Admin Console ---');
// A. Test GET Admin Dashboard View
res = await makeRequest('GET', '/admin');
assert(res.statusCode === 200, 'GET /admin returns status 200');
assert(res.body.includes('WebSpeed Premium Admin Console'), 'Admin dashboard rendered with correct title');
// B. Test GET Admin DB Explorer API
res = await makeRequest('GET', '/admin/api/database');
assert(res.statusCode === 200, 'GET /admin/api/database returns status 200');
assert(!!(res.headers['content-type']?.includes('application/json')), 'Database list content-type is json');
let collections = JSON.parse(res.body);
assert(Array.isArray(collections), 'Returns an array of collections');
// C. Test GET Admin WebSockets stats API
res = await makeRequest('GET', '/admin/api/websockets');
assert(res.statusCode === 200, 'GET /admin/api/websockets returns status 200');
// D. Test GET Admin Log Analyzer API
res = await makeRequest('GET', '/admin/api/logs');
assert(res.statusCode === 200, 'GET /admin/api/logs returns status 200');
let logs = JSON.parse(res.body);
assert(Array.isArray(logs) && logs.length > 0, 'Logs are collected and returned');
// Test GET Premium Showcase Portal page
res = await makeRequest('GET', '/premium-showcase');
assert(res.statusCode === 200, 'GET /premium-showcase returns status 200');
assert(res.body.includes('WebSpeed Features Showcase Portal'), 'Showcase portal page rendered with correct title');
} catch (error) {
console.error('Test Execution Error:', error);
stats.failed++;
} finally {
// 5. Cleanup and Shutdown
console.log('\n--- Shutting Down Server ---');
server.close(async () => {
console.log('HTTP Server closed.');
await dbConnection.disconnect();
console.log('Database disconnected.');
console.log('\n==================================================');
console.log('📊 Test Execution Summary');
console.log(` Passed: ${stats.passed}`);
console.log(` Failed: ${stats.failed}`);
console.log('==================================================\n');
if (stats.failed > 0) {
process.exit(1);
} else {
process.exit(0);
}
});
}
}
runTests();