123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- import {OrganizationFixture} from 'sentry-fixture/organization';
- import {isSimilarOrigin, Request, resolveHostname} from 'sentry/api';
- import {PROJECT_MOVED} from 'sentry/constants/apiErrorCodes';
- import ConfigStore from './stores/configStore';
- import OrganizationStore from './stores/organizationStore';
- jest.unmock('sentry/api');
- describe('api', function () {
- let api;
- beforeEach(function () {
- api = new MockApiClient();
- });
- describe('Client', function () {
- describe('cancel()', function () {
- it('should abort any open XHR requests', function () {
- const abort1 = jest.fn();
- const abort2 = jest.fn();
- const req1 = new Request(new Promise(() => null), {
- abort: abort1,
- } as any);
- const req2 = new Request(new Promise(() => null), {abort: abort2} as any);
- api.activeRequests = {
- 1: req1,
- 2: req2,
- };
- api.clear();
- expect(req1.aborter?.abort).toHaveBeenCalledTimes(1);
- expect(req2.aborter?.abort).toHaveBeenCalledTimes(1);
- });
- });
- });
- it('does not call success callback if 302 was returned because of a project slug change', function () {
- const successCb = jest.fn();
- api.activeRequests = {id: {alive: true}};
- api.wrapCallback(
- 'id',
- successCb
- )({
- responseJSON: {
- detail: {
- code: PROJECT_MOVED,
- message: '...',
- extra: {
- slug: 'new-slug',
- },
- },
- },
- });
- expect(successCb).not.toHaveBeenCalled();
- });
- it('handles error callback', function () {
- jest.spyOn(api, 'wrapCallback').mockImplementation((_id, func) => func);
- const errorCb = jest.fn();
- const args = ['test', true, 1];
- api.handleRequestError(
- {
- id: 'test',
- path: 'test',
- requestOptions: {error: errorCb},
- },
- ...args
- );
- expect(errorCb).toHaveBeenCalledWith(...args);
- });
- it('handles undefined error callback', function () {
- expect(() =>
- api.handleRequestError(
- {
- id: 'test',
- path: 'test',
- requestOptions: {},
- },
- {},
- {}
- )
- ).not.toThrow();
- });
- });
- describe('resolveHostname', function () {
- let devUi, location, orgstate, configstate;
- const controlPath = '/api/0/broadcasts/';
- const regionPath = '/api/0/organizations/slug/issues/';
- beforeEach(function () {
- orgstate = OrganizationStore.get();
- configstate = ConfigStore.getState();
- location = window.location;
- devUi = window.__SENTRY_DEV_UI;
- OrganizationStore.onUpdate(OrganizationFixture({features: ['frontend-domainsplit']}));
- ConfigStore.loadInitialData({
- ...configstate,
- links: {
- organizationUrl: 'https://acme.sentry.io',
- sentryUrl: 'https://sentry.io',
- regionUrl: 'https://us.sentry.io',
- },
- });
- });
- afterEach(() => {
- window.location = location;
- window.__SENTRY_DEV_UI = devUi;
- OrganizationStore.onUpdate(orgstate.organization);
- ConfigStore.loadInitialData(configstate);
- });
- it('does nothing without feature', function () {
- // Org does not have the required feature.
- OrganizationStore.onUpdate(OrganizationFixture());
- let result = resolveHostname(controlPath);
- expect(result).toBe(controlPath);
- // Explicit domains still work.
- result = resolveHostname(controlPath, 'https://sentry.io');
- expect(result).toBe(`https://sentry.io${controlPath}`);
- result = resolveHostname(regionPath, 'https://de.sentry.io');
- expect(result).toBe(`https://de.sentry.io${regionPath}`);
- });
- it('does not override region in _admin', function () {
- Object.defineProperty(window, 'location', {
- configurable: true,
- enumerable: true,
- value: new URL('https://sentry.io/_admin/'),
- });
- // Adds domain to control paths
- let result = resolveHostname(controlPath);
- expect(result).toBe('https://sentry.io/api/0/broadcasts/');
- // Doesn't add domain to region paths
- result = resolveHostname(regionPath);
- expect(result).toBe(regionPath);
- // Explicit domains still work.
- result = resolveHostname(controlPath, 'https://sentry.io');
- expect(result).toBe(`https://sentry.io${controlPath}`);
- result = resolveHostname(regionPath, 'https://de.sentry.io');
- expect(result).toBe(`https://de.sentry.io${regionPath}`);
- });
- it('adds domains when feature enabled', function () {
- let result = resolveHostname(regionPath);
- expect(result).toBe('https://us.sentry.io/api/0/organizations/slug/issues/');
- result = resolveHostname(controlPath);
- expect(result).toBe('https://sentry.io/api/0/broadcasts/');
- });
- it('matches if querystrings are in path', function () {
- const result = resolveHostname(
- '/api/0/organizations/acme/sentry-app-components/?projectId=123'
- );
- expect(result).toBe(
- 'https://sentry.io/api/0/organizations/acme/sentry-app-components/?projectId=123'
- );
- });
- it('uses paths for region silo in dev-ui', function () {
- window.__SENTRY_DEV_UI = true;
- let result = resolveHostname(regionPath);
- expect(result).toBe('/region/us/api/0/organizations/slug/issues/');
- result = resolveHostname(controlPath);
- expect(result).toBe('/api/0/broadcasts/');
- });
- it('removes sentryUrl from dev-ui mode requests', function () {
- window.__SENTRY_DEV_UI = true;
- let result = resolveHostname(regionPath, 'https://sentry.io');
- expect(result).toBe('/api/0/organizations/slug/issues/');
- result = resolveHostname(controlPath, 'https://sentry.io');
- expect(result).toBe('/api/0/broadcasts/');
- });
- it('removes sentryUrl from dev-ui mode requests when feature is off', function () {
- window.__SENTRY_DEV_UI = true;
- // Org does not have the required feature.
- OrganizationStore.onUpdate(OrganizationFixture());
- let result = resolveHostname(controlPath);
- expect(result).toBe(controlPath);
- // control silo shaped URLs don't get a host
- result = resolveHostname(controlPath, 'https://sentry.io');
- expect(result).toBe(controlPath);
- result = resolveHostname(regionPath, 'https://de.sentry.io');
- expect(result).toBe(`/region/de${regionPath}`);
- });
- it('preserves host parameters', function () {
- const result = resolveHostname(regionPath, 'https://de.sentry.io');
- expect(result).toBe('https://de.sentry.io/api/0/organizations/slug/issues/');
- });
- });
- describe('isSimilarOrigin', function () {
- test.each([
- // Same domain
- ['https://sentry.io', 'https://sentry.io', true],
- ['https://example.io', 'https://example.io', true],
- // Not the same
- ['https://example.io', 'https://sentry.io', false],
- ['https://sentry.io', 'https://io.sentry', false],
- // Sibling domains
- ['https://us.sentry.io', 'https://sentry.sentry.io', true],
- ['https://us.sentry.io', 'https://acme.sentry.io', true],
- ['https://us.sentry.io', 'https://eu.sentry.io', true],
- ['https://woof.sentry.io', 'https://woof-org.sentry.io', true],
- ['https://woof.sentry.io/issues/1234/', 'https://woof-org.sentry.io', true],
- // Subdomain
- ['https://sentry.io/api/0/broadcasts/', 'https://woof.sentry.io', true],
- ['https://sentry.io/api/0/users/', 'https://sentry.sentry.io', true],
- ['https://sentry.io/api/0/users/', 'https://io.sentry.io', true],
- // request to subdomain from parent
- ['https://us.sentry.io/api/0/users/', 'https://sentry.io', true],
- // Not siblings
- ['https://sentry.io/api/0/broadcasts/', 'https://sentry.example.io', false],
- ['https://acme.sentry.io', 'https://acme.sent.ryio', false],
- ['https://woof.example.io', 'https://woof.sentry.io', false],
- ['https://woof.sentry.io', 'https://sentry.woof.io', false],
- ])('allows sibling domains %s and %s is %s', (target, origin, expected) => {
- expect(isSimilarOrigin(target, origin)).toBe(expected);
- });
- });
|