diff --git a/src/modules/configuration/employees/employees.repository.spec.ts b/src/modules/configuration/employees/employees.repository.spec.ts index 81a0319..34a1d81 100644 --- a/src/modules/configuration/employees/employees.repository.spec.ts +++ b/src/modules/configuration/employees/employees.repository.spec.ts @@ -220,6 +220,26 @@ describe('EmployeesRepository', () => { ); }); + it('delete maps foreign-key violations to ConflictException', async () => { + returning.mockRejectedValueOnce({ code: '23503' }); + await expect(repository.delete('emp-1')).rejects.toMatchObject({ + constructor: ConflictException, + message: 'Employee is referenced by other records', + }); + + returning.mockRejectedValueOnce({ + cause: { code: '23503' }, + }); + await expect(repository.delete('emp-1')).rejects.toBeInstanceOf( + ConflictException, + ); + }); + + it('delete rethrows unknown errors', async () => { + returning.mockRejectedValueOnce(new Error('db down')); + await expect(repository.delete('emp-1')).rejects.toThrow('db down'); + }); + it('bulkUpdateStatus and bulkDelete return 0 for empty ids', async () => { await expect( repository.bulkUpdateStatus([], Status.create('active'), 'user-1'), @@ -240,6 +260,16 @@ describe('EmployeesRepository', () => { await expect(repository.bulkDelete(['emp-1'])).resolves.toBe(1); }); + it('bulkDelete maps foreign-key violations to ConflictException', async () => { + returning.mockRejectedValueOnce({ + cause: { code: '23503' }, + }); + await expect(repository.bulkDelete(['emp-1'])).rejects.toMatchObject({ + constructor: ConflictException, + message: 'Employee is referenced by other records', + }); + }); + it('extendListQuery is a passthrough hook', () => { const qb = { join: true }; expect(repository.extendListQuery(qb, { limit: 10, offset: 0 })).toBe(qb); diff --git a/src/modules/configuration/employees/employees.repository.ts b/src/modules/configuration/employees/employees.repository.ts index bbb88e8..4a1a5e9 100644 --- a/src/modules/configuration/employees/employees.repository.ts +++ b/src/modules/configuration/employees/employees.repository.ts @@ -221,12 +221,16 @@ export class EmployeesRepository { } async delete(id: string): Promise { - const deleted = await this.db - .delete(employees) - .where(eq(employees.id, id)) - .returning({ id: employees.id }); - if (deleted.length === 0) { - throw new NotFoundException('Employee not found'); + try { + const deleted = await this.db + .delete(employees) + .where(eq(employees.id, id)) + .returning({ id: employees.id }); + if (deleted.length === 0) { + throw new NotFoundException('Employee not found'); + } + } catch (error) { + this.rethrowForeignKeyViolation(error); } } @@ -234,11 +238,15 @@ export class EmployeesRepository { if (ids.length === 0) { return 0; } - const deleted = await this.db - .delete(employees) - .where(inArray(employees.id, ids)) - .returning({ id: employees.id }); - return deleted.length; + try { + const deleted = await this.db + .delete(employees) + .where(inArray(employees.id, ids)) + .returning({ id: employees.id }); + return deleted.length; + } catch (error) { + this.rethrowForeignKeyViolation(error); + } } private buildListWhere(filters: ListEmployeesFilters): SQL | undefined { @@ -363,6 +371,14 @@ export class EmployeesRepository { throw error; } + private rethrowForeignKeyViolation(error: unknown): never { + const err = this.unwrapDbError(error); + if (err.code === '23503') { + throw new ConflictException('Employee is referenced by other records'); + } + throw error; + } + private unwrapDbError(error: unknown): { code?: string; constraint?: string;