@@ -118,53 +118,63 @@ export async function healthRoutes(fastify: FastifyInstance): Promise<void> {
118118 } ,
119119 ) ;
120120
121- // Stub route for testing validation errors
122- fastify . post (
123- '/api/_test/validation-error' ,
124- {
125- schema : {
126- hide : true ,
127- body : {
128- type : 'object' ,
129- properties : {
130- trigger : { type : 'string' } ,
121+ // ---------------------------------------------------------------------------
122+ // Test-harness routes — registered only when NODE_ENV !== 'production'.
123+ //
124+ // These exist purely to exercise the error-mapping + idempotency code
125+ // paths from CI tests. Gating them off in production is defense in
126+ // depth: there's no reason production callers should be able to hit
127+ // /api/_test/internal-error and force a 500. See issue #116.
128+ // ---------------------------------------------------------------------------
129+ if ( fastify . config . NODE_ENV !== 'production' ) {
130+ // Stub route for testing validation errors
131+ fastify . post (
132+ '/api/_test/validation-error' ,
133+ {
134+ schema : {
135+ hide : true ,
136+ body : {
137+ type : 'object' ,
138+ properties : {
139+ trigger : { type : 'string' } ,
140+ } ,
131141 } ,
132142 } ,
133143 } ,
134- } ,
135- async ( ) => {
136- const { ApiValidationError } = await import ( '../lib/errors.js' ) ;
137- throw new ApiValidationError ( 'Test validation failed' , { field : 'required' } ) ;
138- } ,
139- ) ;
144+ async ( ) => {
145+ const { ApiValidationError } = await import ( '../lib/errors.js' ) ;
146+ throw new ApiValidationError ( 'Test validation failed' , { field : 'required' } ) ;
147+ } ,
148+ ) ;
140149
141- // Stub route for testing unknown/500 errors
142- fastify . post (
143- '/api/_test/internal-error' ,
144- { schema : { hide : true } } ,
145- async ( ) => {
146- throw new Error ( 'Deliberate internal error — should not leak to client' ) ;
147- } ,
148- ) ;
150+ // Stub route for testing unknown/500 errors
151+ fastify . post (
152+ '/api/_test/internal-error' ,
153+ { schema : { hide : true } } ,
154+ async ( ) => {
155+ throw new Error ( 'Deliberate internal error — should not leak to client' ) ;
156+ } ,
157+ ) ;
149158
150- // Stub route for testing idempotency
151- fastify . post (
152- '/api/_test/idempotency' ,
153- { schema : { hide : true } } ,
154- async ( request , reply ) => {
155- const idempotencyKey = request . headers [ 'idempotency-key' ] ;
156- if ( typeof idempotencyKey === 'string' && idempotencyKey . length > 0 ) {
157- const personId = 'test-person' ;
158- const cached = request . server . idempotency . check ( personId , idempotencyKey ) ;
159- if ( cached ) {
160- return reply . code ( cached . status ) . send ( cached . body ) ;
161- }
159+ // Stub route for testing idempotency
160+ fastify . post (
161+ '/api/_test/idempotency' ,
162+ { schema : { hide : true } } ,
163+ async ( request , reply ) => {
164+ const idempotencyKey = request . headers [ 'idempotency-key' ] ;
165+ if ( typeof idempotencyKey === 'string' && idempotencyKey . length > 0 ) {
166+ const personId = 'test-person' ;
167+ const cached = request . server . idempotency . check ( personId , idempotencyKey ) ;
168+ if ( cached ) {
169+ return reply . code ( cached . status ) . send ( cached . body ) ;
170+ }
162171
163- const body = ok ( { echoed : idempotencyKey , at : new Date ( ) . toISOString ( ) } ) ;
164- request . server . idempotency . store ( personId , idempotencyKey , { status : 200 , body } ) ;
165- return reply . code ( 200 ) . send ( body ) ;
166- }
167- return ok ( { echoed : null } ) ;
168- } ,
169- ) ;
172+ const body = ok ( { echoed : idempotencyKey , at : new Date ( ) . toISOString ( ) } ) ;
173+ request . server . idempotency . store ( personId , idempotencyKey , { status : 200 , body } ) ;
174+ return reply . code ( 200 ) . send ( body ) ;
175+ }
176+ return ok ( { echoed : null } ) ;
177+ } ,
178+ ) ;
179+ }
170180}
0 commit comments