import {ActorFixture} from 'sentry-fixture/actor';
import {MemberFixture} from 'sentry-fixture/member';
import {OrganizationFixture} from 'sentry-fixture/organization';
import {ProjectFixture} from 'sentry-fixture/project';
import {TeamFixture} from 'sentry-fixture/team';
import {UptimeRuleFixture} from 'sentry-fixture/uptimeRule';
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
import selectEvent from 'sentry-test/selectEvent';
import OrganizationStore from 'sentry/stores/organizationStore';
import ProjectsStore from 'sentry/stores/projectsStore';
import {UptimeAlertForm} from 'sentry/views/alerts/rules/uptime/uptimeAlertForm';
describe('Uptime Alert Form', function () {
const organization = OrganizationFixture();
const project = ProjectFixture({environments: ['prod', 'dev']});
beforeEach(function () {
OrganizationStore.onUpdate(organization);
ProjectsStore.loadInitialData([project]);
MockApiClient.addMockResponse({
url: '/organizations/org-slug/members/',
body: [MemberFixture()],
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/teams/',
body: [TeamFixture()],
});
});
function input(name: string) {
return screen.getByRole('textbox', {name});
}
it('can create a new rule', async function () {
render(, {
organization,
});
await screen.findByText('Configure Request');
await selectEvent.select(input('Environment'), 'prod');
await userEvent.clear(input('URL'));
await userEvent.type(input('URL'), 'http://example.com');
await selectEvent.clearAll(input('Method'));
await selectEvent.select(input('Method'), 'POST');
await userEvent.clear(input('Body'));
await userEvent.type(input('Body'), '{{"key": "value"}');
await userEvent.type(input('Name of header 1'), 'X-Something');
await userEvent.type(input('Value of X-Something'), 'Header Value');
const name = input('Uptime rule name');
await userEvent.clear(name);
await userEvent.type(name, 'New Uptime Rule');
await selectEvent.select(input('Owner'), 'Foo Bar');
const updateMock = MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/${project.slug}/uptime/`,
method: 'POST',
});
await userEvent.click(screen.getByRole('button', {name: 'Create Rule'}));
expect(updateMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
data: expect.objectContaining({
environment: 'prod',
name: 'New Uptime Rule',
owner: 'user:1',
url: 'http://example.com',
method: 'POST',
headers: [['X-Something', 'Header Value']],
body: '{"key": "value"}',
intervalSeconds: 60,
}),
})
);
});
it('renders existing rule', async function () {
const rule = UptimeRuleFixture({
name: 'Existing Rule',
environment: 'prod',
projectSlug: project.slug,
url: 'https://existing-url.com',
method: 'POST',
headers: [
['X-Test1', 'value 1'],
['X-Test2', 'value 2'],
],
body: '{"key": "value"}',
owner: ActorFixture(),
});
render(
,
{organization}
);
await screen.findByText('Configure Request');
expect(input('Uptime rule name')).toHaveValue('Existing Rule');
expect(input('URL')).toHaveValue('https://existing-url.com');
expect(input('Body')).toHaveValue('{"key": "value"}');
expect(input('Name of header 1')).toHaveValue('X-Test1');
expect(input('Name of header 2')).toHaveValue('X-Test2');
expect(input('Value of X-Test1')).toHaveValue('value 1');
expect(input('Value of X-Test2')).toHaveValue('value 2');
await selectEvent.openMenu(input('Method'));
expect(screen.getByRole('menuitemradio', {name: 'POST'})).toBeChecked();
await selectEvent.openMenu(input('Environment'));
expect(screen.getByRole('menuitemradio', {name: 'prod'})).toBeChecked();
});
it('handles simple edits', async function () {
// XXX(epurkhiser): This test covers the case where the formModel waws not
// triggering the observer that updates the apiEndpoint url based on the
// selected project for existing rules. The other tests all pass as the
// triggered error state from clearing the fields causes the observer to be
// called for the first time and correctly set the apiEndpoint.
const rule = UptimeRuleFixture({
name: 'Existing Rule',
projectSlug: project.slug,
url: 'https://existing-url.com',
owner: ActorFixture(),
});
render(
,
{organization}
);
await screen.findByText('Configure Request');
await userEvent.type(input('URL'), '/test');
const updateMock = MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/${project.slug}/uptime/${rule.id}/`,
method: 'PUT',
});
await userEvent.click(screen.getByRole('button', {name: 'Save Rule'}));
expect(updateMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
data: expect.objectContaining({
url: 'https://existing-url.com/test',
}),
})
);
});
it('can edit an existing rule', async function () {
OrganizationStore.onUpdate(organization);
const rule = UptimeRuleFixture({
name: 'Existing Rule',
projectSlug: project.slug,
url: 'https://existing-url.com',
owner: ActorFixture(),
});
render(
,
{organization}
);
await screen.findByText('Configure Request');
await selectEvent.select(input('Interval'), 'Every 10 minutes');
await selectEvent.select(input('Environment'), 'dev');
await userEvent.clear(input('URL'));
await userEvent.type(input('URL'), 'http://another-url.com');
await selectEvent.clearAll(input('Method'));
await selectEvent.select(input('Method'), 'POST');
await userEvent.clear(input('Body'));
await userEvent.type(input('Body'), '{{"different": "value"}');
await userEvent.type(input('Name of header 1'), 'X-Something');
await userEvent.type(input('Value of X-Something'), 'Header Value');
await userEvent.click(screen.getByRole('button', {name: 'Add Header'}));
await userEvent.type(input('Name of header 2'), 'X-Another');
await userEvent.type(input('Value of X-Another'), 'Second Value');
const name = input('Uptime rule name');
await userEvent.clear(name);
await userEvent.type(name, 'Updated name');
await selectEvent.select(input('Owner'), 'Foo Bar');
const updateMock = MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/${project.slug}/uptime/${rule.id}/`,
method: 'PUT',
});
await userEvent.click(screen.getByRole('button', {name: 'Save Rule'}));
expect(updateMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
data: expect.objectContaining({
name: 'Updated name',
environment: 'dev',
owner: 'user:1',
url: 'http://another-url.com',
method: 'POST',
headers: [
['X-Something', 'Header Value'],
['X-Another', 'Second Value'],
],
body: '{"different": "value"}',
intervalSeconds: 60 * 10,
}),
})
);
});
it('does not show body for GET and HEAD', async function () {
OrganizationStore.onUpdate(organization);
const rule = UptimeRuleFixture({
projectSlug: project.slug,
owner: ActorFixture(),
});
render(
,
{organization}
);
await screen.findByText('Configure Request');
// GET
await selectEvent.clearAll(input('Method'));
await selectEvent.select(input('Method'), 'GET');
expect(screen.queryByRole('textbox', {name: 'Body'})).not.toBeInTheDocument();
// HEAD
await selectEvent.clearAll(input('Method'));
await selectEvent.select(input('Method'), 'HEAD');
expect(screen.queryByRole('textbox', {name: 'Body'})).not.toBeInTheDocument();
// POST
await selectEvent.clearAll(input('Method'));
await selectEvent.select(input('Method'), 'POST');
expect(input('Body')).toBeInTheDocument();
});
it('updates environments for different projects', async function () {
OrganizationStore.onUpdate(organization);
const project1 = ProjectFixture({
slug: 'project-1',
environments: ['dev-1', 'prod-1'],
});
const project2 = ProjectFixture({
slug: 'project-2',
environments: ['dev-2', 'prod-2'],
});
ProjectsStore.loadInitialData([project, project1, project2]);
render(, {
organization,
});
await screen.findByText('Configure Request');
// Select project 1
await selectEvent.openMenu(input('Project'));
expect(screen.getByRole('menuitemradio', {name: 'project-1'})).toBeInTheDocument();
expect(screen.getByRole('menuitemradio', {name: 'project-2'})).toBeInTheDocument();
await userEvent.click(screen.getByRole('menuitemradio', {name: 'project-1'}));
// Verify correct envs
await selectEvent.openMenu(input('Environment'));
expect(screen.getByRole('menuitemradio', {name: 'dev-1'})).toBeInTheDocument();
expect(screen.getByRole('menuitemradio', {name: 'prod-1'})).toBeInTheDocument();
// Select project 2
await selectEvent.openMenu(input('Project'));
await userEvent.click(screen.getByRole('menuitemradio', {name: 'project-2'}));
// Verify correct envs
await selectEvent.openMenu(input('Environment'));
expect(screen.getByRole('menuitemradio', {name: 'dev-2'})).toBeInTheDocument();
expect(screen.getByRole('menuitemradio', {name: 'prod-2'})).toBeInTheDocument();
});
it('can create a new environment', async function () {
OrganizationStore.onUpdate(organization);
render(, {
organization,
});
await screen.findByText('Configure Request');
await userEvent.type(input('Environment'), 'my-custom-env');
await userEvent.click(
screen.getByRole('menuitemradio', {name: 'Create "my-custom-env"'})
);
await userEvent.clear(input('URL'));
await userEvent.type(input('URL'), 'http://example.com');
const name = input('Uptime rule name');
await userEvent.clear(name);
await userEvent.type(name, 'New Uptime Rule');
const updateMock = MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/${project.slug}/uptime/`,
method: 'POST',
});
await userEvent.click(screen.getByRole('button', {name: 'Create Rule'}));
expect(updateMock).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
data: expect.objectContaining({}),
})
);
});
});