mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
253 lines
7.8 KiB
TypeScript
253 lines
7.8 KiB
TypeScript
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);
|
|
});
|
|
});
|