@facetlayer/prism-framework 0.4.0 → 0.4.1

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.
Files changed (130) hide show
  1. package/README.md +176 -8
  2. package/dist/Errors.d.ts +38 -0
  3. package/dist/Errors.d.ts.map +1 -0
  4. package/dist/Metrics.d.ts +5 -0
  5. package/dist/Metrics.d.ts.map +1 -0
  6. package/dist/RequestContext.d.ts +17 -0
  7. package/dist/RequestContext.d.ts.map +1 -0
  8. package/dist/ServiceDefinition.d.ts +16 -0
  9. package/dist/ServiceDefinition.d.ts.map +1 -0
  10. package/dist/app/PrismApp.d.ts +31 -0
  11. package/dist/app/PrismApp.d.ts.map +1 -0
  12. package/dist/app/callEndpoint.d.ts +13 -0
  13. package/dist/app/callEndpoint.d.ts.map +1 -0
  14. package/dist/app/validateApp.d.ts +20 -0
  15. package/dist/app/validateApp.d.ts.map +1 -0
  16. package/dist/authorization/AuthSource.d.ts +8 -0
  17. package/dist/authorization/AuthSource.d.ts.map +1 -0
  18. package/dist/authorization/Authorization.d.ts +24 -0
  19. package/dist/authorization/Authorization.d.ts.map +1 -0
  20. package/dist/authorization/Resource.d.ts +5 -0
  21. package/dist/authorization/Resource.d.ts.map +1 -0
  22. package/dist/authorization/index.d.ts +5 -0
  23. package/dist/authorization/index.d.ts.map +1 -0
  24. package/dist/cli.js +1 -1
  25. package/dist/databases/DatabaseInitializationOptions.d.ts +9 -0
  26. package/dist/databases/DatabaseInitializationOptions.d.ts.map +1 -0
  27. package/dist/databases/DatabaseSetup.d.ts +3 -0
  28. package/dist/databases/DatabaseSetup.d.ts.map +1 -0
  29. package/dist/endpoints/createEndpoint.d.ts +4 -0
  30. package/dist/endpoints/createEndpoint.d.ts.map +1 -0
  31. package/dist/endpoints/getEffectiveOperationId.d.ts +19 -0
  32. package/dist/endpoints/getEffectiveOperationId.d.ts.map +1 -0
  33. package/dist/env/Env.d.ts +2 -0
  34. package/dist/env/Env.d.ts.map +1 -0
  35. package/dist/index.d.ts +34 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +1364 -0
  38. package/dist/launch/launchConfig.d.ts +18 -0
  39. package/dist/launch/launchConfig.d.ts.map +1 -0
  40. package/dist/logging/index.d.ts +9 -0
  41. package/dist/logging/index.d.ts.map +1 -0
  42. package/dist/sse/ConnectionManager.d.ts +23 -0
  43. package/dist/sse/ConnectionManager.d.ts.map +1 -0
  44. package/dist/stdin/StdinServer.d.ts +38 -0
  45. package/dist/stdin/StdinServer.d.ts.map +1 -0
  46. package/dist/web/EndpointListing.d.ts +3 -0
  47. package/dist/web/EndpointListing.d.ts.map +1 -0
  48. package/dist/web/ExpressAppSetup.d.ts +18 -0
  49. package/dist/web/ExpressAppSetup.d.ts.map +1 -0
  50. package/dist/web/ExpressEndpointSetup.d.ts +31 -0
  51. package/dist/web/ExpressEndpointSetup.d.ts.map +1 -0
  52. package/dist/web/SseResponse.d.ts +15 -0
  53. package/dist/web/SseResponse.d.ts.map +1 -0
  54. package/dist/web/ViteIntegration.d.ts +19 -0
  55. package/dist/web/ViteIntegration.d.ts.map +1 -0
  56. package/dist/web/corsMiddleware.d.ts +14 -0
  57. package/dist/web/corsMiddleware.d.ts.map +1 -0
  58. package/dist/web/localhostOnlyMiddleware.d.ts +3 -0
  59. package/dist/web/localhostOnlyMiddleware.d.ts.map +1 -0
  60. package/dist/web/openapi/OpenAPI.d.ts +37 -0
  61. package/dist/web/openapi/OpenAPI.d.ts.map +1 -0
  62. package/dist/web/openapi/validateServicesForOpenapi.d.ts +32 -0
  63. package/dist/web/openapi/validateServicesForOpenapi.d.ts.map +1 -0
  64. package/dist/web/requestContextMiddleware.d.ts +3 -0
  65. package/dist/web/requestContextMiddleware.d.ts.map +1 -0
  66. package/docs/authorization.md +281 -0
  67. package/docs/cors-setup.md +172 -0
  68. package/docs/creating-services.md +220 -0
  69. package/docs/database-setup.md +134 -0
  70. package/docs/endpoint-tools.md +1 -11
  71. package/docs/env-files.md +12 -1
  72. package/docs/error-handling.md +70 -0
  73. package/docs/getting-started.md +22 -12
  74. package/docs/launch-configuration.md +223 -0
  75. package/docs/overview.md +62 -0
  76. package/docs/server-setup.md +144 -0
  77. package/docs/source-directory-organization.md +115 -0
  78. package/docs/stdin-protocol.md +176 -0
  79. package/package.json +42 -9
  80. package/src/Errors.ts +120 -0
  81. package/src/Metrics.ts +53 -0
  82. package/src/RequestContext.ts +36 -0
  83. package/src/ServiceDefinition.ts +35 -0
  84. package/src/__tests__/Authorization.test.ts +350 -0
  85. package/src/__tests__/Errors.test.ts +378 -0
  86. package/src/__tests__/ListEndpoints.test.ts +98 -0
  87. package/src/__tests__/PrismApp.test.ts +274 -0
  88. package/src/__tests__/RequestContext.test.ts +295 -0
  89. package/src/__tests__/SseResponse.test.ts +189 -0
  90. package/src/__tests__/StdinServer.test.ts +304 -0
  91. package/src/__tests__/corsMiddleware.test.ts +293 -0
  92. package/src/__tests__/createEndpoint.test.ts +412 -0
  93. package/src/__tests__/validateApp.test.ts +206 -0
  94. package/src/app/PrismApp.ts +117 -0
  95. package/src/app/callEndpoint.ts +55 -0
  96. package/src/app/validateApp.ts +78 -0
  97. package/src/authorization/AuthSource.ts +14 -0
  98. package/src/authorization/Authorization.ts +78 -0
  99. package/src/authorization/Resource.ts +8 -0
  100. package/src/authorization/index.ts +4 -0
  101. package/src/databases/DatabaseInitializationOptions.ts +9 -0
  102. package/src/databases/DatabaseSetup.ts +19 -0
  103. package/src/endpoints/createEndpoint.ts +39 -0
  104. package/src/endpoints/getEffectiveOperationId.ts +90 -0
  105. package/src/env/Env.ts +23 -0
  106. package/src/index.ts +78 -0
  107. package/src/launch/launchConfig.ts +59 -0
  108. package/src/list-endpoints-command.ts +1 -1
  109. package/src/logging/index.ts +25 -0
  110. package/src/sse/ConnectionManager.ts +79 -0
  111. package/src/stdin/StdinServer.ts +129 -0
  112. package/src/web/EndpointListing.ts +166 -0
  113. package/src/web/ExpressAppSetup.ts +125 -0
  114. package/src/web/ExpressEndpointSetup.ts +178 -0
  115. package/src/web/SseResponse.ts +78 -0
  116. package/src/web/ViteIntegration.ts +72 -0
  117. package/src/web/__tests__/OpenAPI.invalidZodSchemas.test.ts +250 -0
  118. package/src/web/corsMiddleware.ts +63 -0
  119. package/src/web/localhostOnlyMiddleware.ts +19 -0
  120. package/src/web/openapi/OpenAPI.ts +248 -0
  121. package/src/web/openapi/validateServicesForOpenapi.ts +76 -0
  122. package/src/web/requestContextMiddleware.ts +25 -0
  123. package/.claude/settings.local.json +0 -20
  124. package/CHANGELOG +0 -28
  125. package/CLAUDE.md +0 -44
  126. package/build.mts +0 -8
  127. package/test/call-command.test.ts +0 -96
  128. package/test/generate-api-clients.test.ts +0 -33
  129. package/test/generate-api-clients.test.ts.disabled +0 -75
  130. package/tsconfig.json +0 -21
