From 7f1d2095e4d2d44435830d6c9789727a5eb1201b Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Fri, 25 Oct 2024 09:15:06 +0100 Subject: [PATCH] Hotfix: promise rejection configuration (#2239) * fix: :bug: ensure reportUnhandledPromiseRejectionsAsHandled is impemented in all platforms * update CHANGELOG.md * test: :adhesive_bandage: fix lineNumber assertion * test: :white_check_mark: add react native scenario for reportUnhandledPromiseRejectionsAsHandled * build: :adhesive_bandage: update react-native fixture generate script * fix: :pencil2: update typo in scenario name --- CHANGELOG.md | 6 ++++++ packages/browser/test/index.test.ts | 16 +++++++++++----- packages/electron/test/type.test.ts | 3 ++- packages/react-native/src/config.js | 2 +- packages/react-native/test/index.test.ts | 8 ++++++++ packages/react-native/types/bugsnag.d.ts | 2 +- scripts/generate-react-native-fixture.js | 2 +- .../unhandled-promise-rejection-as-handled.js | 11 +++++++++++ test/node/features/unhandled_errors.feature | 10 +++++----- .../fixtures/app/scenario_js/app/Scenarios.js | 1 + ...handledJsPromiseRejectionAsHandledScenario.js | 12 ++++++++++++ ...handledJsPromiseRejectionAsHandledScenario.js | 12 ++++++++++++ .../scenario-launcher/src/scenarios/index.js | 1 + .../features/unhandled-android.feature | 8 ++++++++ test/react-native/features/unhandled-ios.feature | 8 ++++++++ 15 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 test/node/features/fixtures/unhandled/scenarios/unhandled-promise-rejection-as-handled.js create mode 100644 test/react-native/features/fixtures/app/scenario_js/app/scenarios/UnhandledJsPromiseRejectionAsHandledScenario.js create mode 100644 test/react-native/features/fixtures/scenario-launcher/src/scenarios/UnhandledJsPromiseRejectionAsHandledScenario.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c32e30b94f..ca4ac6b177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [8.1.2] - 2024-10-24 + +### Fixed + +- Ensure `reportUnhandledPromiseRejectionsAsHandled` is correctly handled for all platforms [#2239](https://github.com/bugsnag/bugsnag-js/pull/2239) + ## [8.1.1] - 2024-10-23 ### Fixed diff --git a/packages/browser/test/index.test.ts b/packages/browser/test/index.test.ts index cc18f3cb3f..7e39b8cb13 100644 --- a/packages/browser/test/index.test.ts +++ b/packages/browser/test/index.test.ts @@ -1,4 +1,4 @@ -import BugsnagBrowserStatic, { Breadcrumb, Session } from '../src/notifier' +import BugsnagBrowserStatic, { Breadcrumb, BrowserConfig, Session } from '../src/notifier' const DONE = window.XMLHttpRequest.DONE @@ -137,7 +137,8 @@ describe('browser notifier', () => { it('accepts all config options', (done) => { const Bugsnag = getBugsnag() - Bugsnag.start({ + + const completeConfig: Required = { apiKey: API_KEY, appVersion: '1.2.3', appType: 'worker', @@ -161,18 +162,23 @@ describe('browser notifier', () => { releaseStage: 'production', maxBreadcrumbs: 20, enabledBreadcrumbTypes: ['manual', 'log', 'request'], + context: 'contextual', + featureFlags: [], + plugins: [], user: null, metadata: { debug: { foo: 'bar' } }, - logger: undefined, + logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() }, redactedKeys: ['foo', /bar/], collectUserIp: true, maxEvents: 10, generateAnonymousId: false, - trackInlineScripts: true - }) + trackInlineScripts: true, + reportUnhandledPromiseRejectionsAsHandled: true + } + Bugsnag.start(completeConfig) Bugsnag.notify(new Error('123'), (event) => { return false }, (err, event) => { diff --git a/packages/electron/test/type.test.ts b/packages/electron/test/type.test.ts index 011d1bdc1d..cc4c6fd2b9 100644 --- a/packages/electron/test/type.test.ts +++ b/packages/electron/test/type.test.ts @@ -32,7 +32,8 @@ describe.skip('@bugsnag/electron types', () => { plugins: [], user: { id: '1234-abcd' }, appType: 'good', - codeBundleId: '1245' + codeBundleId: '1245', + reportUnhandledPromiseRejectionsAsHandled: true }) }) }) diff --git a/packages/react-native/src/config.js b/packages/react-native/src/config.js index 7e1d79e18c..da2be13468 100644 --- a/packages/react-native/src/config.js +++ b/packages/react-native/src/config.js @@ -3,7 +3,7 @@ const stringWithLength = require('@bugsnag/core/lib/validators/string-with-lengt const rnPackage = require('react-native/package.json') const iserror = require('iserror') -const ALLOWED_IN_JS = ['onError', 'onBreadcrumb', 'logger', 'metadata', 'user', 'context', 'codeBundleId', 'plugins', 'featureFlags'] +const ALLOWED_IN_JS = ['onError', 'onBreadcrumb', 'logger', 'metadata', 'user', 'context', 'codeBundleId', 'plugins', 'featureFlags', 'reportUnhandledPromiseRejectionsAsHandled'] const allowedErrorTypes = () => ({ unhandledExceptions: true, unhandledRejections: true, diff --git a/packages/react-native/test/index.test.ts b/packages/react-native/test/index.test.ts index 1e687876f4..857b848c08 100644 --- a/packages/react-native/test/index.test.ts +++ b/packages/react-native/test/index.test.ts @@ -108,6 +108,14 @@ describe('react native notifier', () => { expect(NativeModules.BugsnagReactNative.addFeatureFlags).toHaveBeenCalled() }) + it('accepts the reportUnhandledPromiseRejectionsAsHandled config option', () => { + const warnMock = jest.spyOn(console, 'warn').mockImplementation(() => {}) + + Bugsnag.start({ reportUnhandledPromiseRejectionsAsHandled: true }) + + expect(warnMock).not.toHaveBeenCalled() + }) + describe('isStarted()', () => { it('returns false when the notifier has not been initialized', () => { expect(Bugsnag.isStarted()).toBe(false) diff --git a/packages/react-native/types/bugsnag.d.ts b/packages/react-native/types/bugsnag.d.ts index 456282e00c..9bd1ff3766 100644 --- a/packages/react-native/types/bugsnag.d.ts +++ b/packages/react-native/types/bugsnag.d.ts @@ -5,7 +5,7 @@ interface ReactNativeSchema extends Config { } // these properties are allowed to be configured in the JS layer -type Configurable = 'onError' | 'onBreadcrumb' | 'logger' | 'metadata' | 'user' | 'context' | 'plugins' | 'codeBundleId' | 'featureFlags' +type Configurable = 'onError' | 'onBreadcrumb' | 'logger' | 'metadata' | 'user' | 'context' | 'plugins' | 'codeBundleId' | 'featureFlags' | 'reportUnhandledPromiseRejectionsAsHandled' type ReactNativeConfig = Pick diff --git a/scripts/generate-react-native-fixture.js b/scripts/generate-react-native-fixture.js index 2f64498c82..b2496e8217 100644 --- a/scripts/generate-react-native-fixture.js +++ b/scripts/generate-react-native-fixture.js @@ -61,7 +61,7 @@ if (!process.env.SKIP_GENERATE_FIXTURE) { } // create the test fixture - const RNInitArgs = ['@react-native-community/cli@latest', 'init', 'reactnative', '--directory', fixtureDir, '--version', reactNativeVersion, '--npm', '--skip-install'] + const RNInitArgs = ['@react-native-community/cli@latest', 'init', 'reactnative', '--directory', fixtureDir, '--version', reactNativeVersion, '--pm', 'npm', '--skip-install'] execFileSync('npx', RNInitArgs, { stdio: 'inherit' }) replaceGeneratedFixtureFiles() diff --git a/test/node/features/fixtures/unhandled/scenarios/unhandled-promise-rejection-as-handled.js b/test/node/features/fixtures/unhandled/scenarios/unhandled-promise-rejection-as-handled.js new file mode 100644 index 0000000000..3a88c8296f --- /dev/null +++ b/test/node/features/fixtures/unhandled/scenarios/unhandled-promise-rejection-as-handled.js @@ -0,0 +1,11 @@ +var Bugsnag = require('@bugsnag/node') +Bugsnag.start({ + reportUnhandledPromiseRejectionsAsHandled: true, + apiKey: process.env.BUGSNAG_API_KEY, + endpoints: { + notify: process.env.BUGSNAG_NOTIFY_ENDPOINT, + sessions: process.env.BUGSNAG_SESSIONS_ENDPOINT + } +}) + +Promise.reject(new Error('not handled')) diff --git a/test/node/features/unhandled_errors.feature b/test/node/features/unhandled_errors.feature index ffdc256eb2..5d78c4935a 100644 --- a/test/node/features/unhandled_errors.feature +++ b/test/node/features/unhandled_errors.feature @@ -37,18 +37,18 @@ Scenario: reporting unhandled promise rejections And the "file" of stack frame 0 equals "scenarios/unhandled-promise-rejection.js" And the "lineNumber" of stack frame 0 equals 10 -Scenario: reporting unhandled promise rejections - And I run the service "unhandled" with the command "node scenarios/unhandled-promise-rejection" +Scenario: reporting unhandled promise rejections as handled + And I run the service "unhandled" with the command "node scenarios/unhandled-promise-rejection-as-handled" And I wait to receive an error Then the error is valid for the error reporting API version "4" for the "Bugsnag Node" notifier - And the event "unhandled" is true + And the event "unhandled" is false And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledPromiseRejection" And the exception "errorClass" equals "Error" And the exception "message" equals "not handled" And the exception "type" equals "nodejs" - And the "file" of stack frame 0 equals "scenarios/unhandled-promise-rejection.js" - And the "lineNumber" of stack frame 0 equals 10 + And the "file" of stack frame 0 equals "scenarios/unhandled-promise-rejection-as-handled.js" + And the "lineNumber" of stack frame 0 equals 11 Scenario: not reporting unhandledRejections when autoDetectErrors is off And I run the service "unhandled" with the command "node scenarios/unhandled-promise-rejection-auto-notify-off" diff --git a/test/react-native/features/fixtures/app/scenario_js/app/Scenarios.js b/test/react-native/features/fixtures/app/scenario_js/app/Scenarios.js index 566e4e1ad9..240c2fdbed 100644 --- a/test/react-native/features/fixtures/app/scenario_js/app/Scenarios.js +++ b/test/react-native/features/fixtures/app/scenario_js/app/Scenarios.js @@ -7,6 +7,7 @@ export { UnhandledNativeErrorScenario } from './scenarios/UnhandledNativeErrorSc export { UnhandledJsErrorScenario } from './scenarios/UnhandledJsErrorScenario' export { UnhandledJsErrorSeverityScenario } from './scenarios/UnhandledJsErrorSeverityScenario' export { UnhandledJsPromiseRejectionScenario } from './scenarios/UnhandledJsPromiseRejectionScenario' +export { UnhandledJsPromiseRejectionAsHandledScenario } from './scenarios/UnhandledJsPromiseRejectionAsHandledScenario' export { RCTFatalScenario } from './scenarios/RCTFatalScenario' // api-key-ios.feature diff --git a/test/react-native/features/fixtures/app/scenario_js/app/scenarios/UnhandledJsPromiseRejectionAsHandledScenario.js b/test/react-native/features/fixtures/app/scenario_js/app/scenarios/UnhandledJsPromiseRejectionAsHandledScenario.js new file mode 100644 index 0000000000..6f20c197ba --- /dev/null +++ b/test/react-native/features/fixtures/app/scenario_js/app/scenarios/UnhandledJsPromiseRejectionAsHandledScenario.js @@ -0,0 +1,12 @@ +import Scenario from './Scenario' + +export class UnhandledJsPromiseRejectionAsHandledScenario extends Scenario { + constructor (configuration, jsConfig) { + super() + jsConfig.reportUnhandledPromiseRejectionsAsHandled = true + } + + run () { + Promise.reject(new Error('UnhandledJsPromiseRejectionAsHandledScenario')) + } +} diff --git a/test/react-native/features/fixtures/scenario-launcher/src/scenarios/UnhandledJsPromiseRejectionAsHandledScenario.js b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/UnhandledJsPromiseRejectionAsHandledScenario.js new file mode 100644 index 0000000000..6f20c197ba --- /dev/null +++ b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/UnhandledJsPromiseRejectionAsHandledScenario.js @@ -0,0 +1,12 @@ +import Scenario from './Scenario' + +export class UnhandledJsPromiseRejectionAsHandledScenario extends Scenario { + constructor (configuration, jsConfig) { + super() + jsConfig.reportUnhandledPromiseRejectionsAsHandled = true + } + + run () { + Promise.reject(new Error('UnhandledJsPromiseRejectionAsHandledScenario')) + } +} diff --git a/test/react-native/features/fixtures/scenario-launcher/src/scenarios/index.js b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/index.js index 51a89fcc48..ddc767cfa5 100644 --- a/test/react-native/features/fixtures/scenario-launcher/src/scenarios/index.js +++ b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/index.js @@ -7,6 +7,7 @@ export { UnhandledNativeErrorScenario } from './UnhandledNativeErrorScenario' export { UnhandledJsErrorScenario } from './UnhandledJsErrorScenario' export { UnhandledJsErrorSeverityScenario } from './UnhandledJsErrorSeverityScenario' export { UnhandledJsPromiseRejectionScenario } from './UnhandledJsPromiseRejectionScenario' +export { UnhandledJsPromiseRejectionAsHandledScenario } from './UnhandledJsPromiseRejectionAsHandledScenario' export { RCTFatalScenario } from './RCTFatalScenario' // api-key-ios.feature diff --git a/test/react-native/features/unhandled-android.feature b/test/react-native/features/unhandled-android.feature index c3b1af3d43..5a6fc616c4 100644 --- a/test/react-native/features/unhandled-android.feature +++ b/test/react-native/features/unhandled-android.feature @@ -18,6 +18,14 @@ Scenario: Reporting an Unhandled promise rejection And the event "unhandled" is true And the exception "message" equals "UnhandledJsPromiseRejectionScenario" +Scenario: Reporting an Unhandled promise rejection as handled + When I run "UnhandledJsPromiseRejectionAsHandledScenario" + Then I wait to receive an error + And the exception "errorClass" equals "Error" + And the exception "type" equals "reactnativejs" + And the event "unhandled" is false + And the exception "message" equals "UnhandledJsPromiseRejectionAsHandledScenario" + Scenario: Reporting an Unhandled Native error When I run "UnhandledNativeErrorScenario" and relaunch the crashed app And I configure Bugsnag for "UnhandledNativeErrorScenario" diff --git a/test/react-native/features/unhandled-ios.feature b/test/react-native/features/unhandled-ios.feature index cfb53d14c2..91bad5d958 100644 --- a/test/react-native/features/unhandled-ios.feature +++ b/test/react-native/features/unhandled-ios.feature @@ -18,6 +18,14 @@ Scenario: Reporting an Unhandled promise rejection And the event "unhandled" is true And the exception "message" equals "UnhandledJsPromiseRejectionScenario" +Scenario: Reporting an Unhandled promise rejection as handled + When I run "UnhandledJsPromiseRejectionAsHandledScenario" + Then I wait to receive an error + And the exception "errorClass" equals "Error" + And the exception "type" equals "reactnativejs" + And the event "unhandled" is false + And the exception "message" equals "UnhandledJsPromiseRejectionAsHandledScenario" + Scenario: Reporting an Unhandled Native error When I run "UnhandledNativeErrorScenario" and relaunch the crashed app And I configure Bugsnag for "UnhandledNativeErrorScenario"