Skip to content

Commit 0a5e99f

Browse files
feat(nestjs): Handle Necord contexts in SentryGlobalFilter
Fixes #18472 Co-authored-by: Tiago Vilas Boas <tcarvalhovb@gmail.com>
1 parent 29de504 commit 0a5e99f

2 files changed

Lines changed: 66 additions & 0 deletions

File tree

‎packages/nestjs/src/setup.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,25 @@ class SentryGlobalFilter extends BaseExceptionFilter {
179179
return;
180180
}
181181

182+
// Necord sets ExecutionContext type to 'necord' (see NecordContextCreator).
183+
// BaseExceptionFilter expects an HTTP adapter and cannot reply to Discord interactions.
184+
if (contextType === 'necord') {
185+
if (!isExpectedError(exception)) {
186+
captureException(exception, {
187+
mechanism: {
188+
handled: false,
189+
type: 'auto.necord.nestjs.global_filter',
190+
},
191+
});
192+
}
193+
194+
if (exception instanceof Error) {
195+
this._logger.error(exception.message, exception.stack);
196+
}
197+
198+
return;
199+
}
200+
182201
// HTTP exceptions
183202
if (!isExpectedError(exception)) {
184203
captureException(exception, {

‎packages/nestjs/test/sentry-global-filter.test.ts‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/unbound-method */
22
import type { ArgumentsHost } from '@nestjs/common';
33
import { HttpException, HttpStatus, Logger } from '@nestjs/common';
4+
import { BaseExceptionFilter } from '@nestjs/core';
45
import * as SentryCore from '@sentry/core';
56
import { beforeEach, describe, expect, it, vi } from 'vitest';
67
import * as Helpers from '../src/helpers';
@@ -322,4 +323,50 @@ describe('SentryGlobalFilter', () => {
322323
expect(mockLoggerError).toHaveBeenCalledWith(error.message, error.stack);
323324
});
324325
});
326+
327+
describe('Necord context', () => {
328+
beforeEach(() => {
329+
vi.mocked(mockArgumentsHost.getType).mockReturnValue('necord');
330+
});
331+
332+
it('captures unexpected errors without delegating to the HTTP exception filter', () => {
333+
const superCatchSpy = vi.spyOn(BaseExceptionFilter.prototype, 'catch').mockImplementation(() => undefined);
334+
const error = new Error('Slash command failed');
335+
336+
filter.catch(error, mockArgumentsHost);
337+
338+
expect(mockCaptureException).toHaveBeenCalledWith(error, {
339+
mechanism: {
340+
handled: false,
341+
type: 'auto.necord.nestjs.global_filter',
342+
},
343+
});
344+
expect(mockLoggerError).toHaveBeenCalledWith(error.message, error.stack);
345+
expect(superCatchSpy).not.toHaveBeenCalled();
346+
});
347+
348+
it('does not capture expected Necord exceptions', () => {
349+
isExpectedErrorMock.mockReturnValueOnce(true);
350+
const exception = new HttpException('Unknown interaction', HttpStatus.BAD_REQUEST);
351+
352+
filter.catch(exception, mockArgumentsHost);
353+
354+
expect(mockCaptureException).not.toHaveBeenCalled();
355+
expect(mockLoggerError).toHaveBeenCalledWith(exception.message, exception.stack);
356+
});
357+
358+
it('captures unexpected non-Error values', () => {
359+
const nonErrorObject = { message: 'interaction failed' };
360+
361+
filter.catch(nonErrorObject, mockArgumentsHost);
362+
363+
expect(mockCaptureException).toHaveBeenCalledWith(nonErrorObject, {
364+
mechanism: {
365+
handled: false,
366+
type: 'auto.necord.nestjs.global_filter',
367+
},
368+
});
369+
expect(mockLoggerError).not.toHaveBeenCalled();
370+
});
371+
});
325372
});

0 commit comments

Comments
 (0)