test(comments): не-вакуумные тесты audit-swallow + dominant-run; CHANGELOG (#512 ревью)

DO1: тест audit-swallow был вакуумен (log() — fire-and-forget void persist(),
.not.toThrow() зелен независимо от try/catch). Теперь: после failInsert()
шпионим Logger.warn + слушаем unhandledRejection, флашим микро/макротаски,
ассертим warn=1 и 0 floating-rejection. Mutation: убрать try/catch у persist
→ красный.
DO2: dominant-run тест не отличал longest от first (самый длинный ран был и
первым). Перестроено: короткий plain ран впереди, длинный bold — следом →
ассерт bold:true держится только при genuine longest; +тест tie→first.
Mutation: reduce→segments[0] → красный.
DO3: CHANGELOG [Unreleased] — 3 записи по #496.
DO4: убран no-op override insertAttrs.comment (=dominant.markAttrs, а он и есть
attributes['comment'] — сегменты фильтруются по нему; {...attributes,comment:
attributes.comment}={...attributes}); коммент про 'defensive re-assert' исправлен.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 23:43:26 +03:00
parent 0099ba272d
commit eae7640f30
4 changed files with 93 additions and 19 deletions
@@ -1,3 +1,4 @@
import { Logger } from '@nestjs/common';
import { DatabaseAuditService } from './database-audit.service';
import { AUDIT_CONTEXT_KEY } from '../../common/middlewares/audit-context.middleware';
import { AuditEvent, AuditResource } from '../../common/events/audit-events';
@@ -115,13 +116,39 @@ describe('DatabaseAuditService', () => {
expect(inserted).toHaveLength(0);
});
it('a failed insert never throws to the caller (audit is a side-record)', async () => {
const { service, failInsert } = makeService(ctx());
failInsert();
// Must not reject / throw.
expect(() => service.log(applyPayload())).not.toThrow();
await Promise.resolve();
await Promise.resolve();
it('a failed insert is swallowed with a warn and floats no rejection (audit is a side-record)', async () => {
// This pins the load-bearing swallow inside persist(). Because log() is
// fire-and-forget (`void this.persist(...)`), it always returns synchronously
// without throwing — so `not.toThrow()` alone would stay green even if the
// try/catch were removed. We instead observe the two effects the catch is
// responsible for: a warn IS emitted, and NO unhandled rejection floats.
// Removing persist()'s try/catch reddens both assertions (warn count 0 + a
// captured rejection).
const warnSpy = jest
.spyOn(Logger.prototype, 'warn')
.mockImplementation(() => undefined as any);
const rejections: unknown[] = [];
const onRejection = (err: unknown) => rejections.push(err);
process.on('unhandledRejection', onRejection);
try {
const { service, failInsert } = makeService(ctx());
failInsert();
expect(() => service.log(applyPayload())).not.toThrow();
// Flush microtasks so the rejected insert settles, then give any floated
// rejection a macrotask tick to be reported by the runtime.
await Promise.resolve();
await Promise.resolve();
await new Promise((r) => setImmediate(r));
expect(warnSpy).toHaveBeenCalledTimes(1);
expect(String(warnSpy.mock.calls[0][0])).toContain(
'Failed to persist audit event',
);
expect(rejections).toHaveLength(0);
} finally {
process.off('unhandledRejection', onRejection);
warnSpy.mockRestore();
}
});
it('logWithContext() persists with an explicit (non-request) context', async () => {