From 65ba2e5ee3e0fdf499ce6b0ddaeb36a94c2add23 Mon Sep 17 00:00:00 2001 From: priosshrsth Date: Thu, 6 Aug 2026 12:00:34 +0000 Subject: [PATCH] OUT-4027 | Fix 500 when deleting a task whose label row is missing deleteLabel passed `id: currentLabel?.id` straight into label.delete, so when findFirst matched nothing Prisma got `{ id: undefined }` and threw PrismaClientValidationError, failing the whole delete transaction. Return early instead. Rows go missing because softDeleteAllSubtasks soft-deletes Labels rows by label string, which is not unique across workspaces. Fixed separately in OUT-4029. Co-Authored-By: Claude Opus 5 (1M context) --- .../label-mapping.service.test.ts | 44 +++++++++++++++++++ .../label-mapping/label-mapping.service.ts | 3 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/app/api/label-mapping/label-mapping.service.test.ts diff --git a/src/app/api/label-mapping/label-mapping.service.test.ts b/src/app/api/label-mapping/label-mapping.service.test.ts new file mode 100644 index 000000000..24a195ede --- /dev/null +++ b/src/app/api/label-mapping/label-mapping.service.test.ts @@ -0,0 +1,44 @@ +const mockLabelFindFirst = jest.fn() +const mockLabelDelete = jest.fn() + +jest.mock('@/lib/db', () => ({ + __esModule: true, + default: { + getInstance: () => ({ + label: { findFirst: mockLabelFindFirst, delete: mockLabelDelete }, + }), + }, +})) + +jest.mock('@/utils/CopilotAPI', () => ({ CopilotAPI: jest.fn() })) + +import { LabelMappingService } from '@api/label-mapping/label-mapping.service' +import User from '@api/core/models/User.model' +import { UserRole } from '@api/core/types/user' + +const user = { + workspaceId: 'ws-1', + role: UserRole.IU, + internalUserId: 'iu-1', + token: 'token', +} as unknown as User + +describe('LabelMappingService#deleteLabel', () => { + beforeEach(() => jest.clearAllMocks()) + + it('deletes the matching label row', async () => { + mockLabelFindFirst.mockResolvedValue({ id: 'label-1' }) + + await new LabelMappingService(user).deleteLabel('ASS10-009') + + expect(mockLabelDelete).toHaveBeenCalledWith({ where: { id: 'label-1' } }) + }) + + it('no-ops when the label row is already gone', async () => { + mockLabelFindFirst.mockResolvedValue(null) + + await new LabelMappingService(user).deleteLabel('ASS10-009') + + expect(mockLabelDelete).not.toHaveBeenCalled() + }) +}) diff --git a/src/app/api/label-mapping/label-mapping.service.ts b/src/app/api/label-mapping/label-mapping.service.ts index 42007a03c..b0d9c2abd 100644 --- a/src/app/api/label-mapping/label-mapping.service.ts +++ b/src/app/api/label-mapping/label-mapping.service.ts @@ -175,9 +175,10 @@ export class LabelMappingService extends BaseService { label, }, }) + if (!currentLabel) return await this.db.label.delete({ where: { - id: currentLabel?.id, + id: currentLabel.id, }, }) }