ts-typed-api 0.2.16 → 0.2.18

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/dist/handler.js CHANGED
@@ -56,8 +56,19 @@ function preprocessQueryParams(query, querySchema) {
56
56
  return processedQuery;
57
57
  }
58
58
  // Helper function to create respond method for middleware compatibility
59
- function createRespondFunction(routeDefinition, responseSetter) {
59
+ function createRespondFunction(routeDefinition, responseSetter, middlewareRes) {
60
60
  return (status, data) => {
61
+ // Call any registered response callbacks
62
+ if (middlewareRes && middlewareRes._responseCallbacks) {
63
+ middlewareRes._responseCallbacks.forEach((callback) => {
64
+ try {
65
+ callback(status, data);
66
+ }
67
+ catch (error) {
68
+ console.error('Error in response callback:', error);
69
+ }
70
+ });
71
+ }
61
72
  const responseSchema = routeDefinition.responses[status];
62
73
  if (!responseSchema) {
63
74
  console.error(`No response schema defined for status ${status}`);
@@ -305,6 +316,17 @@ middlewares) {
305
316
  // Augment expressRes with the .respond and .setHeader methods, using TDef
306
317
  const typedExpressRes = expressRes;
307
318
  typedExpressRes.respond = (status, dataForResponse) => {
319
+ // Call any registered response callbacks from middleware
320
+ if (expressRes._responseCallbacks) {
321
+ expressRes._responseCallbacks.forEach((callback) => {
322
+ try {
323
+ callback(status, dataForResponse);
324
+ }
325
+ catch (error) {
326
+ console.error('Error in response callback:', error);
327
+ }
328
+ });
329
+ }
308
330
  // Use the passed apiDefinition object
309
331
  const routeSchemaForHandler = apiDefinition.endpoints[currentDomain][currentRouteKey];
310
332
  const responseSchemaForStatus = routeSchemaForHandler.responses[status];
@@ -347,6 +369,12 @@ middlewares) {
347
369
  });
348
370
  }
349
371
  };
372
+ typedExpressRes.respondContentType = (status, data, contentType) => {
373
+ // Set the content type header
374
+ typedExpressRes.setHeader('Content-Type', contentType);
375
+ // Send the raw data without JSON wrapping or validation
376
+ typedExpressRes.status(status).send(data);
377
+ };
350
378
  typedExpressRes.setHeader = (name, value) => {
351
379
  // Call the original Express setHeader method to avoid recursion
352
380
  Object.getPrototypeOf(expressRes).setHeader.call(expressRes, name, value);
@@ -416,11 +444,18 @@ middlewares) {
416
444
  middlewares.forEach(middleware => {
417
445
  const wrappedMiddleware = async (req, res, next) => {
418
446
  try {
419
- // Add respond method to res for middleware compatibility
447
+ // Add respond and onFinish methods to res for middleware compatibility
420
448
  const middlewareRes = res;
421
449
  middlewareRes.respond = createRespondFunction(routeDefinition, (status, data) => {
422
450
  res.status(status).json(data);
423
- });
451
+ }, middlewareRes);
452
+ middlewareRes.onResponse = (callback) => {
453
+ // Store callback on the underlying express response so it's accessible from TypedResponse
454
+ if (!res._responseCallbacks) {
455
+ res._responseCallbacks = [];
456
+ }
457
+ res._responseCallbacks.push(callback);
458
+ };
424
459
  await middleware(req, middlewareRes, next, { domain: currentDomain, routeKey: currentRouteKey });
425
460
  }
426
461
  catch (error) {
@@ -273,6 +273,17 @@ function registerHonoRouteHandlers(app, apiDefinition, routeHandlers, middleware
273
273
  c.ctx = c.get('ctx') || {};
274
274
  // Add respond method to context
275
275
  c.respond = (status, data) => {
276
+ // Call any registered response callbacks from middleware
277
+ if (c._responseCallbacks) {
278
+ c._responseCallbacks.forEach((callback) => {
279
+ try {
280
+ callback(status, data);
281
+ }
282
+ catch (error) {
283
+ console.error('Error in response callback:', error);
284
+ }
285
+ });
286
+ }
276
287
  const responseSchema = routeDefinition.responses[status];
277
288
  if (!responseSchema) {
278
289
  console.error(`No response schema defined for status ${status} in route ${String(currentDomain)}/${String(currentRouteKey)}`);
@@ -330,6 +341,13 @@ function registerHonoRouteHandlers(app, apiDefinition, routeHandlers, middleware
330
341
  };
331
342
  const fakeRes = {
332
343
  respond: c.respond,
344
+ respondContentType: (status, data, contentType) => {
345
+ // For Hono, set the response directly
346
+ c.__response = new Response(data, {
347
+ status: status,
348
+ headers: { 'Content-Type': contentType }
349
+ });
350
+ },
333
351
  setHeader: (name, value) => {
334
352
  c.header(name, value);
335
353
  return fakeRes;
@@ -419,9 +437,20 @@ function registerHonoRouteHandlers(app, apiDefinition, routeHandlers, middleware
419
437
  path: c.req.path,
420
438
  originalUrl: c.req.url
421
439
  };
422
- // Create minimal res object with respond method for middleware compatibility
440
+ // Create minimal res object with respond and onResponse methods for middleware compatibility
423
441
  const fakeRes = {
424
442
  respond: (status, data) => {
443
+ // Call any registered response callbacks
444
+ if (c._responseCallbacks) {
445
+ c._responseCallbacks.forEach((callback) => {
446
+ try {
447
+ callback(status, data);
448
+ }
449
+ catch (error) {
450
+ console.error('Error in response callback:', error);
451
+ }
452
+ });
453
+ }
425
454
  const responseSchema = routeDefinition.responses[status];
426
455
  if (!responseSchema) {
427
456
  console.error(`No response schema defined for status ${status}`);
@@ -475,6 +504,13 @@ function registerHonoRouteHandlers(app, apiDefinition, routeHandlers, middleware
475
504
  },
476
505
  end: () => {
477
506
  // Perhaps do nothing or set response
507
+ },
508
+ onResponse: (callback) => {
509
+ // Store callback to be called when respond() is invoked
510
+ if (!c._responseCallbacks) {
511
+ c._responseCallbacks = [];
512
+ }
513
+ c._responseCallbacks.push(callback);
478
514
  }
479
515
  };
480
516
  // Call Express-style middleware
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { ApiClient, FetchHttpClientAdapter } from './client';
2
2
  export { generateOpenApiSpec } from './openapi';
3
3
  export { generateOpenApiSpec as generateOpenApiSpec2 } from './openapi-self';
4
4
  export { CreateApiDefinition, CreateResponses, ApiDefinitionSchema } from './definition';
5
- export { RegisterHandlers, EndpointMiddleware, UniversalEndpointMiddleware, SimpleMiddleware, EndpointInfo } from './object-handlers';
5
+ export { RegisterHandlers, EndpointMiddleware, UniversalEndpointMiddleware, SimpleMiddleware, EndpointInfo, MiddlewareResponse } from './object-handlers';
6
6
  export { File as UploadedFile } from './router';
7
7
  export { z as ZodSchema } from 'zod';
8
8
  export { RegisterHonoHandlers, registerHonoRouteHandlers, HonoFile, HonoFileType, honoFileSchema, HonoTypedContext, CreateTypedHonoHandlerWithContext } from './hono-cloudflare-workers';
@@ -19,6 +19,7 @@ export interface MiddlewareResponse {
19
19
  json(data: any): void;
20
20
  setHeader(name: string, value: string): void;
21
21
  end(): void;
22
+ onResponse(callback: (status: number, data: any) => void): void;
22
23
  }
23
24
  export type EndpointMiddlewareCtx<Ctx extends Record<string, any> = Record<string, any>, TDef extends ApiDefinitionSchema = ApiDefinitionSchema> = ((req: express.Request & {
24
25
  ctx?: Ctx;
package/dist/router.d.ts CHANGED
@@ -14,6 +14,7 @@ type ResponseDataForStatus<TDef extends ApiDefinitionSchema, TDomain extends key
14
14
  type RespondFunction<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends keyof TDef['endpoints'][TDomain]> = <TStatusLocal extends keyof TDef['endpoints'][TDomain][TRouteName]['responses'] & number>(status: TStatusLocal, data: ResponseDataForStatus<TDef, TDomain, TRouteName, TStatusLocal>) => void;
15
15
  export interface TypedResponse<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends keyof TDef['endpoints'][TDomain], L extends Record<string, any> = Record<string, any>> extends express.Response<any, L> {
16
16
  respond: RespondFunction<TDef, TDomain, TRouteName>;
17
+ respondContentType: (status: number, data: any, contentType: string) => void;
17
18
  setHeader: (name: string, value: string) => this;
18
19
  json: <B = any>(body: B) => this;
19
20
  }
@@ -30,6 +30,9 @@ export const PublicApiDefinition = CreateApiDefinition({
30
30
  ping: {
31
31
  method: 'GET',
32
32
  path: '/ping',
33
+ query: z.object({
34
+ format: z.enum(["json", "html"]).optional()
35
+ }),
33
36
  responses: CreateResponses({
34
37
  200: z.enum(["pong"]),
35
38
  })
@@ -1,6 +1,6 @@
1
1
  import express from 'express';
2
2
  import { PrivateApiDefinition, PublicApiDefinition } from './definitions';
3
- import { RegisterHandlers, EndpointMiddleware, createTypedHandler } from '../../src';
3
+ import { RegisterHandlers, EndpointMiddleware } from '../../src';
4
4
  const app = express();
5
5
  const port = 3001;
6
6
  app.set('etag', false);
@@ -39,7 +39,16 @@ RegisterHandlers(app, PublicApiDefinition, {
39
39
  // TypeScript will give you type errors if this handler is missing
40
40
  ping: async (req, res) => {
41
41
  // req and res are fully typed based on the API definition
42
- res.respond(200, "pong");
42
+ if (req.query.format === 'html') {
43
+ res.respondContentType(200, "<h1>pong</h1>", "text/html");
44
+ } else {
45
+ res.respond(200, "pong");
46
+ }
47
+ },
48
+ customHeaders: async (req, res) => {
49
+ res.setHeader('x-custom-test', 'test-value');
50
+ res.setHeader('x-another-header', 'another-value');
51
+ res.respond(200, { message: "headers set" });
43
52
  }
44
53
  },
45
54
  status: {
@@ -59,10 +68,10 @@ RegisterHandlers(app, PublicApiDefinition, {
59
68
  // Add another api definition
60
69
  RegisterHandlers(app, PrivateApiDefinition, {
61
70
  user: {
62
- get: createTypedHandler(async (req, res) => {
71
+ get: async (req, res) => {
63
72
  console.log('Fetching user', req.params.id);
64
73
  res.respond(200, "ok");
65
- })
74
+ }
66
75
  }
67
76
  }, [loggingMiddlewareTyped, authMiddleware]);
68
77
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-typed-api",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "A lightweight, type-safe RPC library for TypeScript with Zod validation",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/handler.ts CHANGED
@@ -83,9 +83,21 @@ function preprocessQueryParams(query: any, querySchema?: z.ZodTypeAny): any {
83
83
  // Helper function to create respond method for middleware compatibility
84
84
  function createRespondFunction(
85
85
  routeDefinition: RouteSchema,
86
- responseSetter: (status: number, data: any) => void
86
+ responseSetter: (status: number, data: any) => void,
87
+ middlewareRes?: any
87
88
  ) {
88
89
  return (status: number, data: any) => {
90
+ // Call any registered response callbacks
91
+ if (middlewareRes && middlewareRes._responseCallbacks) {
92
+ middlewareRes._responseCallbacks.forEach((callback: (status: number, data: any) => void) => {
93
+ try {
94
+ callback(status, data);
95
+ } catch (error) {
96
+ console.error('Error in response callback:', error);
97
+ }
98
+ });
99
+ }
100
+
89
101
  const responseSchema = routeDefinition.responses[status];
90
102
 
91
103
  if (!responseSchema) {
@@ -359,6 +371,17 @@ export function registerRouteHandlers<TDef extends ApiDefinitionSchema>(
359
371
  const typedExpressRes = expressRes as TypedResponse<TDef, typeof currentDomain, typeof currentRouteKey>;
360
372
 
361
373
  typedExpressRes.respond = (status, dataForResponse) => {
374
+ // Call any registered response callbacks from middleware
375
+ if ((expressRes as any)._responseCallbacks) {
376
+ (expressRes as any)._responseCallbacks.forEach((callback: (status: number, data: any) => void) => {
377
+ try {
378
+ callback(status, dataForResponse);
379
+ } catch (error) {
380
+ console.error('Error in response callback:', error);
381
+ }
382
+ });
383
+ }
384
+
362
385
  // Use the passed apiDefinition object
363
386
  const routeSchemaForHandler = apiDefinition.endpoints[currentDomain][currentRouteKey] as RouteSchema;
364
387
  const responseSchemaForStatus = routeSchemaForHandler.responses[status as number];
@@ -408,6 +431,13 @@ export function registerRouteHandlers<TDef extends ApiDefinitionSchema>(
408
431
  }
409
432
  };
410
433
 
434
+ typedExpressRes.respondContentType = (status: number, data: any, contentType: string) => {
435
+ // Set the content type header
436
+ typedExpressRes.setHeader('Content-Type', contentType);
437
+ // Send the raw data without JSON wrapping or validation
438
+ typedExpressRes.status(status).send(data);
439
+ };
440
+
411
441
  typedExpressRes.setHeader = (name: string, value: string) => {
412
442
  // Call the original Express setHeader method to avoid recursion
413
443
  Object.getPrototypeOf(expressRes).setHeader.call(expressRes, name, value);
@@ -480,11 +510,18 @@ export function registerRouteHandlers<TDef extends ApiDefinitionSchema>(
480
510
  middlewares.forEach(middleware => {
481
511
  const wrappedMiddleware: express.RequestHandler = async (req, res, next) => {
482
512
  try {
483
- // Add respond method to res for middleware compatibility
513
+ // Add respond and onFinish methods to res for middleware compatibility
484
514
  const middlewareRes = res as any;
485
515
  middlewareRes.respond = createRespondFunction(routeDefinition, (status, data) => {
486
516
  res.status(status).json(data);
487
- });
517
+ }, middlewareRes);
518
+ middlewareRes.onResponse = (callback: (status: number, data: any) => void) => {
519
+ // Store callback on the underlying express response so it's accessible from TypedResponse
520
+ if (!(res as any)._responseCallbacks) {
521
+ (res as any)._responseCallbacks = [];
522
+ }
523
+ (res as any)._responseCallbacks.push(callback);
524
+ };
488
525
  await middleware(req, middlewareRes as MiddlewareResponse, next, { domain: currentDomain, routeKey: currentRouteKey } as any);
489
526
  } catch (error) {
490
527
  next(error);
@@ -341,6 +341,17 @@ export function registerHonoRouteHandlers<
341
341
 
342
342
  // Add respond method to context
343
343
  (c as any).respond = (status: number, data: any) => {
344
+ // Call any registered response callbacks from middleware
345
+ if ((c as any)._responseCallbacks) {
346
+ (c as any)._responseCallbacks.forEach((callback: (status: number, data: any) => void) => {
347
+ try {
348
+ callback(status, data);
349
+ } catch (error) {
350
+ console.error('Error in response callback:', error);
351
+ }
352
+ });
353
+ }
354
+
344
355
  const responseSchema = routeDefinition.responses[status];
345
356
 
346
357
  if (!responseSchema) {
@@ -408,6 +419,13 @@ export function registerHonoRouteHandlers<
408
419
 
409
420
  const fakeRes = {
410
421
  respond: (c as any).respond,
422
+ respondContentType: (status: number, data: any, contentType: string) => {
423
+ // For Hono, set the response directly
424
+ (c as any).__response = new Response(data, {
425
+ status: status,
426
+ headers: { 'Content-Type': contentType }
427
+ });
428
+ },
411
429
  setHeader: (name: string, value: string) => {
412
430
  c.header(name, value);
413
431
  return fakeRes;
@@ -501,9 +519,20 @@ export function registerHonoRouteHandlers<
501
519
  originalUrl: c.req.url
502
520
  };
503
521
 
504
- // Create minimal res object with respond method for middleware compatibility
522
+ // Create minimal res object with respond and onResponse methods for middleware compatibility
505
523
  const fakeRes = {
506
524
  respond: (status: number, data: any) => {
525
+ // Call any registered response callbacks
526
+ if ((c as any)._responseCallbacks) {
527
+ (c as any)._responseCallbacks.forEach((callback: (status: number, data: any) => void) => {
528
+ try {
529
+ callback(status, data);
530
+ } catch (error) {
531
+ console.error('Error in response callback:', error);
532
+ }
533
+ });
534
+ }
535
+
507
536
  const responseSchema = routeDefinition.responses[status];
508
537
 
509
538
  if (!responseSchema) {
@@ -564,6 +593,13 @@ export function registerHonoRouteHandlers<
564
593
  },
565
594
  end: () => {
566
595
  // Perhaps do nothing or set response
596
+ },
597
+ onResponse: (callback: (status: number, data: any) => void) => {
598
+ // Store callback to be called when respond() is invoked
599
+ if (!(c as any)._responseCallbacks) {
600
+ (c as any)._responseCallbacks = [];
601
+ }
602
+ (c as any)._responseCallbacks.push(callback);
567
603
  }
568
604
  };
569
605
 
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ export { ApiClient, FetchHttpClientAdapter } from './client';
2
2
  export { generateOpenApiSpec } from './openapi'
3
3
  export { generateOpenApiSpec as generateOpenApiSpec2 } from './openapi-self'
4
4
  export { CreateApiDefinition, CreateResponses, ApiDefinitionSchema } from './definition';
5
- export { RegisterHandlers, EndpointMiddleware, UniversalEndpointMiddleware, SimpleMiddleware, EndpointInfo } from './object-handlers';
5
+ export { RegisterHandlers, EndpointMiddleware, UniversalEndpointMiddleware, SimpleMiddleware, EndpointInfo, MiddlewareResponse } from './object-handlers';
6
6
  export { File as UploadedFile } from './router';
7
7
  export { z as ZodSchema } from 'zod';
8
8
 
@@ -43,6 +43,7 @@ export interface MiddlewareResponse {
43
43
  json(data: any): void;
44
44
  setHeader(name: string, value: string): void;
45
45
  end(): void;
46
+ onResponse(callback: (status: number, data: any) => void): void;
46
47
  }
47
48
 
48
49
  // Unified middleware type that works for both Express and Hono with context typing
package/src/router.ts CHANGED
@@ -59,6 +59,7 @@ export interface TypedResponse<
59
59
  L extends Record<string, any> = Record<string, any>
60
60
  > extends express.Response<any, L> {
61
61
  respond: RespondFunction<TDef, TDomain, TRouteName>;
62
+ respondContentType: (status: number, data: any, contentType: string) => void;
62
63
  setHeader: (name: string, value: string) => this;
63
64
  json: <B = any>(body: B) => this; // Keep original json
64
65
  }
@@ -105,4 +105,81 @@ describe.each([
105
105
  ).rejects.toThrow('Forbidden as expected');
106
106
  });
107
107
  });
108
+
109
+ describe('Response Logging Middleware', () => {
110
+ let consoleSpy: jest.SpyInstance;
111
+
112
+ beforeEach(() => {
113
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
114
+ });
115
+
116
+ afterEach(() => {
117
+ consoleSpy.mockRestore();
118
+ });
119
+
120
+ test('should log response status without breaking functionality', async () => {
121
+ // The response logging middleware should not interfere with normal operation
122
+ const result = await client.callApi('public', 'ping', {}, {
123
+ 200: ({ data }) => {
124
+ expect(data.message).toBe('pong');
125
+ return data;
126
+ },
127
+ 422: ({ error }) => {
128
+ throw new Error(`Validation error: ${JSON.stringify(error)}`);
129
+ }
130
+ });
131
+
132
+ expect(result.message).toBe('pong');
133
+
134
+ // Assert that console.log was called with the expected message
135
+ expect(consoleSpy).toHaveBeenCalledWith('[TIMING] public.ping responded with 200');
136
+ expect(consoleSpy).toHaveBeenCalledWith('[Test] GET /api/v1/ping - Domain: public, Route: ping');
137
+ });
138
+
139
+ test('should log response status for protected routes', async () => {
140
+ const result = await client.callApi('public', 'protected', { headers: { Authorization: 'Bearer valid-token' } }, {
141
+ 200: ({ data }) => {
142
+ expect(data.message).toBe('protected content');
143
+ expect(data.user).toBe('testuser');
144
+ return data;
145
+ },
146
+ 401: ({ data }) => {
147
+ throw new Error(`Authentication failed: ${data.error}`);
148
+ },
149
+ 403: ({ data }) => {
150
+ throw new Error(`Forbidden: ${data.error}`);
151
+ },
152
+ 422: ({ error }) => {
153
+ throw new Error(`Validation error: ${JSON.stringify(error)}`);
154
+ }
155
+ });
156
+
157
+ expect(result.user).toBe('testuser');
158
+
159
+ // Assert that console.log was called with the expected messages
160
+ expect(consoleSpy).toHaveBeenCalledWith('[TIMING] public.protected responded with 200');
161
+ expect(consoleSpy).toHaveBeenCalledWith('[Test] GET /api/v1/protected - Domain: public, Route: protected');
162
+ });
163
+
164
+ test('should log error status codes for auth failures', async () => {
165
+ await expect(
166
+ client.callApi('public', 'protected', {}, {
167
+ 200: ({ data }) => data,
168
+ 401: ({ data }) => {
169
+ expect(data.error).toBe('No authorization header');
170
+ throw new Error('Authentication failed as expected');
171
+ },
172
+ 403: ({ data }) => {
173
+ throw new Error(`Unexpected forbidden: ${data.error}`);
174
+ },
175
+ 422: ({ error }) => {
176
+ throw new Error(`Validation error: ${JSON.stringify(error)}`);
177
+ }
178
+ })
179
+ ).rejects.toThrow('Authentication failed as expected');
180
+
181
+ // Assert that console.log was called with the error status
182
+ expect(consoleSpy).toHaveBeenCalledWith('[TIMING] public.protected responded with 401');
183
+ });
184
+ });
108
185
  });
package/tests/setup.ts CHANGED
@@ -13,7 +13,11 @@ import { EndpointMiddlewareCtx } from '../src/object-handlers';
13
13
  const simplePublicHandlers = {
14
14
  common: {
15
15
  ping: async (req: any, res: any) => {
16
- res.respond(200, "pong");
16
+ if (req.query?.format === 'html') {
17
+ res.respondContentType(200, "<h1>pong</h1>", "text/html");
18
+ } else {
19
+ res.respond(200, "pong");
20
+ }
17
21
  },
18
22
  customHeaders: async (req: any, res: any) => {
19
23
  res.setHeader('X-Custom-Test', 'test-value');
@@ -477,6 +481,14 @@ const middlewareTestHandlers = {
477
481
  // Generic middleware setup function
478
482
  function setupMiddlewareApp(app: any, isHono: boolean) {
479
483
  // Define middleware functions
484
+ const timingMiddleware: EndpointMiddlewareCtx<Ctx> = async (req, res, next, endpointInfo) => {
485
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
486
+ res.onResponse((status, _data) => {
487
+ console.log(`[TIMING] ${endpointInfo.domain}.${endpointInfo.routeKey} responded with ${status}`);
488
+ });
489
+ await next();
490
+ };
491
+
480
492
  const loggingMiddleware: EndpointMiddlewareCtx<Ctx> = async (req, res, next, endpointInfo) => {
481
493
  console.log(`[Test] ${req.method} ${req.path} - Domain: ${endpointInfo.domain}, Route: ${endpointInfo.routeKey}`);
482
494
  await next();
@@ -505,6 +517,7 @@ function setupMiddlewareApp(app: any, isHono: boolean) {
505
517
  }
506
518
 
507
519
  const middlewares = [
520
+ timingMiddleware,
508
521
  loggingMiddleware,
509
522
  contextMiddleware,
510
523
  authMiddleware
@@ -27,6 +27,19 @@ describe.each([
27
27
  expect(result).toBe('pong');
28
28
  });
29
29
 
30
+ test('should ping successfully with HTML format', async () => {
31
+ const response = await fetch(`${baseUrl}/api/v1/public/ping?format=html`);
32
+ expect(response.status).toBe(200);
33
+ const contentType = response.headers.get('content-type');
34
+ if (serverName === 'Express') {
35
+ expect(contentType).toBe('text/html; charset=utf-8');
36
+ } else {
37
+ expect(contentType).toBe('text/html');
38
+ }
39
+ const html = await response.text();
40
+ expect(html).toBe('<h1>pong</h1>');
41
+ });
42
+
30
43
  test('should handle probe1 with match=true', async () => {
31
44
  const result = await client.callApi('status', 'probe1', {
32
45
  query: { match: true }