Skip to content
Snippets Groups Projects
Commit e810e626 authored by Mike Greiling's avatar Mike Greiling :speech_balloon:
Browse files

Merge branch 'mrincon-sentry-double-integration' into 'master'

Install two versions of Sentry Client SDK

See merge request !102790



Merged-by: default avatarMike Greiling <mike@pixelcog.com>
Approved-by: default avatarDominic Couture <dcouture@gitlab.com>
Approved-by: Lukas Eipert's avatarLukas 'Eipi' Eipert <leipert@gitlab.com>
Approved-by: default avatarRichard Chong <rchong@gitlab.com>
Approved-by: Jon Jenkins's avatarJon Jenkins <jjenkins@gitlab.com>
Approved-by: default avatarSean McGivern <sean@gitlab.com>
Approved-by: default avatarMike Greiling <mike@pixelcog.com>
Co-authored-by: Miguel Rincon's avatarMiguel Rincon <mrincon@gitlab.com>
parents 35e780c4 654836ec
No related branches found
No related tags found
1 merge request!102790Install two versions of Sentry Client SDK
Pipeline #707088298 passed
Showing
with 615 additions and 191 deletions
......@@ -75,6 +75,8 @@ rules:
- sibling
- index
pathGroups:
- pattern: '@sentry/browser'
group: external
- pattern: ~/**
group: internal
- pattern: emojis/**
......
import { __ } from '~/locale';
// TODO: Remove in favor of https://gitlab.com/gitlab-org/gitlab/issues/35144
export const IGNORE_ERRORS = [
// Random plugins/extensions
'top.GLOBALS',
......
import '../webpack';
import * as Sentry from 'sentrybrowser7';
import SentryConfig from './sentry_config';
const index = function index() {
// Configuration for newer versions of Sentry SDK (v7)
SentryConfig.init({
dsn: gon.sentry_dsn,
environment: gon.sentry_environment,
currentUserId: gon.current_user_id,
whitelistUrls:
allowUrls:
process.env.NODE_ENV === 'production'
? [gon.gitlab_url]
: [gon.gitlab_url, 'webpack-internal://'],
environment: gon.sentry_environment,
release: gon.revision,
tags: {
revision: gon.revision,
feature_category: gon.feature_category,
},
});
return SentryConfig;
};
index();
// The _Sentry object is globally exported so it can be used by
// ./sentry_browser_wrapper.js
// This hack allows us to load a single version of `@sentry/browser`
// in the browser, see app/views/layouts/_head.html.haml to find how it is imported.
// eslint-disable-next-line no-underscore-dangle
window._Sentry = Sentry;
export default index;
import '../webpack';
import * as Sentry5 from 'sentrybrowser5';
import LegacySentryConfig from './legacy_sentry_config';
const index = function index() {
// Configuration for legacy versions of Sentry SDK (v5)
LegacySentryConfig.init({
dsn: gon.sentry_dsn,
currentUserId: gon.current_user_id,
whitelistUrls:
process.env.NODE_ENV === 'production'
? [gon.gitlab_url]
: [gon.gitlab_url, 'webpack-internal://'],
environment: gon.sentry_environment,
release: gon.revision,
tags: {
revision: gon.revision,
feature_category: gon.feature_category,
},
});
};
index();
// The _Sentry object is globally exported so it can be used by
// ./sentry_browser_wrapper.js
// This hack allows us to load a single version of `@sentry/browser`
// in the browser, see app/views/layouts/_head.html.haml to find how it is imported.
// eslint-disable-next-line no-underscore-dangle
window._Sentry = Sentry5;
export default index;
import * as Sentry5 from 'sentrybrowser5';
import $ from 'jquery';
import { __ } from '~/locale';
import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from './constants';
const SentryConfig = {
IGNORE_ERRORS,
BLACKLIST_URLS: DENY_URLS,
SAMPLE_RATE,
init(options = {}) {
this.options = options;
this.configure();
this.bindSentryErrors();
if (this.options.currentUserId) this.setUser();
},
configure() {
const { dsn, release, tags, whitelistUrls, environment } = this.options;
Sentry5.init({
dsn,
release,
whitelistUrls,
environment,
ignoreErrors: this.IGNORE_ERRORS, // TODO: Remove in favor of https://gitlab.com/gitlab-org/gitlab/issues/35144
blacklistUrls: this.BLACKLIST_URLS,
sampleRate: SAMPLE_RATE,
});
Sentry5.setTags(tags);
},
setUser() {
Sentry5.setUser({
id: this.options.currentUserId,
});
},
bindSentryErrors() {
$(document).on('ajaxError.sentry', this.handleSentryErrors);
},
handleSentryErrors(event, req, config, err) {
const error = err || req.statusText;
const { responseText = __('Unknown response text') } = req;
const { type, url, data } = config;
const { status } = req;
Sentry5.captureMessage(error, {
extra: {
type,
url,
data,
status,
response: responseText,
error,
event,
},
});
},
};
export default SentryConfig;
// The _Sentry object is globally exported so it can be used here
// This hack allows us to load a single version of `@sentry/browser`
// in the browser (or none). See app/views/layouts/_head.html.haml
// to find how it is imported.
// This module wraps methods used by our production code.
// Each export is names as we cannot export the entire namespace from *.
export const captureException = (...args) => {
// eslint-disable-next-line no-underscore-dangle
const Sentry = window._Sentry;
Sentry?.captureException(...args);
};
export const captureMessage = (...args) => {
// eslint-disable-next-line no-underscore-dangle
const Sentry = window._Sentry;
Sentry?.captureMessage(...args);
};
export const withScope = (...args) => {
// eslint-disable-next-line no-underscore-dangle
const Sentry = window._Sentry;
Sentry?.withScope(...args);
};
import * as Sentry from '@sentry/browser';
import $ from 'jquery';
import { __ } from '~/locale';
import * as Sentry from 'sentrybrowser7';
import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from './constants';
const SentryConfig = {
IGNORE_ERRORS,
BLACKLIST_URLS: DENY_URLS,
SAMPLE_RATE,
init(options = {}) {
this.options = options;
this.configure();
this.bindSentryErrors();
if (this.options.currentUserId) this.setUser();
},
configure() {
const { dsn, release, tags, whitelistUrls, environment } = this.options;
const { dsn, release, tags, allowUrls, environment } = this.options;
Sentry.init({
dsn,
release,
whitelistUrls,
allowUrls,
environment,
ignoreErrors: this.IGNORE_ERRORS, // TODO: Remove in favor of https://gitlab.com/gitlab-org/gitlab/issues/35144
blacklistUrls: this.BLACKLIST_URLS,
ignoreErrors: IGNORE_ERRORS,
denyUrls: DENY_URLS,
sampleRate: SAMPLE_RATE,
});
......@@ -36,29 +30,6 @@ const SentryConfig = {
id: this.options.currentUserId,
});
},
bindSentryErrors() {
$(document).on('ajaxError.sentry', this.handleSentryErrors);
},
handleSentryErrors(event, req, config, err) {
const error = err || req.statusText;
const { responseText = __('Unknown response text') } = req;
const { type, url, data } = config;
const { status } = req;
Sentry.captureMessage(error, {
extra: {
type,
url,
data,
status,
response: responseText,
error,
event,
},
});
},
};
export default SentryConfig;
......@@ -47,7 +47,10 @@
= Gon::Base.render_data(nonce: content_security_policy_nonce)
= render_if_exists 'layouts/header/translations'
= webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled
- if Feature.enabled?(:enable_new_sentry_clientside_integration, current_user) && Gitlab::CurrentSettings.sentry_enabled
= webpack_bundle_tag 'sentry'
- elsif Gitlab.config.sentry.enabled
= webpack_bundle_tag 'legacy_sentry'
= webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
= yield :page_specific_javascripts
......
......@@ -160,6 +160,7 @@ function generateEntries() {
*/
const manualEntries = {
default: defaultEntries,
legacy_sentry: './sentry/legacy_index.js',
sentry: './sentry/index.js',
performance_bar: './performance_bar/index.js',
jira_connect_app: './jira_connect/subscriptions/index.js',
......@@ -174,6 +175,11 @@ function generateEntries() {
const alias = {
// Map Apollo client to apollo/client/core to prevent react related imports from being loaded
'@apollo/client$': '@apollo/client/core',
// Map Sentry calls to use local wrapper
'@sentry/browser$': path.join(
ROOT_PATH,
'app/assets/javascripts/sentry/sentry_browser_wrapper.js',
),
'~': path.join(ROOT_PATH, 'app/assets/javascripts'),
emojis: path.join(ROOT_PATH, 'fixtures/emojis'),
empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'),
......
......@@ -63,6 +63,7 @@ module.exports = (path, options = {}) => {
'^jest/(.*)$': '<rootDir>/spec/frontend/$1',
'^ee_else_ce_jest/(.*)$': '<rootDir>/spec/frontend/$1',
'^jquery$': '<rootDir>/node_modules/jquery/dist/jquery.slim.js',
'^@sentry/browser$': '<rootDir>/app/assets/javascripts/sentry/sentry_browser_wrapper.js',
...extModuleNameMapper,
};
......
......@@ -43,7 +43,10 @@ def self.default_directives
allow_websocket_connections(directives)
allow_cdn(directives, Settings.gitlab.cdn_host) if Settings.gitlab.cdn_host.present?
allow_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
# Support for Sentry setup via configuration files will be removed in 16.0
# in favor of Gitlab::CurrentSettings.
allow_legacy_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn
allow_sentry(directives) if Gitlab::CurrentSettings.sentry_enabled && Gitlab::CurrentSettings.sentry_clientside_dsn
allow_framed_gitlab_paths(directives)
allow_customersdot(directives) if ENV['CUSTOMER_PORTAL_URL'].present?
allow_review_apps(directives) if ENV['REVIEW_APPS_ENABLED']
......@@ -135,13 +138,22 @@ def self.allow_customersdot(directives)
append_to_directive(directives, 'frame_src', customersdot_host)
end
def self.allow_sentry(directives)
def self.allow_legacy_sentry(directives)
# Support for Sentry setup via configuration files will be removed in 16.0
# in favor of Gitlab::CurrentSettings.
sentry_dsn = Gitlab.config.sentry.clientside_dsn
sentry_uri = URI(sentry_dsn)
append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
end
def self.allow_sentry(directives)
sentry_dsn = Gitlab::CurrentSettings.sentry_clientside_dsn
sentry_uri = URI(sentry_dsn)
append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}")
end
def self.allow_letter_opener(directives)
append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, '/rails/letter_opener/'))
end
......
......@@ -3,26 +3,61 @@
require 'spec_helper'
RSpec.describe 'Sentry' do
let(:sentry_regex_path) { '\/sentry.*\.chunk\.js' }
context 'when enable_new_sentry_clientside_integration is disabled' do
before do
stub_feature_flags(enable_new_sentry_clientside_integration: false)
end
it 'does not load sentry if sentry is disabled' do
allow(Gitlab.config.sentry).to receive(:enabled).and_return(false)
visit new_user_session_path
expect(has_requested_legacy_sentry).to eq(false)
end
it 'does not load sentry if sentry is disabled' do
allow(Gitlab.config.sentry).to receive(:enabled).and_return(false)
visit new_user_session_path
it 'loads legacy sentry if sentry config is enabled', :js do
allow(Gitlab.config.sentry).to receive(:enabled).and_return(true)
expect(has_requested_sentry).to eq(false)
visit new_user_session_path
expect(has_requested_legacy_sentry).to eq(true)
expect(evaluate_script('window._Sentry.SDK_VERSION')).to match(%r{^5\.})
end
end
it 'loads sentry if sentry is enabled' do
stub_sentry_settings
context 'when enable_new_sentry_clientside_integration is enabled' do
before do
stub_feature_flags(enable_new_sentry_clientside_integration: true)
end
it 'does not load sentry if sentry settings are disabled' do
allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_return(false)
visit new_user_session_path
visit new_user_session_path
expect(has_requested_sentry).to eq(true)
expect(has_requested_sentry).to eq(false)
end
it 'loads sentry if sentry settings are enabled', :js do
allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_return(true)
visit new_user_session_path
expect(has_requested_sentry).to eq(true)
expect(evaluate_script('window._Sentry.SDK_VERSION')).to match(%r{^7\.})
end
end
def has_requested_legacy_sentry
page.all('script', visible: false).one? do |elm|
elm[:src] =~ %r{/legacy_sentry.*\.chunk\.js\z}
end
end
def has_requested_sentry
page.all('script', visible: false).one? do |elm|
elm[:src] =~ /#{sentry_regex_path}$/
elm[:src] =~ %r{/sentry.*\.chunk\.js\z}
end
end
end
......@@ -61,6 +61,10 @@ describe('Clusters', () => {
let captureException;
beforeEach(() => {
jest.spyOn(Sentry, 'withScope').mockImplementation((fn) => {
const mockScope = { setTag: () => {} };
fn(mockScope);
});
captureException = jest.spyOn(Sentry, 'captureException');
mock = new MockAdapter(axios);
......
......@@ -17,6 +17,10 @@ describe('Clusters store actions', () => {
describe('reportSentryError', () => {
beforeEach(() => {
jest.spyOn(Sentry, 'withScope').mockImplementation((fn) => {
const mockScope = { setTag: () => {} };
fn(mockScope);
});
captureException = jest.spyOn(Sentry, 'captureException');
});
......
import index from '~/sentry/index';
import LegacySentryConfig from '~/sentry/legacy_sentry_config';
import SentryConfig from '~/sentry/sentry_config';
describe('SentryConfig options', () => {
describe('Sentry init', () => {
let originalGon;
const dsn = 'https://123@sentry.gitlab.test/123';
const currentUserId = 'currentUserId';
const gitlabUrl = 'gitlabUrl';
const environment = 'test';
const currentUserId = '1';
const gitlabUrl = 'gitlabUrl';
const revision = 'revision';
const featureCategory = 'my_feature_category';
let indexReturnValue;
beforeEach(() => {
originalGon = window.gon;
window.gon = {
sentry_dsn: dsn,
sentry_environment: environment,
......@@ -21,28 +24,41 @@ describe('SentryConfig options', () => {
feature_category: featureCategory,
};
process.env.HEAD_COMMIT_SHA = revision;
jest.spyOn(LegacySentryConfig, 'init').mockImplementation();
jest.spyOn(SentryConfig, 'init').mockImplementation();
});
indexReturnValue = index();
afterEach(() => {
window.gon = originalGon;
});
it('should init with .sentryDsn, .currentUserId, .whitelistUrls and environment', () => {
expect(SentryConfig.init).toHaveBeenCalledWith({
dsn,
currentUserId,
whitelistUrls: [gitlabUrl, 'webpack-internal://'],
environment,
release: revision,
tags: {
revision,
feature_category: featureCategory,
},
});
it('exports new version of Sentry in the global object', () => {
// eslint-disable-next-line no-underscore-dangle
expect(window._Sentry.SDK_VERSION).not.toMatch(/^5\./);
});
it('should return SentryConfig', () => {
expect(indexReturnValue).toBe(SentryConfig);
describe('when called', () => {
beforeEach(() => {
index();
});
it('configures sentry', () => {
expect(SentryConfig.init).toHaveBeenCalledTimes(1);
expect(SentryConfig.init).toHaveBeenCalledWith({
dsn,
currentUserId,
allowUrls: [gitlabUrl, 'webpack-internal://'],
environment,
release: revision,
tags: {
revision,
feature_category: featureCategory,
},
});
});
it('does not configure legacy sentry', () => {
expect(LegacySentryConfig.init).not.toHaveBeenCalled();
});
});
});
import index from '~/sentry/legacy_index';
import LegacySentryConfig from '~/sentry/legacy_sentry_config';
import SentryConfig from '~/sentry/sentry_config';
describe('Sentry init', () => {
let originalGon;
const dsn = 'https://123@sentry.gitlab.test/123';
const environment = 'test';
const currentUserId = '1';
const gitlabUrl = 'gitlabUrl';
const revision = 'revision';
const featureCategory = 'my_feature_category';
beforeEach(() => {
originalGon = window.gon;
window.gon = {
sentry_dsn: dsn,
sentry_environment: environment,
current_user_id: currentUserId,
gitlab_url: gitlabUrl,
revision,
feature_category: featureCategory,
};
jest.spyOn(LegacySentryConfig, 'init').mockImplementation();
jest.spyOn(SentryConfig, 'init').mockImplementation();
});
afterEach(() => {
window.gon = originalGon;
});
it('exports legacy version of Sentry in the global object', () => {
// eslint-disable-next-line no-underscore-dangle
expect(window._Sentry.SDK_VERSION).toMatch(/^5\./);
});
describe('when called', () => {
beforeEach(() => {
index();
});
it('configures legacy sentry', () => {
expect(LegacySentryConfig.init).toHaveBeenCalledTimes(1);
expect(LegacySentryConfig.init).toHaveBeenCalledWith({
dsn,
currentUserId,
whitelistUrls: [gitlabUrl, 'webpack-internal://'],
environment,
release: revision,
tags: {
revision,
feature_category: featureCategory,
},
});
});
it('does not configure new sentry', () => {
expect(SentryConfig.init).not.toHaveBeenCalled();
});
});
});
import * as Sentry5 from 'sentrybrowser5';
import LegacySentryConfig from '~/sentry/legacy_sentry_config';
describe('LegacySentryConfig', () => {
describe('IGNORE_ERRORS', () => {
it('should be an array of strings', () => {
const areStrings = LegacySentryConfig.IGNORE_ERRORS.every(
(error) => typeof error === 'string',
);
expect(areStrings).toBe(true);
});
});
describe('BLACKLIST_URLS', () => {
it('should be an array of regexps', () => {
const areRegExps = LegacySentryConfig.BLACKLIST_URLS.every((url) => url instanceof RegExp);
expect(areRegExps).toBe(true);
});
});
describe('SAMPLE_RATE', () => {
it('should be a finite number', () => {
expect(typeof LegacySentryConfig.SAMPLE_RATE).toEqual('number');
});
});
describe('init', () => {
const options = {
currentUserId: 1,
};
beforeEach(() => {
jest.spyOn(LegacySentryConfig, 'configure');
jest.spyOn(LegacySentryConfig, 'bindSentryErrors');
jest.spyOn(LegacySentryConfig, 'setUser');
LegacySentryConfig.init(options);
});
it('should set the options property', () => {
expect(LegacySentryConfig.options).toEqual(options);
});
it('should call the configure method', () => {
expect(LegacySentryConfig.configure).toHaveBeenCalled();
});
it('should call the error bindings method', () => {
expect(LegacySentryConfig.bindSentryErrors).toHaveBeenCalled();
});
it('should call setUser', () => {
expect(LegacySentryConfig.setUser).toHaveBeenCalled();
});
it('should not call setUser if there is no current user ID', () => {
LegacySentryConfig.setUser.mockClear();
options.currentUserId = undefined;
LegacySentryConfig.init(options);
expect(LegacySentryConfig.setUser).not.toHaveBeenCalled();
});
});
describe('configure', () => {
const sentryConfig = {};
const options = {
dsn: 'https://123@sentry.gitlab.test/123',
whitelistUrls: ['//gitlabUrl', 'webpack-internal://'],
environment: 'test',
release: 'revision',
tags: {
revision: 'revision',
feature_category: 'my_feature_category',
},
};
beforeEach(() => {
jest.spyOn(Sentry5, 'init').mockImplementation();
jest.spyOn(Sentry5, 'setTags').mockImplementation();
sentryConfig.options = options;
sentryConfig.IGNORE_ERRORS = 'ignore_errors';
sentryConfig.BLACKLIST_URLS = 'blacklist_urls';
LegacySentryConfig.configure.call(sentryConfig);
});
it('should call Sentry5.init', () => {
expect(Sentry5.init).toHaveBeenCalledWith({
dsn: options.dsn,
release: options.release,
sampleRate: 0.95,
whitelistUrls: options.whitelistUrls,
environment: 'test',
ignoreErrors: sentryConfig.IGNORE_ERRORS,
blacklistUrls: sentryConfig.BLACKLIST_URLS,
});
});
it('should call Sentry5.setTags', () => {
expect(Sentry5.setTags).toHaveBeenCalledWith(options.tags);
});
it('should set environment from options', () => {
sentryConfig.options.environment = 'development';
LegacySentryConfig.configure.call(sentryConfig);
expect(Sentry5.init).toHaveBeenCalledWith({
dsn: options.dsn,
release: options.release,
sampleRate: 0.95,
whitelistUrls: options.whitelistUrls,
environment: 'development',
ignoreErrors: sentryConfig.IGNORE_ERRORS,
blacklistUrls: sentryConfig.BLACKLIST_URLS,
});
});
});
describe('setUser', () => {
let sentryConfig;
beforeEach(() => {
sentryConfig = { options: { currentUserId: 1 } };
jest.spyOn(Sentry5, 'setUser');
LegacySentryConfig.setUser.call(sentryConfig);
});
it('should call .setUser', () => {
expect(Sentry5.setUser).toHaveBeenCalledWith({
id: sentryConfig.options.currentUserId,
});
});
});
describe('handleSentryErrors', () => {
let event;
let req;
let config;
let err;
beforeEach(() => {
event = {};
req = { status: 'status', responseText: 'Unknown response text', statusText: 'statusText' };
config = { type: 'type', url: 'url', data: 'data' };
err = {};
jest.spyOn(Sentry5, 'captureMessage');
LegacySentryConfig.handleSentryErrors(event, req, config, err);
});
it('should call Sentry5.captureMessage', () => {
expect(Sentry5.captureMessage).toHaveBeenCalledWith(err, {
extra: {
type: config.type,
url: config.url,
data: config.data,
status: req.status,
response: req.responseText,
error: err,
event,
},
});
});
describe('if no err is provided', () => {
beforeEach(() => {
LegacySentryConfig.handleSentryErrors(event, req, config);
});
it('should use req.statusText as the error value', () => {
expect(Sentry5.captureMessage).toHaveBeenCalledWith(req.statusText, {
extra: {
type: config.type,
url: config.url,
data: config.data,
status: req.status,
response: req.responseText,
error: req.statusText,
event,
},
});
});
});
describe('if no req.responseText is provided', () => {
beforeEach(() => {
req.responseText = undefined;
LegacySentryConfig.handleSentryErrors(event, req, config, err);
});
it('should use `Unknown response text` as the response', () => {
expect(Sentry5.captureMessage).toHaveBeenCalledWith(err, {
extra: {
type: config.type,
url: config.url,
data: config.data,
status: req.status,
response: 'Unknown response text',
error: err,
event,
},
});
});
});
});
});
import * as Sentry from '~/sentry/sentry_browser_wrapper';
const mockError = new Error('error!');
const mockMsg = 'msg!';
const mockFn = () => {};
describe('SentryBrowserWrapper', () => {
afterEach(() => {
// eslint-disable-next-line no-underscore-dangle
delete window._Sentry;
});
describe('when _Sentry is not defined', () => {
it('methods fail silently', () => {
expect(() => {
Sentry.captureException(mockError);
Sentry.captureMessage(mockMsg);
Sentry.withScope(mockFn);
}).not.toThrow();
});
});
describe('when _Sentry is defined', () => {
let mockCaptureException;
let mockCaptureMessage;
let mockWithScope;
beforeEach(async () => {
mockCaptureException = jest.fn();
mockCaptureMessage = jest.fn();
mockWithScope = jest.fn();
// eslint-disable-next-line no-underscore-dangle
window._Sentry = {
captureException: mockCaptureException,
captureMessage: mockCaptureMessage,
withScope: mockWithScope,
};
});
it('captureException is called', () => {
Sentry.captureException(mockError);
expect(mockCaptureException).toHaveBeenCalledWith(mockError);
});
it('captureMessage is called', () => {
Sentry.captureMessage(mockMsg);
expect(mockCaptureMessage).toHaveBeenCalledWith(mockMsg);
});
it('withScope is called', () => {
Sentry.withScope(mockFn);
expect(mockWithScope).toHaveBeenCalledWith(mockFn);
});
});
});
import * as Sentry from '@sentry/browser';
import * as Sentry from 'sentrybrowser7';
import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from '~/sentry/constants';
import SentryConfig from '~/sentry/sentry_config';
describe('SentryConfig', () => {
describe('IGNORE_ERRORS', () => {
it('should be an array of strings', () => {
const areStrings = SentryConfig.IGNORE_ERRORS.every((error) => typeof error === 'string');
expect(areStrings).toBe(true);
});
});
describe('BLACKLIST_URLS', () => {
it('should be an array of regexps', () => {
const areRegExps = SentryConfig.BLACKLIST_URLS.every((url) => url instanceof RegExp);
expect(areRegExps).toBe(true);
});
});
describe('SAMPLE_RATE', () => {
it('should be a finite number', () => {
expect(typeof SentryConfig.SAMPLE_RATE).toEqual('number');
});
});
describe('init', () => {
const options = {
currentUserId: 1,
......@@ -31,7 +11,6 @@ describe('SentryConfig', () => {
beforeEach(() => {
jest.spyOn(SentryConfig, 'configure');
jest.spyOn(SentryConfig, 'bindSentryErrors');
jest.spyOn(SentryConfig, 'setUser');
SentryConfig.init(options);
......@@ -45,19 +24,13 @@ describe('SentryConfig', () => {
expect(SentryConfig.configure).toHaveBeenCalled();
});
it('should call the error bindings method', () => {
expect(SentryConfig.bindSentryErrors).toHaveBeenCalled();
});
it('should call setUser', () => {
expect(SentryConfig.setUser).toHaveBeenCalled();
});
it('should not call setUser if there is no current user ID', () => {
SentryConfig.setUser.mockClear();
options.currentUserId = undefined;
SentryConfig.init(options);
SentryConfig.init({ currentUserId: undefined });
expect(SentryConfig.setUser).not.toHaveBeenCalled();
});
......@@ -67,7 +40,7 @@ describe('SentryConfig', () => {
const sentryConfig = {};
const options = {
dsn: 'https://123@sentry.gitlab.test/123',
whitelistUrls: ['//gitlabUrl', 'webpack-internal://'],
allowUrls: ['//gitlabUrl', 'webpack-internal://'],
environment: 'test',
release: 'revision',
tags: {
......@@ -81,8 +54,6 @@ describe('SentryConfig', () => {
jest.spyOn(Sentry, 'setTags').mockImplementation();
sentryConfig.options = options;
sentryConfig.IGNORE_ERRORS = 'ignore_errors';
sentryConfig.BLACKLIST_URLS = 'blacklist_urls';
SentryConfig.configure.call(sentryConfig);
});
......@@ -91,11 +62,11 @@ describe('SentryConfig', () => {
expect(Sentry.init).toHaveBeenCalledWith({
dsn: options.dsn,
release: options.release,
sampleRate: 0.95,
whitelistUrls: options.whitelistUrls,
environment: 'test',
ignoreErrors: sentryConfig.IGNORE_ERRORS,
blacklistUrls: sentryConfig.BLACKLIST_URLS,
sampleRate: SAMPLE_RATE,
allowUrls: options.allowUrls,
environment: options.environment,
ignoreErrors: IGNORE_ERRORS,
denyUrls: DENY_URLS,
});
});
......@@ -111,11 +82,11 @@ describe('SentryConfig', () => {
expect(Sentry.init).toHaveBeenCalledWith({
dsn: options.dsn,
release: options.release,
sampleRate: 0.95,
whitelistUrls: options.whitelistUrls,
sampleRate: SAMPLE_RATE,
allowUrls: options.allowUrls,
environment: 'development',
ignoreErrors: sentryConfig.IGNORE_ERRORS,
blacklistUrls: sentryConfig.BLACKLIST_URLS,
ignoreErrors: IGNORE_ERRORS,
denyUrls: DENY_URLS,
});
});
});
......@@ -136,78 +107,4 @@ describe('SentryConfig', () => {
});
});
});
describe('handleSentryErrors', () => {
let event;
let req;
let config;
let err;
beforeEach(() => {
event = {};
req = { status: 'status', responseText: 'Unknown response text', statusText: 'statusText' };
config = { type: 'type', url: 'url', data: 'data' };
err = {};
jest.spyOn(Sentry, 'captureMessage');
SentryConfig.handleSentryErrors(event, req, config, err);
});
it('should call Sentry.captureMessage', () => {
expect(Sentry.captureMessage).toHaveBeenCalledWith(err, {
extra: {
type: config.type,
url: config.url,
data: config.data,
status: req.status,
response: req.responseText,
error: err,
event,
},
});
});
describe('if no err is provided', () => {
beforeEach(() => {
SentryConfig.handleSentryErrors(event, req, config);
});
it('should use req.statusText as the error value', () => {
expect(Sentry.captureMessage).toHaveBeenCalledWith(req.statusText, {
extra: {
type: config.type,
url: config.url,
data: config.data,
status: req.status,
response: req.responseText,
error: req.statusText,
event,
},
});
});
});
describe('if no req.responseText is provided', () => {
beforeEach(() => {
req.responseText = undefined;
SentryConfig.handleSentryErrors(event, req, config, err);
});
it('should use `Unknown response text` as the response', () => {
expect(Sentry.captureMessage).toHaveBeenCalledWith(err, {
extra: {
type: config.type,
url: config.url,
data: config.data,
status: req.status,
response: 'Unknown response text',
error: err,
event,
},
});
});
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment