From d84e5ddbad1ecdd7e48a633ec99ee52f8c363c8c Mon Sep 17 00:00:00 2001 From: agent_coder Date: Fri, 10 Jul 2026 05:32:08 +0300 Subject: [PATCH] =?UTF-8?q?test(comment):=20=D0=BF=D0=BE=D0=BA=D1=80=D1=8B?= =?UTF-8?q?=D1=82=D1=8C=20fire-and-forget=20resolve-=D0=BF=D1=83=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BF=D1=80=D0=B8=20=D0=BE=D1=82=D0=BA=D0=B0=D0=B7?= =?UTF-8?q?=D0=B5=20=D0=BE=D1=87=D0=B5=D1=80=D0=B5=D0=B4=D0=B8=20(=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=8C=D1=8E=20#438)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolve/unresolve enqueue — fire-and-forget (void ...catch(warn)): смысл #399 в том, что недоступность очереди НЕ должна ронять HTTP-запрос. Delete-путь уже покрыт (enqueue awaited перед hard-delete), а reject resolve-пути — нет. Тест: generalQueue.add реджектит -> resolveComment всё равно resolves (не throws) + warn залогирован (ошибка проглочена на микротаске после возврата, поэтому flushMicrotasks перед ассертом). Мутационно: сделать enqueue awaited без catch -> тест краснеет. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../comment.service.mark-async.spec.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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(); + }); });