diff --git a/apps/web/src/app/api/trpc/[trpc]/route.test.ts b/apps/web/src/app/api/trpc/[trpc]/route.test.ts new file mode 100644 index 0000000000..afc98eb666 --- /dev/null +++ b/apps/web/src/app/api/trpc/[trpc]/route.test.ts @@ -0,0 +1,43 @@ +import { afterEach, expect, it, jest } from '@jest/globals'; +import { initTRPC, TRPCError } from '@trpc/server'; + +jest.mock('@/lib/trpc/init', () => ({ + createTRPCContext: () => ({}), +})); + +jest.mock('@/routers/root-router', () => { + const t = initTRPC.create(); + return { + rootRouter: t.router({ + ok: t.procedure.query(() => 'ok'), + fail: t.procedure.query(() => { + throw new TRPCError({ + code: 'BAD_REQUEST', + message: 'SYNTHETIC_SENSITIVE_TOKEN', + }); + }), + }), + }; +}); + +afterEach(() => { + jest.restoreAllMocks(); +}); + +it.each(['development', 'production', 'test'] as const)( + 'logs only safe batch error fields in development, with NODE_ENV=%s', + async nodeEnv => { + jest.replaceProperty(process, 'env', { ...process.env, NODE_ENV: nodeEnv }); + const log = jest.spyOn(console, 'error').mockImplementation(() => {}); + const { GET } = await import('./route'); + + const response = await GET(new Request('http://localhost/api/trpc/ok,fail?batch=1')); + + expect(response.status).toBe(207); + if (nodeEnv === 'development') { + expect(log.mock.calls).toEqual([['[trpc] query fail failed: BAD_REQUEST']]); + } else { + expect(log).not.toHaveBeenCalled(); + } + } +); diff --git a/apps/web/src/app/api/trpc/[trpc]/route.ts b/apps/web/src/app/api/trpc/[trpc]/route.ts index b93bc4f102..09139b27ba 100644 --- a/apps/web/src/app/api/trpc/[trpc]/route.ts +++ b/apps/web/src/app/api/trpc/[trpc]/route.ts @@ -11,6 +11,16 @@ const handler = (req: Request) => router: rootRouter, createContext: createTRPCContext, allowMethodOverride: true, + // A batched call answers 207 when one procedure fails, and folds the failure + // into the response body. Without this the server log shows only the 207, so + // nobody can tell which of a dozen batched procedures raised, or why. + // Development only: production reporting is unchanged. + onError: + process.env.NODE_ENV === 'development' + ? ({ path, type, error }) => { + console.error(`[trpc] ${type} ${path ?? ''} failed: ${error.code}`); + } + : undefined, }); export { handler as GET, handler as POST };