@dismissible/nestjs-api 0.0.2-canary.8976e84.0 → 0.0.2-canary.b0d8bfe.0
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.
Potentially problematic release.
This version of @dismissible/nestjs-api might be problematic. Click here for more details.
- package/config/.env.yaml +34 -1
- package/jest.config.ts +23 -1
- package/jest.e2e-config.ts +2 -1
- package/package.json +22 -13
- package/project.json +7 -4
- package/scripts/performance-test.config.json +29 -0
- package/scripts/performance-test.ts +845 -0
- package/src/app-test.factory.ts +8 -1
- package/src/app.module.ts +13 -1
- package/src/app.setup.ts +40 -5
- package/src/bootstrap.ts +8 -3
- package/src/config/app.config.spec.ts +117 -0
- package/src/config/app.config.ts +5 -0
- package/src/config/config.module.spec.ts +0 -2
- package/src/config/default-app.config.spec.ts +74 -0
- package/src/config/default-app.config.ts +15 -0
- package/src/cors/cors.config.spec.ts +162 -0
- package/src/cors/cors.config.ts +37 -0
- package/src/cors/index.ts +1 -0
- package/src/health/health.controller.spec.ts +7 -31
- package/src/health/health.controller.ts +4 -5
- package/src/health/health.module.ts +2 -3
- package/src/health/index.ts +0 -1
- package/src/helmet/helmet.config.spec.ts +197 -0
- package/src/helmet/helmet.config.ts +60 -0
- package/src/helmet/index.ts +1 -0
- package/src/server/server.config.spec.ts +65 -0
- package/src/swagger/swagger.config.spec.ts +113 -0
- package/src/swagger/swagger.config.ts +2 -10
- package/src/swagger/swagger.factory.spec.ts +125 -0
- package/src/swagger/swagger.factory.ts +0 -1
- package/src/validation/index.ts +1 -0
- package/src/validation/validation.config.ts +47 -0
- package/test/config/.env.yaml +23 -0
- package/test/config-jwt-auth/.env.yaml +26 -0
- package/test/dismiss.e2e-spec.ts +61 -0
- package/test/full-cycle.e2e-spec.ts +55 -0
- package/test/get-or-create.e2e-spec.ts +51 -0
- package/test/jwt-auth.e2e-spec.ts +335 -0
- package/test/restore.e2e-spec.ts +61 -0
- package/tsconfig.e2e.json +12 -0
- package/tsconfig.spec.json +12 -0
- package/src/app.e2e-spec.ts +0 -221
- package/src/health/health.service.spec.ts +0 -46
- package/src/health/health.service.ts +0 -16
package/src/app.e2e-spec.ts
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { INestApplication } from '@nestjs/common';
|
|
2
|
-
import request from 'supertest';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import { createTestApp, cleanupTestData } from './app-test.factory';
|
|
5
|
-
|
|
6
|
-
describe('Dismissible API (e2e)', () => {
|
|
7
|
-
let app: INestApplication;
|
|
8
|
-
|
|
9
|
-
beforeAll(async () => {
|
|
10
|
-
app = await createTestApp({
|
|
11
|
-
moduleOptions: {
|
|
12
|
-
configPath: join(__dirname, '../config'),
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
await cleanupTestData(app);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterAll(async () => {
|
|
19
|
-
await app.close();
|
|
20
|
-
await cleanupTestData(app);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('GET /v1/user/:userId/dismissible-item/:id (get-or-create)', () => {
|
|
24
|
-
it('should create a new item on first request', async () => {
|
|
25
|
-
const response = await request(app.getHttpServer())
|
|
26
|
-
.get('/v1/user/user-123/dismissible-item/test-banner-1')
|
|
27
|
-
.expect(200);
|
|
28
|
-
|
|
29
|
-
expect(response.body.data).toBeDefined();
|
|
30
|
-
expect(response.body.data.itemId).toBe('test-banner-1');
|
|
31
|
-
expect(response.body.data.userId).toBe('user-123');
|
|
32
|
-
expect(response.body.data.createdAt).toBeDefined();
|
|
33
|
-
expect(response.body.data.dismissedAt).toBeUndefined();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should return existing item on subsequent requests', async () => {
|
|
37
|
-
// First request - creates the item
|
|
38
|
-
const firstResponse = await request(app.getHttpServer())
|
|
39
|
-
.get('/v1/user/user-456/dismissible-item/test-banner-2')
|
|
40
|
-
.expect(200);
|
|
41
|
-
|
|
42
|
-
const createdAt = firstResponse.body.data.createdAt;
|
|
43
|
-
|
|
44
|
-
// Second request - returns existing item
|
|
45
|
-
const secondResponse = await request(app.getHttpServer())
|
|
46
|
-
.get('/v1/user/user-456/dismissible-item/test-banner-2')
|
|
47
|
-
.expect(200);
|
|
48
|
-
|
|
49
|
-
expect(secondResponse.body.data.itemId).toBe('test-banner-2');
|
|
50
|
-
expect(secondResponse.body.data.createdAt).toBe(createdAt);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should store and retrieve metadata', async () => {
|
|
54
|
-
const response = await request(app.getHttpServer())
|
|
55
|
-
.get('/v1/user/user-789/dismissible-item/test-banner-metadata')
|
|
56
|
-
.query({ metadata: ['version:2', 'category:promo'] })
|
|
57
|
-
.expect(200);
|
|
58
|
-
|
|
59
|
-
expect(response.body.data.metadata).toEqual({
|
|
60
|
-
version: 2,
|
|
61
|
-
category: 'promo',
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('DELETE /v1/user/:userId/dismissible-item/:id (dismiss)', () => {
|
|
67
|
-
it('should dismiss an existing item', async () => {
|
|
68
|
-
const userId = 'user-dismiss-1';
|
|
69
|
-
// First create the item
|
|
70
|
-
await request(app.getHttpServer())
|
|
71
|
-
.get(`/v1/user/${userId}/dismissible-item/dismiss-test-1`)
|
|
72
|
-
.expect(200);
|
|
73
|
-
|
|
74
|
-
// Then dismiss it
|
|
75
|
-
const response = await request(app.getHttpServer())
|
|
76
|
-
.delete(`/v1/user/${userId}/dismissible-item/dismiss-test-1`)
|
|
77
|
-
.expect(200);
|
|
78
|
-
|
|
79
|
-
expect(response.body.data).toBeDefined();
|
|
80
|
-
expect(response.body.data.dismissedAt).toBeDefined();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should return 400 when dismissing non-existent item', async () => {
|
|
84
|
-
const response = await request(app.getHttpServer())
|
|
85
|
-
.delete('/v1/user/user-123/dismissible-item/non-existent-item')
|
|
86
|
-
.expect(400);
|
|
87
|
-
|
|
88
|
-
expect(response.body.error).toBeDefined();
|
|
89
|
-
expect(response.body.error.message).toContain('not found');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should return 400 when dismissing already dismissed item', async () => {
|
|
93
|
-
const userId = 'user-dismiss-2';
|
|
94
|
-
// Create the item
|
|
95
|
-
await request(app.getHttpServer())
|
|
96
|
-
.get(`/v1/user/${userId}/dismissible-item/dismiss-test-2`)
|
|
97
|
-
.expect(200);
|
|
98
|
-
|
|
99
|
-
// Dismiss it first time
|
|
100
|
-
await request(app.getHttpServer())
|
|
101
|
-
.delete(`/v1/user/${userId}/dismissible-item/dismiss-test-2`)
|
|
102
|
-
.expect(200);
|
|
103
|
-
|
|
104
|
-
// Try to dismiss again
|
|
105
|
-
const response = await request(app.getHttpServer())
|
|
106
|
-
.delete(`/v1/user/${userId}/dismissible-item/dismiss-test-2`)
|
|
107
|
-
.expect(400);
|
|
108
|
-
|
|
109
|
-
expect(response.body.error).toBeDefined();
|
|
110
|
-
expect(response.body.error.message).toContain('already dismissed');
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('POST /v1/user/:userId/dismissible-item/:id (restore)', () => {
|
|
115
|
-
it('should restore a dismissed item', async () => {
|
|
116
|
-
const userId = 'user-restore-1';
|
|
117
|
-
// Create and dismiss the item
|
|
118
|
-
await request(app.getHttpServer())
|
|
119
|
-
.get(`/v1/user/${userId}/dismissible-item/restore-test-1`)
|
|
120
|
-
.expect(200);
|
|
121
|
-
|
|
122
|
-
await request(app.getHttpServer())
|
|
123
|
-
.delete(`/v1/user/${userId}/dismissible-item/restore-test-1`)
|
|
124
|
-
.expect(200);
|
|
125
|
-
|
|
126
|
-
// Restore it
|
|
127
|
-
const response = await request(app.getHttpServer())
|
|
128
|
-
.post(`/v1/user/${userId}/dismissible-item/restore-test-1`)
|
|
129
|
-
.expect(201);
|
|
130
|
-
|
|
131
|
-
expect(response.body.data).toBeDefined();
|
|
132
|
-
expect(response.body.data.dismissedAt).toBeUndefined();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should return 400 when restoring non-existent item', async () => {
|
|
136
|
-
const response = await request(app.getHttpServer())
|
|
137
|
-
.post('/v1/user/user-123/dismissible-item/non-existent-restore')
|
|
138
|
-
.expect(400);
|
|
139
|
-
|
|
140
|
-
expect(response.body.error).toBeDefined();
|
|
141
|
-
expect(response.body.error.message).toContain('not found');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should return 400 when restoring non-dismissed item', async () => {
|
|
145
|
-
const userId = 'user-restore-2';
|
|
146
|
-
// Create item but don't dismiss it
|
|
147
|
-
await request(app.getHttpServer())
|
|
148
|
-
.get(`/v1/user/${userId}/dismissible-item/restore-test-2`)
|
|
149
|
-
.expect(200);
|
|
150
|
-
|
|
151
|
-
// Try to restore
|
|
152
|
-
const response = await request(app.getHttpServer())
|
|
153
|
-
.post(`/v1/user/${userId}/dismissible-item/restore-test-2`)
|
|
154
|
-
.expect(400);
|
|
155
|
-
|
|
156
|
-
expect(response.body.error).toBeDefined();
|
|
157
|
-
expect(response.body.error.message).toContain('not dismissed');
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('Full lifecycle flow', () => {
|
|
162
|
-
it('should handle create -> dismiss -> restore -> dismiss cycle', async () => {
|
|
163
|
-
const userId = 'lifecycle-user';
|
|
164
|
-
const itemId = 'lifecycle-test';
|
|
165
|
-
|
|
166
|
-
// Create
|
|
167
|
-
const createResponse = await request(app.getHttpServer())
|
|
168
|
-
.get(`/v1/user/${userId}/dismissible-item/${itemId}`)
|
|
169
|
-
.expect(200);
|
|
170
|
-
|
|
171
|
-
expect(createResponse.body.data.dismissedAt).toBeUndefined();
|
|
172
|
-
|
|
173
|
-
// Dismiss
|
|
174
|
-
const dismissResponse = await request(app.getHttpServer())
|
|
175
|
-
.delete(`/v1/user/${userId}/dismissible-item/${itemId}`)
|
|
176
|
-
.expect(200);
|
|
177
|
-
|
|
178
|
-
expect(dismissResponse.body.data.dismissedAt).toBeDefined();
|
|
179
|
-
|
|
180
|
-
// Restore
|
|
181
|
-
const restoreResponse = await request(app.getHttpServer())
|
|
182
|
-
.post(`/v1/user/${userId}/dismissible-item/${itemId}`)
|
|
183
|
-
.expect(201);
|
|
184
|
-
|
|
185
|
-
expect(restoreResponse.body.data.dismissedAt).toBeUndefined();
|
|
186
|
-
|
|
187
|
-
// Dismiss again
|
|
188
|
-
const dismissAgainResponse = await request(app.getHttpServer())
|
|
189
|
-
.delete(`/v1/user/${userId}/dismissible-item/${itemId}`)
|
|
190
|
-
.expect(200);
|
|
191
|
-
|
|
192
|
-
expect(dismissAgainResponse.body.data.dismissedAt).toBeDefined();
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('should preserve metadata through dismiss and restore', async () => {
|
|
196
|
-
const userId = 'user-metadata';
|
|
197
|
-
const itemId = 'metadata-preserve-test';
|
|
198
|
-
|
|
199
|
-
// Create with metadata
|
|
200
|
-
await request(app.getHttpServer())
|
|
201
|
-
.get(`/v1/user/${userId}/dismissible-item/${itemId}`)
|
|
202
|
-
.query({ metadata: ['key:value', 'count:42'] })
|
|
203
|
-
.expect(200);
|
|
204
|
-
|
|
205
|
-
// Dismiss
|
|
206
|
-
await request(app.getHttpServer())
|
|
207
|
-
.delete(`/v1/user/${userId}/dismissible-item/${itemId}`)
|
|
208
|
-
.expect(200);
|
|
209
|
-
|
|
210
|
-
// Restore and verify metadata
|
|
211
|
-
const restoreResponse = await request(app.getHttpServer())
|
|
212
|
-
.post(`/v1/user/${userId}/dismissible-item/${itemId}`)
|
|
213
|
-
.expect(201);
|
|
214
|
-
|
|
215
|
-
expect(restoreResponse.body.data.metadata).toEqual({
|
|
216
|
-
key: 'value',
|
|
217
|
-
count: 42,
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { HealthService } from './health.service';
|
|
2
|
-
|
|
3
|
-
describe('HealthService', () => {
|
|
4
|
-
let service: HealthService;
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
service = new HealthService();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
describe('getHealth', () => {
|
|
11
|
-
it('should return health status with ok status', () => {
|
|
12
|
-
const result = service.getHealth();
|
|
13
|
-
|
|
14
|
-
expect(result.status).toBe('ok');
|
|
15
|
-
expect(result).toHaveProperty('timestamp');
|
|
16
|
-
expect(result).toHaveProperty('uptime');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should return a valid ISO timestamp', () => {
|
|
20
|
-
const result = service.getHealth();
|
|
21
|
-
const timestamp = new Date(result.timestamp);
|
|
22
|
-
|
|
23
|
-
expect(timestamp.toISOString()).toBe(result.timestamp);
|
|
24
|
-
expect(timestamp.getTime()).toBeGreaterThan(0);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should return process uptime', () => {
|
|
28
|
-
const result = service.getHealth();
|
|
29
|
-
|
|
30
|
-
expect(typeof result.uptime).toBe('number');
|
|
31
|
-
expect(result.uptime).toBeGreaterThanOrEqual(0);
|
|
32
|
-
// Uptime should be approximately equal to process.uptime() (within 0.1 seconds)
|
|
33
|
-
expect(Math.abs(result.uptime - process.uptime())).toBeLessThan(0.1);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should return current timestamp', () => {
|
|
37
|
-
const before = new Date();
|
|
38
|
-
const result = service.getHealth();
|
|
39
|
-
const after = new Date();
|
|
40
|
-
const timestamp = new Date(result.timestamp);
|
|
41
|
-
|
|
42
|
-
expect(timestamp.getTime()).toBeGreaterThanOrEqual(before.getTime());
|
|
43
|
-
expect(timestamp.getTime()).toBeLessThanOrEqual(after.getTime());
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
2
|
-
|
|
3
|
-
@Injectable()
|
|
4
|
-
export class HealthService {
|
|
5
|
-
getHealth(): {
|
|
6
|
-
status: string;
|
|
7
|
-
timestamp: string;
|
|
8
|
-
uptime: number;
|
|
9
|
-
} {
|
|
10
|
-
return {
|
|
11
|
-
status: 'ok',
|
|
12
|
-
timestamp: new Date().toISOString(),
|
|
13
|
-
uptime: process.uptime(),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
}
|