@oalacea/daemon 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/Dockerfile +4 -4
- package/dist/prompts/DEPS_EFFICIENCY.md +558 -0
- package/dist/prompts/E2E.md +491 -0
- package/dist/prompts/EXECUTE.md +1060 -0
- package/dist/prompts/INTEGRATION_API.md +484 -0
- package/dist/prompts/INTEGRATION_DB.md +425 -0
- package/dist/prompts/PERF_API.md +433 -0
- package/dist/prompts/PERF_DB.md +430 -0
- package/dist/prompts/PERF_FRONT.md +357 -0
- package/dist/prompts/REMEDIATION.md +482 -0
- package/dist/prompts/UNIT.md +260 -0
- package/dist/templates/README.md +221 -0
- package/dist/templates/k6/load-test.js +54 -0
- package/dist/templates/nestjs/controller.spec.ts +203 -0
- package/dist/templates/nestjs/e2e/api.e2e-spec.ts +451 -0
- package/dist/templates/nestjs/e2e/auth.e2e-spec.ts +533 -0
- package/dist/templates/nestjs/fixtures/test-module.ts +311 -0
- package/dist/templates/nestjs/guard.spec.ts +314 -0
- package/dist/templates/nestjs/interceptor.spec.ts +458 -0
- package/dist/templates/nestjs/module.spec.ts +173 -0
- package/dist/templates/nestjs/pipe.spec.ts +474 -0
- package/dist/templates/nestjs/service.spec.ts +296 -0
- package/dist/templates/playwright/e2e.spec.ts +61 -0
- package/dist/templates/rust/Cargo.toml +72 -0
- package/dist/templates/rust/actix-controller.test.rs +114 -0
- package/dist/templates/rust/axum-handler.test.rs +117 -0
- package/dist/templates/rust/integration.test.rs +63 -0
- package/dist/templates/rust/rocket-route.test.rs +106 -0
- package/dist/templates/rust/unit.test.rs +38 -0
- package/dist/templates/vitest/angular-component.test.ts +38 -0
- package/dist/templates/vitest/api.test.ts +51 -0
- package/dist/templates/vitest/component.test.ts +27 -0
- package/dist/templates/vitest/hook.test.ts +36 -0
- package/dist/templates/vitest/solid-component.test.ts +34 -0
- package/dist/templates/vitest/svelte-component.test.ts +33 -0
- package/dist/templates/vitest/vue-component.test.ts +39 -0
- package/package.json +2 -2
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NestJS Authentication E2E Test Template
|
|
3
|
+
*
|
|
4
|
+
* End-to-end tests for authentication flows:
|
|
5
|
+
* - Registration
|
|
6
|
+
* - Login
|
|
7
|
+
* - Token refresh
|
|
8
|
+
* - Password reset
|
|
9
|
+
* - Logout
|
|
10
|
+
* - Protected routes
|
|
11
|
+
*
|
|
12
|
+
* @package test
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
16
|
+
import { INestApplication, ValidationPipe } from '@nestjs/common';
|
|
17
|
+
import * as request from 'supertest';
|
|
18
|
+
import { AppModule } from './../../src/app.module';
|
|
19
|
+
|
|
20
|
+
describe('Authentication E2E Tests', () => {
|
|
21
|
+
let app: INestApplication;
|
|
22
|
+
let authToken: string;
|
|
23
|
+
let refreshToken: string;
|
|
24
|
+
let userId: string;
|
|
25
|
+
|
|
26
|
+
// Test user credentials
|
|
27
|
+
const testUser = {
|
|
28
|
+
email: `e2e-test-${Date.now()}@example.com`,
|
|
29
|
+
password: 'SecurePass123!',
|
|
30
|
+
name: 'E2E Test User',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
35
|
+
imports: [AppModule],
|
|
36
|
+
}).compile();
|
|
37
|
+
|
|
38
|
+
app = moduleFixture.createNestApplication();
|
|
39
|
+
|
|
40
|
+
app.useGlobalPipes(
|
|
41
|
+
new ValidationPipe({
|
|
42
|
+
whitelist: true,
|
|
43
|
+
transform: true,
|
|
44
|
+
forbidNonWhitelisted: true,
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
app.enableShutdownHooks();
|
|
49
|
+
await app.init();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterAll(async () => {
|
|
53
|
+
await app.close();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('Registration Flow', () => {
|
|
57
|
+
describe('POST /auth/register', () => {
|
|
58
|
+
it('should register a new user successfully', () => {
|
|
59
|
+
return request(app.getHttpServer())
|
|
60
|
+
.post('/auth/register')
|
|
61
|
+
.send(testUser)
|
|
62
|
+
.expect(201)
|
|
63
|
+
.expect((res) => {
|
|
64
|
+
expect(res.body).toMatchObject({
|
|
65
|
+
id: expect.any(String),
|
|
66
|
+
email: testUser.email,
|
|
67
|
+
name: testUser.name,
|
|
68
|
+
});
|
|
69
|
+
expect(res.body).not.toHaveProperty('password');
|
|
70
|
+
expect(res.body).not.toHaveProperty('hash');
|
|
71
|
+
userId = res.body.id;
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return authentication tokens after registration', () => {
|
|
76
|
+
return request(app.getHttpServer())
|
|
77
|
+
.post('/auth/register')
|
|
78
|
+
.send({
|
|
79
|
+
email: `tokens-${Date.now()}@example.com`,
|
|
80
|
+
password: 'SecurePass123!',
|
|
81
|
+
name: 'Token User',
|
|
82
|
+
})
|
|
83
|
+
.expect(201)
|
|
84
|
+
.expect((res) => {
|
|
85
|
+
expect(res.body).toHaveProperty('access_token');
|
|
86
|
+
expect(res.body).toHaveProperty('refresh_token');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should reject duplicate email registration', () => {
|
|
91
|
+
return request(app.getHttpServer())
|
|
92
|
+
.post('/auth/register')
|
|
93
|
+
.send(testUser)
|
|
94
|
+
.expect(409)
|
|
95
|
+
.expect((res) => {
|
|
96
|
+
expect(res.body).toMatchObject({
|
|
97
|
+
statusCode: 409,
|
|
98
|
+
message: expect.stringContaining('email'),
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should reject registration with invalid email', () => {
|
|
104
|
+
return request(app.getHttpServer())
|
|
105
|
+
.post('/auth/register')
|
|
106
|
+
.send({
|
|
107
|
+
email: 'not-an-email',
|
|
108
|
+
password: testUser.password,
|
|
109
|
+
name: testUser.name,
|
|
110
|
+
})
|
|
111
|
+
.expect(400);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should reject registration with weak password', () => {
|
|
115
|
+
return request(app.getHttpServer())
|
|
116
|
+
.post('/auth/register')
|
|
117
|
+
.send({
|
|
118
|
+
email: `weak-${Date.now()}@example.com`,
|
|
119
|
+
password: '123',
|
|
120
|
+
name: 'Weak User',
|
|
121
|
+
})
|
|
122
|
+
.expect(400)
|
|
123
|
+
.expect((res) => {
|
|
124
|
+
expect(res.body.message).toEqual(
|
|
125
|
+
expect.arrayContaining([
|
|
126
|
+
expect.stringContaining('password'),
|
|
127
|
+
])
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should reject registration with missing required fields', () => {
|
|
133
|
+
return request(app.getHttpServer())
|
|
134
|
+
.post('/auth/register')
|
|
135
|
+
.send({
|
|
136
|
+
email: `incomplete-${Date.now()}@example.com`,
|
|
137
|
+
})
|
|
138
|
+
.expect(400);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should reject registration with extra fields', () => {
|
|
142
|
+
return request(app.getHttpServer())
|
|
143
|
+
.post('/auth/register')
|
|
144
|
+
.send({
|
|
145
|
+
email: `extra-${Date.now()}@example.com`,
|
|
146
|
+
password: 'SecurePass123!',
|
|
147
|
+
name: 'Extra User',
|
|
148
|
+
role: 'admin', // Should be stripped or rejected
|
|
149
|
+
})
|
|
150
|
+
.expect(201)
|
|
151
|
+
.expect((res) => {
|
|
152
|
+
expect(res.body.role).not.toBe('admin');
|
|
153
|
+
expect(res.body.role).toBe('user');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('Login Flow', () => {
|
|
160
|
+
describe('POST /auth/login', () => {
|
|
161
|
+
it('should login with valid credentials', () => {
|
|
162
|
+
return request(app.getHttpServer())
|
|
163
|
+
.post('/auth/login')
|
|
164
|
+
.send({
|
|
165
|
+
email: testUser.email,
|
|
166
|
+
password: testUser.password,
|
|
167
|
+
})
|
|
168
|
+
.expect(200)
|
|
169
|
+
.expect((res) => {
|
|
170
|
+
expect(res.body).toMatchObject({
|
|
171
|
+
access_token: expect.any(String),
|
|
172
|
+
refresh_token: expect.any(String),
|
|
173
|
+
user: {
|
|
174
|
+
id: expect.any(String),
|
|
175
|
+
email: testUser.email,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
authToken = res.body.access_token;
|
|
179
|
+
refreshToken = res.body.refresh_token;
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should reject login with invalid email', () => {
|
|
184
|
+
return request(app.getHttpServer())
|
|
185
|
+
.post('/auth/login')
|
|
186
|
+
.send({
|
|
187
|
+
email: 'nonexistent@example.com',
|
|
188
|
+
password: testUser.password,
|
|
189
|
+
})
|
|
190
|
+
.expect(401)
|
|
191
|
+
.expect((res) => {
|
|
192
|
+
expect(res.body).toMatchObject({
|
|
193
|
+
statusCode: 401,
|
|
194
|
+
message: expect.stringContaining('credentials'),
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should reject login with invalid password', () => {
|
|
200
|
+
return request(app.getHttpServer())
|
|
201
|
+
.post('/auth/login')
|
|
202
|
+
.send({
|
|
203
|
+
email: testUser.email,
|
|
204
|
+
password: 'WrongPassword123!',
|
|
205
|
+
})
|
|
206
|
+
.expect(401);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should reject login with missing email', () => {
|
|
210
|
+
return request(app.getHttpServer())
|
|
211
|
+
.post('/auth/login')
|
|
212
|
+
.send({
|
|
213
|
+
password: testUser.password,
|
|
214
|
+
})
|
|
215
|
+
.expect(400);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should reject login with missing password', () => {
|
|
219
|
+
return request(app.getHttpServer())
|
|
220
|
+
.post('/auth/login')
|
|
221
|
+
.send({
|
|
222
|
+
email: testUser.email,
|
|
223
|
+
})
|
|
224
|
+
.expect(400);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('Token Refresh Flow', () => {
|
|
230
|
+
describe('POST /auth/refresh', () => {
|
|
231
|
+
it('should refresh access token with valid refresh token', () => {
|
|
232
|
+
return request(app.getHttpServer())
|
|
233
|
+
.post('/auth/refresh')
|
|
234
|
+
.send({ refresh_token: refreshToken })
|
|
235
|
+
.expect(200)
|
|
236
|
+
.expect((res) => {
|
|
237
|
+
expect(res.body).toMatchObject({
|
|
238
|
+
access_token: expect.any(String),
|
|
239
|
+
refresh_token: expect.any(String),
|
|
240
|
+
});
|
|
241
|
+
// New tokens should be different
|
|
242
|
+
expect(res.body.access_token).not.toBe(authToken);
|
|
243
|
+
authToken = res.body.access_token;
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should reject refresh with invalid token', () => {
|
|
248
|
+
return request(app.getHttpServer())
|
|
249
|
+
.post('/auth/refresh')
|
|
250
|
+
.send({ refresh_token: 'invalid-refresh-token' })
|
|
251
|
+
.expect(401);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should reject refresh with expired token', () => {
|
|
255
|
+
return request(app.getHttpServer())
|
|
256
|
+
.post('/auth/refresh')
|
|
257
|
+
.send({
|
|
258
|
+
refresh_token:
|
|
259
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDAwMDAwMDB9.expired',
|
|
260
|
+
})
|
|
261
|
+
.expect(401);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should reject refresh without token', () => {
|
|
265
|
+
return request(app.getHttpServer())
|
|
266
|
+
.post('/auth/refresh')
|
|
267
|
+
.send({})
|
|
268
|
+
.expect(400);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('Password Reset Flow', () => {
|
|
274
|
+
describe('POST /auth/password-reset/request', () => {
|
|
275
|
+
it('should initiate password reset', () => {
|
|
276
|
+
return request(app.getHttpServer())
|
|
277
|
+
.post('/auth/password-reset/request')
|
|
278
|
+
.send({ email: testUser.email })
|
|
279
|
+
.expect(201)
|
|
280
|
+
.expect((res) => {
|
|
281
|
+
expect(res.body).toHaveProperty('message');
|
|
282
|
+
expect(res.body).toHaveProperty('token');
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should not reveal if email exists or not', () => {
|
|
287
|
+
return request(app.getHttpServer())
|
|
288
|
+
.post('/auth/password-reset/request')
|
|
289
|
+
.send({ email: 'nonexistent@example.com' })
|
|
290
|
+
.expect(201);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should reject request without email', () => {
|
|
294
|
+
return request(app.getHttpServer())
|
|
295
|
+
.post('/auth/password-reset/request')
|
|
296
|
+
.send({})
|
|
297
|
+
.expect(400);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('POST /auth/password-reset/confirm', () => {
|
|
302
|
+
let resetToken: string;
|
|
303
|
+
|
|
304
|
+
beforeAll(() => {
|
|
305
|
+
// Get reset token from previous request
|
|
306
|
+
// In real test, you'd extract this from email or mock
|
|
307
|
+
resetToken = 'valid-reset-token';
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should reset password with valid token', () => {
|
|
311
|
+
return request(app.getHttpServer())
|
|
312
|
+
.post('/auth/password-reset/confirm')
|
|
313
|
+
.send({
|
|
314
|
+
token: resetToken,
|
|
315
|
+
newPassword: 'NewSecurePass456!',
|
|
316
|
+
})
|
|
317
|
+
.expect(200);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should reject reset with invalid token', () => {
|
|
321
|
+
return request(app.getHttpServer())
|
|
322
|
+
.post('/auth/password-reset/confirm')
|
|
323
|
+
.send({
|
|
324
|
+
token: 'invalid-token',
|
|
325
|
+
newPassword: 'NewSecurePass456!',
|
|
326
|
+
})
|
|
327
|
+
.expect(401);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should reject reset with weak password', () => {
|
|
331
|
+
return request(app.getHttpServer())
|
|
332
|
+
.post('/auth/password-reset/confirm')
|
|
333
|
+
.send({
|
|
334
|
+
token: resetToken,
|
|
335
|
+
newPassword: 'weak',
|
|
336
|
+
})
|
|
337
|
+
.expect(400);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should reject reset without token', () => {
|
|
341
|
+
return request(app.getHttpServer())
|
|
342
|
+
.post('/auth/password-reset/confirm')
|
|
343
|
+
.send({
|
|
344
|
+
newPassword: 'NewSecurePass456!',
|
|
345
|
+
})
|
|
346
|
+
.expect(400);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe('Logout Flow', () => {
|
|
352
|
+
describe('POST /auth/logout', () => {
|
|
353
|
+
it('should logout with valid token', () => {
|
|
354
|
+
return request(app.getHttpServer())
|
|
355
|
+
.post('/auth/logout')
|
|
356
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
357
|
+
.expect(204);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should reject logout without token', () => {
|
|
361
|
+
return request(app.getHttpServer())
|
|
362
|
+
.post('/auth/logout')
|
|
363
|
+
.expect(401);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should invalidate refresh token on logout', () => {
|
|
367
|
+
return request(app.getHttpServer())
|
|
368
|
+
.post('/auth/logout')
|
|
369
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
370
|
+
.expect(204)
|
|
371
|
+
.then(() => {
|
|
372
|
+
// Try to use refresh token after logout
|
|
373
|
+
return request(app.getHttpServer())
|
|
374
|
+
.post('/auth/refresh')
|
|
375
|
+
.send({ refresh_token: refreshToken })
|
|
376
|
+
.expect(401);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe('Protected Routes', () => {
|
|
383
|
+
describe('Authentication Guard', () => {
|
|
384
|
+
it('should allow access with valid token', () => {
|
|
385
|
+
return request(app.getHttpServer())
|
|
386
|
+
.get('/auth/me')
|
|
387
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
388
|
+
.expect(200)
|
|
389
|
+
.expect((res) => {
|
|
390
|
+
expect(res.body).toMatchObject({
|
|
391
|
+
id: expect.any(String),
|
|
392
|
+
email: testUser.email,
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('should reject access without token', () => {
|
|
398
|
+
return request(app.getHttpServer())
|
|
399
|
+
.get('/auth/me')
|
|
400
|
+
.expect(401);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should reject access with invalid token', () => {
|
|
404
|
+
return request(app.getHttpServer())
|
|
405
|
+
.get('/auth/me')
|
|
406
|
+
.set('Authorization', 'Bearer invalid-token')
|
|
407
|
+
.expect(401);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('should reject access with expired token', () => {
|
|
411
|
+
return request(app.getHttpServer())
|
|
412
|
+
.get('/auth/me')
|
|
413
|
+
.set(
|
|
414
|
+
'Authorization',
|
|
415
|
+
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDAwMDAwMDB9.expired'
|
|
416
|
+
)
|
|
417
|
+
.expect(401);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('should reject access with malformed token', () => {
|
|
421
|
+
return request(app.getHttpServer())
|
|
422
|
+
.get('/auth/me')
|
|
423
|
+
.set('Authorization', 'Bearer not-a-jwt')
|
|
424
|
+
.expect(401);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
describe('Authorization (Role-based)', () => {
|
|
430
|
+
let adminToken: string;
|
|
431
|
+
|
|
432
|
+
beforeAll(async () => {
|
|
433
|
+
// Login as admin
|
|
434
|
+
const res = await request(app.getHttpServer())
|
|
435
|
+
.post('/auth/login')
|
|
436
|
+
.send({
|
|
437
|
+
email: 'admin@example.com',
|
|
438
|
+
password: 'AdminPass123!',
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
adminToken = res.body.access_token;
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
describe('Admin Routes', () => {
|
|
445
|
+
it('should allow access for admin users', () => {
|
|
446
|
+
return request(app.getHttpServer())
|
|
447
|
+
.get('/admin/users')
|
|
448
|
+
.set('Authorization', `Bearer ${adminToken}`)
|
|
449
|
+
.expect(200);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should deny access for regular users', () => {
|
|
453
|
+
return request(app.getHttpServer())
|
|
454
|
+
.get('/admin/users')
|
|
455
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
456
|
+
.expect(403);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
describe('Resource Ownership', () => {
|
|
461
|
+
let userResourceId: string;
|
|
462
|
+
|
|
463
|
+
beforeAll(async () => {
|
|
464
|
+
const res = await request(app.getHttpServer())
|
|
465
|
+
.post('/resources')
|
|
466
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
467
|
+
.send({ name: 'My Resource' });
|
|
468
|
+
|
|
469
|
+
userResourceId = res.body.id;
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should allow user to update their own resource', () => {
|
|
473
|
+
return request(app.getHttpServer())
|
|
474
|
+
.patch(`/resources/${userResourceId}`)
|
|
475
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
476
|
+
.send({ name: 'Updated Name' })
|
|
477
|
+
.expect(200);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should prevent user from accessing others resources', () => {
|
|
481
|
+
// Assuming resource IDs are UUIDs
|
|
482
|
+
const otherResourceId = '00000000-0000-0000-0000-000000000001';
|
|
483
|
+
|
|
484
|
+
return request(app.getHttpServer())
|
|
485
|
+
.patch(`/resources/${otherResourceId}`)
|
|
486
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
487
|
+
.send({ name: 'Hacked Name' })
|
|
488
|
+
.expect(403);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
describe('Session Management', () => {
|
|
494
|
+
it('should allow multiple concurrent sessions', async () => {
|
|
495
|
+
const sessions = await Promise.all([
|
|
496
|
+
request(app.getHttpServer())
|
|
497
|
+
.post('/auth/login')
|
|
498
|
+
.send({ email: testUser.email, password: testUser.password }),
|
|
499
|
+
request(app.getHttpServer())
|
|
500
|
+
.post('/auth/login')
|
|
501
|
+
.send({ email: testUser.email, password: testUser.password }),
|
|
502
|
+
]);
|
|
503
|
+
|
|
504
|
+
sessions.forEach((res) => {
|
|
505
|
+
expect(res.body).toHaveProperty('access_token');
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('should track active sessions', () => {
|
|
510
|
+
return request(app.getHttpServer())
|
|
511
|
+
.get('/auth/sessions')
|
|
512
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
513
|
+
.expect(200)
|
|
514
|
+
.expect((res) => {
|
|
515
|
+
expect(Array.isArray(res.body)).toBe(true);
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('should allow revoking specific session', () => {
|
|
520
|
+
return request(app.getHttpServer())
|
|
521
|
+
.delete('/auth/sessions/specific-session-id')
|
|
522
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
523
|
+
.expect(204);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('should allow revoking all sessions', () => {
|
|
527
|
+
return request(app.getHttpServer())
|
|
528
|
+
.delete('/auth/sessions')
|
|
529
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
530
|
+
.expect(204);
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
});
|