test: cover the six untested tRPC routers - #439
Merged
Conversation
…undaries Adds router tests for auth, chat, documents, orders, organizations and tasks, following the mocking conventions in billing.test.ts and admin.test.ts. Each file covers every procedure's happy path, the authorization outcome for each procedure (unauthenticated caller, non-member on orgProcedure, non-owner on role-gated deletes), zod input rejection, and the not-found/conflict/forbidden paths the routers already implement. No router code was changed; tests that hit suspicious behavior document it as current behavior. Also adds expectTRPCError to __tests__/setup/utils.ts so tests assert on the TRPCError code rather than only the message.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's Changed
Tests for the six tRPC routers that had none:
auth,chat,documents,orders,organizations,tasks. Onlyadmin,billinganduserwere covered.An untested router is an untested authorization boundary. The procedure ladder in
lib/trpc/init.tsis where authorization actually lives for most of this app, so this was the largest gap in an otherwise well-covered repo.Each router covers every procedure's happy path, the authorization case per procedure (unauthenticated against protected, non-member against
orgProcedure, non-owner againstorgOwnerProcedure, non-admin againstsuperAdminProcedure), input validation rejection, and the error paths already present in the router code.Also adds
expectTRPCErrorto__tests__/setup/utils.ts, becauserejects.toThrow('...')passes for the wrong error code. Authorization tests now assert on the TRPCError code.Type of Change
Testing
No router code was changed. Where a test documents a bug, it asserts current behavior and the bug is listed below rather than fixed here, so this PR stays reviewable as a test-only change.
Bugs found while writing these
Nothing here is fixed. Triage order would be 3, 4, 5.
3.
chat.deleteSessionandchat.updateSessionare not user-scoped, but the reads are.listSessions/getSession/getMessagesfilter onchatSessions.userId === ctx.userId. The two mutations filter only onorganizationId. Any member of an org can delete or rename another member's private chat session, including sessions they cannot read. Most serious finding.4.
tasks.updatemoves a task into any organization without a membership check.tasksRouterisprotectedProcedureand forwardsinput.organizationIdtotaskService.updateTask, which only verifies task ownership. A user can attach their task to an org they do not belong to and it surfaces in that org's task list. Same fortasks.create.5.
organizations.getUserOrganizationstakesuserIdfrom input and never compares it toctx.userId. An arbitrary UUID is forwarded toauth.api.listOrganizations. Exploitability depends on whether Better Auth ignores that query for non-admin sessions, which is worth confirming.ctx.userIdis right there.6. The whole
organizationsrouter isprotectedProcedure.deleteOrganization,removeMember,updateMemberRole,inviteMembertakeorganizationIdfrom input with all enforcement delegated to Better Auth inside the services. Defensible, but the procedure ladder does nothing here and every service is individually load-bearing.orgProcedure/orgOwnerProcedureexist and are unused.1.
orders.list()with no input crashes instead of returning BAD_REQUEST.orgProceduredoes(await getRawInput()).organizationId; with an.optional()input schemarawInputisundefined, so it is a TypeError surfacing as INTERNAL_SERVER_ERROR. Affects anyorgProcedurewith an optional input schema.2.
getOrgMembersSchema.offsetcan never be sent explicitly.z.number().int().positive().default(0)— Zod does not validate defaults, so the implicit 0 works but a client sendingoffset: 0gets BAD_REQUEST. Should be.min(0).7.
auth.requestOtpis a user-enumeration oracle. Sign-in with an unknown email returnsNOT_FOUND: User not found; sign-up with a known email returnsBAD_REQUEST: User already exists. Both confirm account existence to an unauthenticated caller.8.
auth.requestOtplogs the email before validating it, so rejected attempts still write PII to logs.9. Dead branch in
auth.requestOtp: the!input.termscheck is unreachable becausesignUpSchemaalready refinesterms === true.10.
getContentTypeByExtensionreturns MIME typesuploadDocumentSchemarejects..tsmaps totext/typescriptand.ppttoapplication/vnd.ms-powerpoint, neither inSUPPORTED_MIME_TYPES. Uploading those fails with "Unsupported file type" though the extension map claims support.11.
documents.listcounts rows in JS. It selects every matching id then takes.length.chat.listSessionsnext door uses a SQLcount().12.
getUserByEmailis typed non-nullable but returnsnull.13.
__tests__/lib/trpc/routers/user.test.tsdoes not test the user router. All seven cases exerciseorganizations.uploadOrganizationLogo/deleteOrganizationLogo. Theuserrouter is still untested; it is a tenth router that the "six of nine" count missed.🤖 Generated with Claude Code
https://claude.ai/code/session_01GyBfVKhYQGLZhkcWiHaruY