diff --git a/apps/server/src/core/comment/comment.service.mark-async.spec.ts b/apps/server/src/core/comment/comment.service.mark-async.spec.ts index 89a87d1d..ed79d72c 100644 --- a/apps/server/src/core/comment/comment.service.mark-async.spec.ts +++ b/apps/server/src/core/comment/comment.service.mark-async.spec.ts @@ -1,6 +1,10 @@ +import { Logger } from '@nestjs/common'; import { CommentService } from './comment.service'; import { QueueJob } from '../../integrations/queue/constants'; +// Flush pending microtasks so a fire-and-forget `.catch(...)` runs before we assert. +const flushMicrotasks = () => new Promise((r) => setImmediate(r)); + /** * #399: the comment inline-mark update is moved OFF the HTTP critical path. * resolveComment / unresolve / the ephemeral-suggestion delete must NO LONGER @@ -150,4 +154,26 @@ describe('CommentService — async comment mark (#399)', () => { 'delete-row', ]); }); + + it('resolve is fire-and-forget: a queue-add rejection does NOT fail the HTTP call (best-effort warn)', async () => { + const { service, generalQueue } = makeService(); + // The queue is unavailable — the whole point of #399 is that this must NOT + // propagate out of resolveComment onto the HTTP request. + const queueErr = new Error('queue is down'); + generalQueue.add.mockRejectedValue(queueErr); + const warnSpy = jest + .spyOn(Logger.prototype, 'warn') + .mockImplementation(() => undefined); + + // Must resolve, never throw, even though the enqueue rejects. + await expect(service.resolveComment(comment(), true, user())).resolves.not.toThrow(); + + // The rejection is swallowed on a microtask AFTER the method returns; flush it. + await flushMicrotasks(); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('Failed to enqueue comment mark update for comment c-1'), + queueErr, + ); + warnSpy.mockRestore(); + }); });