52 lines
2.1 KiB
TypeScript
52 lines
2.1 KiB
TypeScript
import fastify from 'fastify';
|
|
import { TRPCError } from '@trpc/server';
|
|
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { router as gameRouter, sessionActivityProcedure } from '../src/trpc.js';
|
|
|
|
const internalFailure = () => {
|
|
throw new Error('synthetic-db-secret', { cause: new Error('synthetic-cause-secret') });
|
|
};
|
|
const badInput = () => {
|
|
throw new TRPCError({
|
|
code: 'BAD_REQUEST',
|
|
message: '잘못된 입력입니다.',
|
|
cause: new Error('synthetic-private-cause'),
|
|
});
|
|
};
|
|
|
|
describe('public HTTP error privacy', () => {
|
|
it('the real router factory hides internal errors and all stacks while preserving validation messages', async () => {
|
|
const app = fastify();
|
|
await app.register(fastifyTRPCPlugin, {
|
|
prefix: '/game',
|
|
trpcOptions: {
|
|
router: gameRouter({
|
|
internal: sessionActivityProcedure.query(internalFailure),
|
|
invalid: sessionActivityProcedure.query(badInput),
|
|
}),
|
|
createContext: () => ({}),
|
|
},
|
|
});
|
|
try {
|
|
const address = await app.listen({ host: '127.0.0.1', port: 0 });
|
|
for (const prefix of ['game']) {
|
|
for (const procedure of ['internal', 'invalid']) {
|
|
const response = await fetch(`${address}/${prefix}/${procedure}`);
|
|
const body = (await response.json()) as {
|
|
error: { message: string; data: { code: string; stack?: string } };
|
|
};
|
|
expect(response.status).toBe(procedure === 'internal' ? 500 : 400);
|
|
expect(body.error.message).toBe(
|
|
procedure === 'internal' ? 'Internal server error' : '잘못된 입력입니다.'
|
|
);
|
|
expect(body.error.data.stack).toBeUndefined();
|
|
expect(JSON.stringify(body)).not.toContain('synthetic-');
|
|
}
|
|
}
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
});
|