import {mountWithTheme} from 'sentry-test/enzyme'; import {selectByValue} from 'sentry-test/select-new'; import {Client} from 'sentry/api'; import JsonForm from 'sentry/components/forms/jsonForm'; import PermissionsObserver from 'sentry/views/settings/organizationDeveloperSettings/permissionsObserver'; import SentryApplicationDetails from 'sentry/views/settings/organizationDeveloperSettings/sentryApplicationDetails'; describe('Sentry Application Details', function () { let org; let orgId; let sentryApp; let token; let wrapper; let createAppRequest; let editAppRequest; const verifyInstallToggle = 'Switch[name="verifyInstall"]'; const redirectUrlInput = 'Input[name="redirectUrl"]'; const maskedValue = '*'.repeat(64); beforeEach(() => { Client.clearMockResponses(); org = TestStubs.Organization({features: ['sentry-app-logo-upload']}); orgId = org.slug; }); describe('Creating a new public Sentry App', () => { beforeEach(() => { createAppRequest = Client.addMockResponse({ url: '/sentry-apps/', method: 'POST', body: [], }); wrapper = mountWithTheme( , TestStubs.routerContext([{organization: org}]) ); }); it('has inputs for redirectUrl and verifyInstall', () => { expect(wrapper.exists(verifyInstallToggle)).toBeTruthy(); expect(wrapper.exists(redirectUrlInput)).toBeTruthy(); }); it('shows empty scopes and no credentials', function () { // new app starts off with no scopes selected expect(wrapper.find('PermissionsObserver').prop('scopes')).toEqual([]); expect( wrapper.find('PanelHeader').findWhere(h => h.text() === 'Permissions') ).toBeDefined(); }); it('does not show logo upload fields', function () { expect(wrapper.find('PanelHeader').at(1).text()).not.toContain('Logo'); expect(wrapper.find('PanelHeader').at(2).text()).not.toContain('Small Icon'); expect(wrapper.exists('AvatarChooser')).toBe(false); }); it('saves', function () { wrapper .find('Input[name="name"]') .simulate('change', {target: {value: 'Test App'}}); wrapper .find('Input[name="author"]') .simulate('change', {target: {value: 'Sentry'}}); wrapper .find('Input[name="webhookUrl"]') .simulate('change', {target: {value: 'https://webhook.com'}}); wrapper .find(redirectUrlInput) .simulate('change', {target: {value: 'https://webhook.com/setup'}}); wrapper.find('TextArea[name="schema"]').simulate('change', {target: {value: '{}'}}); wrapper.find('Switch[name="isAlertable"]').simulate('click'); selectByValue(wrapper, 'admin', {name: 'Member--permission'}); selectByValue(wrapper, 'admin', {name: 'Event--permission'}); wrapper .find('Checkbox') .first() .simulate('change', {target: {checked: true}}); wrapper.find('form').simulate('submit'); const data = { name: 'Test App', author: 'Sentry', organization: org.slug, redirectUrl: 'https://webhook.com/setup', webhookUrl: 'https://webhook.com', scopes: expect.arrayContaining([ 'member:read', 'member:admin', 'event:read', 'event:admin', ]), events: ['issue'], isInternal: false, verifyInstall: true, isAlertable: true, allowedOrigins: [], schema: {}, }; expect(createAppRequest).toHaveBeenCalledWith( '/sentry-apps/', expect.objectContaining({ data, method: 'POST', }) ); }); }); describe('Creating a new internal Sentry App', () => { beforeEach(() => { wrapper = mountWithTheme( , TestStubs.routerContext([{organization: org}]) ); }); it('does not show logo upload fields', function () { expect(wrapper.find('PanelHeader').at(1).text()).not.toContain('Logo'); expect(wrapper.find('PanelHeader').at(2).text()).not.toContain('Small Icon'); expect(wrapper.exists('AvatarChooser')).toBe(false); }); it('no inputs for redirectUrl and verifyInstall', () => { expect(wrapper.exists(verifyInstallToggle)).toBeFalsy(); expect(wrapper.exists(redirectUrlInput)).toBeFalsy(); }); }); describe('Renders public app', function () { beforeEach(() => { sentryApp = TestStubs.SentryApp(); sentryApp.events = ['issue']; Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/`, body: sentryApp, }); Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/api-tokens/`, body: [], }); wrapper = mountWithTheme( , TestStubs.routerContext([{organization: org}]) ); }); it('shows logo upload fields', function () { expect(wrapper.find('PanelHeader').at(1).text()).toContain('Logo'); expect(wrapper.find('PanelHeader').at(2).text()).toContain('Small Icon'); expect(wrapper.find('AvatarChooser')).toHaveLength(2); }); it('has inputs for redirectUrl and verifyInstall', () => { expect(wrapper.exists(verifyInstallToggle)).toBeTruthy(); expect(wrapper.exists(redirectUrlInput)).toBeTruthy(); }); it('shows application data', function () { // data should be filled out expect(wrapper.find('PermissionsObserver').prop('scopes')).toEqual([ 'project:read', ]); }); it('renders clientId and clientSecret for public apps', function () { expect(wrapper.find('#clientId').exists()).toBe(true); expect(wrapper.find('#clientSecret').exists()).toBe(true); }); }); describe('Renders for internal apps', () => { beforeEach(() => { sentryApp = TestStubs.SentryApp({ status: 'internal', }); token = TestStubs.SentryAppToken(); sentryApp.events = ['issue']; Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/`, body: sentryApp, }); Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/api-tokens/`, body: [token], }); wrapper = mountWithTheme( , TestStubs.routerContext([{organization: org}]) ); }); it('no inputs for redirectUrl and verifyInstall', () => { expect(wrapper.exists(verifyInstallToggle)).toBeFalsy(); expect(wrapper.exists(redirectUrlInput)).toBeFalsy(); }); it('shows logo upload fields', function () { expect(wrapper.find('PanelHeader').at(1).text()).toContain('Logo'); expect(wrapper.find('PanelHeader').at(2).text()).toContain('Small Icon'); expect(wrapper.find('AvatarChooser')).toHaveLength(2); }); it('shows tokens', function () { expect(wrapper.find('PanelHeader').at(5).text()).toContain('Tokens'); expect(wrapper.find('TokenItem').exists()).toBe(true); }); it('shows just clientSecret', function () { expect(wrapper.find('#clientSecret').exists()).toBe(true); expect(wrapper.find('#clientId').exists()).toBe(false); }); }); describe('Renders masked values', () => { beforeEach(() => { sentryApp = TestStubs.SentryApp({ status: 'internal', clientSecret: maskedValue, }); token = TestStubs.SentryAppToken({token: maskedValue, refreshToken: maskedValue}); sentryApp.events = ['issue']; Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/`, body: sentryApp, }); Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/api-tokens/`, body: [token], }); wrapper = mountWithTheme( , TestStubs.routerContext([{organization: org}]) ); }); it('shows masked tokens', function () { expect(wrapper.find('TextCopyInput input').first().prop('value')).toBe(maskedValue); }); it('shows masked clientSecret', function () { expect(wrapper.find('#clientSecret input').prop('value')).toBe(maskedValue); }); }); describe('Editing internal app tokens', () => { beforeEach(() => { sentryApp = TestStubs.SentryApp({ status: 'internal', isAlertable: true, }); token = TestStubs.SentryAppToken(); sentryApp.events = ['issue']; Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/`, body: sentryApp, }); Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/api-tokens/`, body: [token], }); wrapper = mountWithTheme( , TestStubs.routerContext([{organization: org}]) ); }); it('adding token to list', async function () { Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/api-tokens/`, method: 'POST', body: [ TestStubs.SentryAppToken({ token: '392847329', dateCreated: '2018-03-02T18:30:26Z', }), ], }); wrapper.find('Button[data-test-id="token-add"]').simulate('click'); await tick(); wrapper.update(); const tokenItems = wrapper.find('TokenItem'); expect(tokenItems).toHaveLength(2); }); it('removing token from list', async function () { Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/api-tokens/${token.token}/`, method: 'DELETE', body: {}, }); wrapper.find('Button[data-test-id="token-delete"]').simulate('click'); await tick(); wrapper.update(); expect(wrapper.find('EmptyMessage').exists()).toBe(true); }); it('removing webhookURL unsets isAlertable and changes webhookDisabled to true', () => { expect(wrapper.find(PermissionsObserver).prop('webhookDisabled')).toBe(false); expect(wrapper.find('Switch[name="isAlertable"]').prop('isActive')).toBe(true); wrapper.find('Input[name="webhookUrl"]').simulate('change', {target: {value: ''}}); expect(wrapper.find('Switch[name="isAlertable"]').prop('isActive')).toBe(false); expect(wrapper.find(PermissionsObserver).prop('webhookDisabled')).toBe(true); expect(wrapper.find(JsonForm).prop('additionalFieldProps')).toEqual({ webhookDisabled: true, }); }); }); describe('Editing an existing public Sentry App', () => { beforeEach(() => { sentryApp = TestStubs.SentryApp(); sentryApp.events = ['issue']; editAppRequest = Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/`, method: 'PUT', body: [], }); Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/`, body: sentryApp, }); Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/api-tokens/`, body: [], }); wrapper = mountWithTheme( , TestStubs.routerContext([{organization: org}]) ); }); it('updates app with correct data', function () { wrapper .find(redirectUrlInput) .simulate('change', {target: {value: 'https://hello.com/'}}); wrapper.find('TextArea[name="schema"]').simulate('change', {target: {value: '{}'}}); wrapper .find('Checkbox') .first() .simulate('change', {target: {checked: false}}); wrapper.find('form').simulate('submit'); expect(editAppRequest).toHaveBeenCalledWith( `/sentry-apps/${sentryApp.slug}/`, expect.objectContaining({ data: expect.objectContaining({ redirectUrl: 'https://hello.com/', events: [], }), method: 'PUT', }) ); }); it('submits with no-access for event subscription when permission is revoked', () => { wrapper .find('Checkbox') .first() .simulate('change', {target: {checked: true}}); wrapper.find('TextArea[name="schema"]').simulate('change', {target: {value: '{}'}}); selectByValue(wrapper, 'no-access', {name: 'Event--permission'}); wrapper.find('form').simulate('submit'); expect(editAppRequest).toHaveBeenCalledWith( `/sentry-apps/${sentryApp.slug}/`, expect.objectContaining({ data: expect.objectContaining({ events: [], }), method: 'PUT', }) ); }); }); describe('Editing an existing public Sentry App with a scope error', () => { beforeEach(() => { sentryApp = TestStubs.SentryApp(); editAppRequest = Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/`, method: 'PUT', statusCode: 400, body: { scopes: [ "Requested permission of member:write exceeds requester's permission. Please contact an administrator to make the requested change.", "Requested permission of member:admin exceeds requester's permission. Please contact an administrator to make the requested change.", ], }, }); Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/`, body: sentryApp, }); Client.addMockResponse({ url: `/sentry-apps/${sentryApp.slug}/api-tokens/`, body: [], }); wrapper = mountWithTheme( , TestStubs.routerContext([{organization: org}]) ); }); it('renders the error', async () => { wrapper.find('form').simulate('submit'); await tick(); wrapper.update(); expect(wrapper.find('div FieldErrorReason').text()).toEqual( "Requested permission of member:admin exceeds requester's permission. Please contact an administrator to make the requested change." ); }); }); });