@@ -0,0 +1,378 @@
1
+ import {
2
+ HttpError,
3
+ BadRequestError,
4
+ UnauthorizedError,
5
+ ForbiddenError,
6
+ NotFoundError,
7
+ ConflictError,
8
+ ValidationError,
9
+ NotImplementedError,
10
+ ServiceUnavailableError,
11
+ SchemaValidationError,
12
+ ResponseSchemaValidationError,
13
+ createErrorFromStatus,
14
+ isHttpError,
15
+ } from '../Errors.ts';
16
+ import { describe, expect, it } from 'vitest';
17
+
18
+ describe('HttpError', () => {
19
+ it('should create an HttpError with status code and message', () => {
20
+ const error = new HttpError(500, 'Internal Server Error');
21
+
22
+ expect(error).toBeInstanceOf(Error);
23
+ expect(error).toBeInstanceOf(HttpError);
24
+ expect(error.statusCode).toBe(500);
25
+ expect(error.message).toBe('Internal Server Error');
26
+ expect(error.name).toBe('HttpError');
27
+ expect(error.details).toBeUndefined();
28
+ });
29
+
30
+ it('should create an HttpError with details', () => {
31
+ const details = { field: 'email', issue: 'invalid format' };
32
+ const error = new HttpError(400, 'Bad Request', details);
33
+
34
+ expect(error.statusCode).toBe(400);
35
+ expect(error.message).toBe('Bad Request');
36
+ expect(error.details).toEqual(details);
37
+ });
38
+ });
39
+
40
+ describe('BadRequestError', () => {
41
+ it('should create a BadRequestError with default message', () => {
42
+ const error = new BadRequestError();
43
+
44
+ expect(error).toBeInstanceOf(HttpError);
45
+ expect(error.statusCode).toBe(400);
46
+ expect(error.message).toBe('Bad Request');
47
+ expect(error.name).toBe('BadRequestError');
48
+ });
49
+
50
+ it('should create a BadRequestError with custom message', () => {
51
+ const error = new BadRequestError('Invalid input');
52
+
53
+ expect(error.statusCode).toBe(400);
54
+ expect(error.message).toBe('Invalid input');
55
+ });
56
+
57
+ it('should create a BadRequestError with details', () => {
58
+ const details = { field: 'username' };
59
+ const error = new BadRequestError('Missing required field', details);
60
+
61
+ expect(error.statusCode).toBe(400);
62
+ expect(error.message).toBe('Missing required field');
63
+ expect(error.details).toEqual(details);
64
+ });
65
+ });
66
+
67
+ describe('SchemaValidationError', () => {
68
+ it('should create a SchemaValidationError with correct status code', () => {
69
+ const error = new SchemaValidationError();
70
+
71
+ expect(error).toBeInstanceOf(HttpError);
72
+ expect(error.statusCode).toBe(422);
73
+ expect(error.message).toBe('Schema Validation Error');
74
+ expect(error.name).toBe('SchemaValidationError');
75
+ });
76
+
77
+ it('should create a SchemaValidationError with custom message and details', () => {
78
+ const details = { errors: ['field1 required', 'field2 invalid'] };
79
+ const error = new SchemaValidationError('Request schema invalid', details);
80
+
81
+ expect(error.statusCode).toBe(422);
82
+ expect(error.message).toBe('Request schema invalid');
83
+ expect(error.details).toEqual(details);
84
+ });
85
+ });
86
+
87
+ describe('ResponseSchemaValidationError', () => {
88
+ it('should create a ResponseSchemaValidationError with correct status code', () => {
89
+ const error = new ResponseSchemaValidationError();
90
+
91
+ expect(error).toBeInstanceOf(HttpError);
92
+ expect(error.statusCode).toBe(500);
93
+ expect(error.message).toBe('Response Schema Validation Error');
94
+ expect(error.name).toBe('ResponseSchemaValidationError');
95
+ });
96
+
97
+ it('should create a ResponseSchemaValidationError with custom message', () => {
98
+ const error = new ResponseSchemaValidationError('Response does not match schema');
99
+
100
+ expect(error.statusCode).toBe(500);
101
+ expect(error.message).toBe('Response does not match schema');
102
+ });
103
+ });
104
+
105
+ describe('UnauthorizedError', () => {
106
+ it('should create an UnauthorizedError with default message', () => {
107
+ const error = new UnauthorizedError();
108
+
109
+ expect(error).toBeInstanceOf(HttpError);
110
+ expect(error.statusCode).toBe(401);
111
+ expect(error.message).toBe('Unauthorized');
112
+ expect(error.name).toBe('UnauthorizedError');
113
+ });
114
+
115
+ it('should create an UnauthorizedError with custom message', () => {
116
+ const error = new UnauthorizedError('Invalid token');
117
+
118
+ expect(error.statusCode).toBe(401);
119
+ expect(error.message).toBe('Invalid token');
120
+ });
121
+ });
122
+
123
+ describe('ForbiddenError', () => {
124
+ it('should create a ForbiddenError with default message', () => {
125
+ const error = new ForbiddenError();
126
+
127
+ expect(error).toBeInstanceOf(HttpError);
128
+ expect(error.statusCode).toBe(403);
129
+ expect(error.message).toBe('Forbidden');
130
+ expect(error.name).toBe('ForbiddenError');
131
+ });
132
+
133
+ it('should create a ForbiddenError with custom message', () => {
134
+ const error = new ForbiddenError('Access denied');
135
+
136
+ expect(error.statusCode).toBe(403);
137
+ expect(error.message).toBe('Access denied');
138
+ });
139
+ });
140
+
141
+ describe('NotFoundError', () => {
142
+ it('should create a NotFoundError with default message', () => {
143
+ const error = new NotFoundError();
144
+
145
+ expect(error).toBeInstanceOf(HttpError);
146
+ expect(error.statusCode).toBe(404);
147
+ expect(error.message).toBe('Not Found');
148
+ expect(error.name).toBe('NotFoundError');
149
+ });
150
+
151
+ it('should create a NotFoundError with custom message', () => {
152
+ const error = new NotFoundError('Resource not found');
153
+
154
+ expect(error.statusCode).toBe(404);
155
+ expect(error.message).toBe('Resource not found');
156
+ });
157
+ });
158
+
159
+ describe('ConflictError', () => {
160
+ it('should create a ConflictError with default message', () => {
161
+ const error = new ConflictError();
162
+
163
+ expect(error).toBeInstanceOf(HttpError);
164
+ expect(error.statusCode).toBe(409);
165
+ expect(error.message).toBe('Conflict');
166
+ expect(error.name).toBe('ConflictError');
167
+ });
168
+
169
+ it('should create a ConflictError with custom message', () => {
170
+ const error = new ConflictError('Username already exists');
171
+
172
+ expect(error.statusCode).toBe(409);
173
+ expect(error.message).toBe('Username already exists');
174
+ });
175
+ });
176
+
177
+ describe('ValidationError', () => {
178
+ it('should create a ValidationError with default message', () => {
179
+ const error = new ValidationError();
180
+
181
+ expect(error).toBeInstanceOf(HttpError);
182
+ expect(error.statusCode).toBe(422);
183
+ expect(error.message).toBe('Validation Error');
184
+ expect(error.name).toBe('ValidationError');
185
+ });
186
+
187
+ it('should create a ValidationError with custom message', () => {
188
+ const error = new ValidationError('Email format invalid');
189
+
190
+ expect(error.statusCode).toBe(422);
191
+ expect(error.message).toBe('Email format invalid');
192
+ });
193
+ });
194
+
195
+ describe('NotImplementedError', () => {
196
+ it('should create a NotImplementedError with default message', () => {
197
+ const error = new NotImplementedError();
198
+
199
+ expect(error).toBeInstanceOf(HttpError);
200
+ expect(error.statusCode).toBe(501);
201
+ expect(error.message).toBe('Not Implemented');
202
+ expect(error.name).toBe('NotImplementedError');
203
+ });
204
+
205
+ it('should create a NotImplementedError with custom message', () => {
206
+ const error = new NotImplementedError('Feature not implemented');
207
+
208
+ expect(error.statusCode).toBe(501);
209
+ expect(error.message).toBe('Feature not implemented');
210
+ });
211
+ });
212
+
213
+ describe('ServiceUnavailableError', () => {
214
+ it('should create a ServiceUnavailableError with default message', () => {
215
+ const error = new ServiceUnavailableError();
216
+
217
+ expect(error).toBeInstanceOf(HttpError);
218
+ expect(error.statusCode).toBe(503);
219
+ expect(error.message).toBe('Service Unavailable');
220
+ expect(error.name).toBe('ServiceUnavailableError');
221
+ });
222
+
223
+ it('should create a ServiceUnavailableError with custom message', () => {
224
+ const error = new ServiceUnavailableError('Database connection failed');
225
+
226
+ expect(error.statusCode).toBe(503);
227
+ expect(error.message).toBe('Database connection failed');
228
+ });
229
+ });
230
+
231
+ describe('createErrorFromStatus', () => {
232
+ it('should create BadRequestError for 400', () => {
233
+ const error = createErrorFromStatus(400, 'Bad input');
234
+
235
+ expect(error).toBeInstanceOf(BadRequestError);
236
+ expect(error.statusCode).toBe(400);
237
+ expect(error.message).toBe('Bad input');
238
+ });
239
+
240
+ it('should create UnauthorizedError for 401', () => {
241
+ const error = createErrorFromStatus(401, 'Auth failed');
242
+
243
+ expect(error).toBeInstanceOf(UnauthorizedError);
244
+ expect(error.statusCode).toBe(401);
245
+ expect(error.message).toBe('Auth failed');
246
+ });
247
+
248
+ it('should create ForbiddenError for 403', () => {
249
+ const error = createErrorFromStatus(403, 'No access');
250
+
251
+ expect(error).toBeInstanceOf(ForbiddenError);
252
+ expect(error.statusCode).toBe(403);
253
+ expect(error.message).toBe('No access');
254
+ });
255
+
256
+ it('should create NotFoundError for 404', () => {
257
+ const error = createErrorFromStatus(404, 'Missing');
258
+
259
+ expect(error).toBeInstanceOf(NotFoundError);
260
+ expect(error.statusCode).toBe(404);
261
+ expect(error.message).toBe('Missing');
262
+ });
263
+
264
+ it('should create ConflictError for 409', () => {
265
+ const error = createErrorFromStatus(409, 'Duplicate');
266
+
267
+ expect(error).toBeInstanceOf(ConflictError);
268
+ expect(error.statusCode).toBe(409);
269
+ expect(error.message).toBe('Duplicate');
270
+ });
271
+
272
+ it('should create ValidationError for 422', () => {
273
+ const error = createErrorFromStatus(422, 'Invalid data');
274
+
275
+ expect(error).toBeInstanceOf(ValidationError);
276
+ expect(error.statusCode).toBe(422);
277
+ expect(error.message).toBe('Invalid data');
278
+ });
279
+
280
+ it('should create HttpError for 500', () => {
281
+ const error = createErrorFromStatus(500, 'Server error');
282
+
283
+ expect(error).toBeInstanceOf(HttpError);
284
+ expect(error.statusCode).toBe(500);
285
+ expect(error.message).toBe('Server error');
286
+ });
287
+
288
+ it('should create HttpError for 500 with default message', () => {
289
+ const error = createErrorFromStatus(500);
290
+
291
+ expect(error).toBeInstanceOf(HttpError);
292
+ expect(error.statusCode).toBe(500);
293
+ expect(error.message).toBe('Internal Server Error');
294
+ });
295
+
296
+ it('should create NotImplementedError for 501', () => {
297
+ const error = createErrorFromStatus(501, 'Not ready');
298
+
299
+ expect(error).toBeInstanceOf(NotImplementedError);
300
+ expect(error.statusCode).toBe(501);
301
+ expect(error.message).toBe('Not ready');
302
+ });
303
+
304
+ it('should create ServiceUnavailableError for 503', () => {
305
+ const error = createErrorFromStatus(503, 'Down');
306
+
307
+ expect(error).toBeInstanceOf(ServiceUnavailableError);
308
+ expect(error.statusCode).toBe(503);
309
+ expect(error.message).toBe('Down');
310
+ });
311
+
312
+ it('should create generic HttpError for unknown status codes', () => {
313
+ const error = createErrorFromStatus(418, 'Teapot');
314
+
315
+ expect(error).toBeInstanceOf(HttpError);
316
+ expect(error.statusCode).toBe(418);
317
+ expect(error.message).toBe('Teapot');
318
+ });
319
+
320
+ it('should create generic HttpError with default message for unknown status codes', () => {
321
+ const error = createErrorFromStatus(418);
322
+
323
+ expect(error).toBeInstanceOf(HttpError);
324
+ expect(error.statusCode).toBe(418);
325
+ expect(error.message).toBe('Unknown Error');
326
+ });
327
+
328
+ it('should pass details to created errors', () => {
329
+ const details = { extra: 'info' };
330
+ const error = createErrorFromStatus(400, 'Error with details', details);
331
+
332
+ expect(error.details).toEqual(details);
333
+ });
334
+ });
335
+
336
+ describe('isHttpError', () => {
337
+ it('should return true for HttpError instances', () => {
338
+ const error = new HttpError(500, 'Error');
339
+
340
+ expect(isHttpError(error)).toBe(true);
341
+ });
342
+
343
+ it('should return true for BadRequestError instances', () => {
344
+ const error = new BadRequestError();
345
+
346
+ expect(isHttpError(error)).toBe(true);
347
+ });
348
+
349
+ it('should return true for all error subclasses', () => {
350
+ expect(isHttpError(new UnauthorizedError())).toBe(true);
351
+ expect(isHttpError(new ForbiddenError())).toBe(true);
352
+ expect(isHttpError(new NotFoundError())).toBe(true);
353
+ expect(isHttpError(new ConflictError())).toBe(true);
354
+ expect(isHttpError(new ValidationError())).toBe(true);
355
+ expect(isHttpError(new NotImplementedError())).toBe(true);
356
+ expect(isHttpError(new ServiceUnavailableError())).toBe(true);
357
+ });
358
+
359
+ it('should return false for standard Error instances', () => {
360
+ const error = new Error('Standard error');
361
+
362
+ expect(isHttpError(error)).toBe(false);
363
+ });
364
+
365
+ it('should return false for non-error objects', () => {
366
+ expect(isHttpError({})).toBe(false);
367
+ expect(isHttpError(null)).toBe(false);
368
+ expect(isHttpError(undefined)).toBe(false);
369
+ expect(isHttpError('error')).toBe(false);
370
+ expect(isHttpError(123)).toBe(false);
371
+ });
372
+
373
+ it('should return false for objects with statusCode property but not HttpError', () => {
374
+ const fakeError = { statusCode: 400, message: 'Fake' };
375
+
376
+ expect(isHttpError(fakeError)).toBe(false);
377
+ });
378
+ });
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { runShellCommand } from '@facetlayer/subprocess';
3
+ import { mkdirSync, writeFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { Server } from 'http';
6
+ import { App, createEndpoint, createExpressApp, startServer } from '../index.ts';
7
+
8
+ describe('prism list-endpoints', () => {
9
+ let server: Server;
10
+ const port = 19876;
11
+ const tempDir = join(__dirname, '../../test/temp');
12
+
13
+ beforeAll(async () => {
14
+ // Create temp directory with .env file for prism CLI
15
+ console.log('Creating temp directory:', tempDir);
16
+ mkdirSync(tempDir, { recursive: true });
17
+ writeFileSync(join(tempDir, '.env'), `PRISM_API_PORT=${port}`);
18
+
19
+ // Create a simple test app with some endpoints
20
+ const testService = {
21
+ name: 'test-service',
22
+ endpoints: [
23
+ createEndpoint({
24
+ method: 'GET',
25
+ path: '/users',
26
+ description: 'List all users',
27
+ handler: () => ({ users: [] }),
28
+ }),
29
+ createEndpoint({
30
+ method: 'POST',
31
+ path: '/users',
32
+ description: 'Create a new user',
33
+ handler: () => ({ id: '123', name: 'Test User' }),
34
+ }),
35
+ createEndpoint({
36
+ method: 'GET',
37
+ path: '/users/:id',
38
+ description: 'Get user by ID',
39
+ handler: () => ({ id: '123', name: 'Test User' }),
40
+ }),
41
+ createEndpoint({
42
+ method: 'DELETE',
43
+ path: '/users/:id',
44
+ description: 'Delete user by ID',
45
+ handler: () => ({ success: true }),
46
+ }),
47
+ ],
48
+ };
49
+
50
+ const app = new App({ name: 'Test App', services: [testService] });
51
+ console.log('Starting server');
52
+ server = await startServer({
53
+ port,
54
+ app,
55
+ });
56
+ console.log('Server started');
57
+ });
58
+
59
+ afterAll(async () => {
60
+ await new Promise<void>((resolve, reject) => {
61
+ server.close((err) => {
62
+ if (err) reject(err);
63
+ else resolve();
64
+ });
65
+ });
66
+ });
67
+
68
+ it('should list endpoints from the running server', async () => {
69
+ // Run the prism list-endpoints command from the temp directory
70
+ const result = await runShellCommand('prism', ['list-endpoints'], {
71
+ cwd: tempDir,
72
+ });
73
+
74
+ const output = result.stdout!.join('\n');
75
+
76
+ // Verify the output contains our endpoints
77
+ expect(output).toContain('Available endpoints');
78
+ expect(output).toContain('GET');
79
+ expect(output).toContain('/users');
80
+ expect(output).toContain('POST');
81
+ expect(output).toContain('DELETE');
82
+ expect(output).toContain('/users/:id');
83
+ });
84
+
85
+ it('should return endpoints via /api/endpoints.json', async () => {
86
+ const response = await fetch(`http://localhost:${port}/api/endpoints.json`);
87
+ const data = await response.json();
88
+
89
+ expect(data.endpoints).toBeDefined();
90
+ expect(Array.isArray(data.endpoints)).toBe(true);
91
+ expect(data.endpoints.length).toBeGreaterThan(0);
92
+
93
+ // Check that our test endpoints are included
94
+ const paths = data.endpoints.map((e: { path: string }) => e.path);
95
+ expect(paths).toContain('/users');
96
+ expect(paths).toContain('/users/:id');
97
+ });
98
+ });