From e47cf6e7757306873056cc5d072a4e44f85e1ef5 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Wed, 8 Jul 2026 15:53:20 +0200 Subject: [PATCH] fix(api): throw NOT_FOUND on lock/password mutations for missing resume --- .../api/src/features/resume/service.test.ts | 27 ++++++++++--------- packages/api/src/features/resume/service.ts | 6 ++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/api/src/features/resume/service.test.ts b/packages/api/src/features/resume/service.test.ts index 8401daf6a..016b7407a 100644 --- a/packages/api/src/features/resume/service.test.ts +++ b/packages/api/src/features/resume/service.test.ts @@ -187,12 +187,13 @@ describe("setLocked", () => { expect(publishResumeUpdatedMock).toHaveBeenCalledWith(expect.objectContaining({ mutation: "lock" })); }); - // Characterizes current behavior: silent no-op when no row matches. Plan 003 flips this to - // reject with NOT_FOUND (that test diff is the intended signal the behavior changed). - it("silently resolves undefined when no row matches, without notifying", async () => { + // Plan 003: no matching row now rejects with NOT_FOUND (previously a silent resolve). + it("throws NOT_FOUND when no row matches, without notifying", async () => { dbMock.update.mockReturnValueOnce(createUpdateChain([]).chain); - await expect(resumeService.setLocked({ id: "r1", userId: "u1", isLocked: true })).resolves.toBeUndefined(); + await expect(resumeService.setLocked({ id: "r1", userId: "u1", isLocked: true })).rejects.toMatchObject({ + code: "NOT_FOUND", + }); expect(publishResumeUpdatedMock).not.toHaveBeenCalled(); }); }); @@ -210,12 +211,13 @@ describe("setPassword", () => { expect(publishResumeUpdatedMock).toHaveBeenCalledWith(expect.objectContaining({ mutation: "password" })); }); - // Characterizes current behavior: silent no-op when no row matches. Plan 003 flips this to - // reject with NOT_FOUND (that test diff is the intended signal the behavior changed). - it("silently resolves undefined when no row matches, without notifying", async () => { + // Plan 003: no matching row now rejects with NOT_FOUND (previously a silent resolve). + it("throws NOT_FOUND when no row matches, without notifying", async () => { dbMock.update.mockReturnValueOnce(createUpdateChain([]).chain); - await expect(resumeService.setPassword({ id: "r1", userId: "u1", password: "secret" })).resolves.toBeUndefined(); + await expect(resumeService.setPassword({ id: "r1", userId: "u1", password: "secret" })).rejects.toMatchObject({ + code: "NOT_FOUND", + }); expect(publishResumeUpdatedMock).not.toHaveBeenCalled(); }); }); @@ -232,12 +234,13 @@ describe("removePassword", () => { expect(publishResumeUpdatedMock).toHaveBeenCalledWith(expect.objectContaining({ mutation: "password" })); }); - // Characterizes current behavior: silent no-op when no row matches. Plan 003 flips this to - // reject with NOT_FOUND (that test diff is the intended signal the behavior changed). - it("silently resolves undefined when no row matches, without notifying", async () => { + // Plan 003: no matching row now rejects with NOT_FOUND (previously a silent resolve). + it("throws NOT_FOUND when no row matches, without notifying", async () => { dbMock.update.mockReturnValueOnce(createUpdateChain([]).chain); - await expect(resumeService.removePassword({ id: "r1", userId: "u1" })).resolves.toBeUndefined(); + await expect(resumeService.removePassword({ id: "r1", userId: "u1" })).rejects.toMatchObject({ + code: "NOT_FOUND", + }); expect(publishResumeUpdatedMock).not.toHaveBeenCalled(); }); }); diff --git a/packages/api/src/features/resume/service.ts b/packages/api/src/features/resume/service.ts index 36d847f49..4e00f749d 100644 --- a/packages/api/src/features/resume/service.ts +++ b/packages/api/src/features/resume/service.ts @@ -667,7 +667,7 @@ export const resumeService = { .where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId))) .returning({ id: schema.resume.id, updatedAt: schema.resume.updatedAt }); - if (!resume) return; + if (!resume) throw new ORPCError("NOT_FOUND"); await notifyResumeUpdated({ type: "resume.updated", @@ -687,7 +687,7 @@ export const resumeService = { .where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId))) .returning({ id: schema.resume.id, updatedAt: schema.resume.updatedAt }); - if (!resume) return; + if (!resume) throw new ORPCError("NOT_FOUND"); await notifyResumeUpdated({ type: "resume.updated", @@ -730,7 +730,7 @@ export const resumeService = { .where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId))) .returning({ id: schema.resume.id, updatedAt: schema.resume.updatedAt }); - if (!resume) return; + if (!resume) throw new ORPCError("NOT_FOUND"); await notifyResumeUpdated({ type: "resume.updated",