debian-mirror-gitlab/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
2021-06-08 01:23:25 +05:30

516 lines
18 KiB
JavaScript

import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
import { typeSet, i18n } from '~/alerts_settings/constants';
import createPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql';
import destroyHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '~/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql';
import resetPrometheusTokenMutation from '~/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql';
import updateCurrentHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/update_current_http_integration.mutation.graphql';
import updateCurrentPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_current_prometheus_integration.mutation.graphql';
import updatePrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql';
import getHttpIntegrationQuery from '~/alerts_settings/graphql/queries/get_http_integration.query.graphql';
import getIntegrationsQuery from '~/alerts_settings/graphql/queries/get_integrations.query.graphql';
import alertsUpdateService from '~/alerts_settings/services';
import {
ADD_INTEGRATION_ERROR,
RESET_INTEGRATION_TOKEN_ERROR,
UPDATE_INTEGRATION_ERROR,
INTEGRATION_PAYLOAD_TEST_ERROR,
INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
DELETE_INTEGRATION_ERROR,
} from '~/alerts_settings/utils/error_messages';
import createFlash, { FLASH_TYPES } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import {
createHttpVariables,
updateHttpVariables,
createPrometheusVariables,
updatePrometheusVariables,
HTTP_ID,
PROMETHEUS_ID,
errorMsg,
getIntegrationsQueryResponse,
destroyIntegrationResponse,
integrationToDestroy,
destroyIntegrationResponseWithErrors,
} from './mocks/apollo_mock';
import mockIntegrations from './mocks/integrations.json';
jest.mock('~/flash');
const localVue = createLocalVue();
describe('AlertsSettingsWrapper', () => {
let wrapper;
let fakeApollo;
let destroyIntegrationHandler;
useMockIntersectionObserver();
const httpMappingData = {
payloadExample: '{"test: : "field"}',
payloadAttributeMappings: [],
payloadAlertFields: [],
};
const findLoader = () => wrapper.findComponent(IntegrationsList).findComponent(GlLoadingIcon);
const findIntegrationsList = () => wrapper.findComponent(IntegrationsList);
const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr');
const findAddIntegrationBtn = () => wrapper.findByTestId('add-integration-btn');
const findAlertsSettingsForm = () => wrapper.findComponent(AlertsSettingsForm);
const findAlert = () => wrapper.findComponent(GlAlert);
async function destroyHttpIntegration(localWrapper) {
await jest.runOnlyPendingTimers();
await localWrapper.vm.$nextTick();
localWrapper
.find(IntegrationsList)
.vm.$emit('delete-integration', { id: integrationToDestroy.id });
}
async function awaitApolloDomMock() {
await nextTick(); // kick off the DOM update
await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
await nextTick(); // kick off the DOM update for flash
}
const createComponent = ({ data = {}, provide = {}, loading = false } = {}) => {
wrapper = extendedWrapper(
mount(AlertsSettingsWrapper, {
data() {
return { ...data };
},
provide: {
...provide,
},
mocks: {
$apollo: {
mutate: jest.fn(),
addSmartQuery: jest.fn((_, options) => {
options.result.call(wrapper.vm);
}),
queries: {
integrations: {
loading,
},
},
},
},
}),
);
};
function createComponentWithApollo({
destroyHandler = jest.fn().mockResolvedValue(destroyIntegrationResponse),
} = {}) {
localVue.use(VueApollo);
destroyIntegrationHandler = destroyHandler;
const requestHandlers = [
[getIntegrationsQuery, jest.fn().mockResolvedValue(getIntegrationsQueryResponse)],
[destroyHttpIntegrationMutation, destroyIntegrationHandler],
];
fakeApollo = createMockApollo(requestHandlers);
wrapper = mount(AlertsSettingsWrapper, {
localVue,
apolloProvider: fakeApollo,
});
}
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('template', () => {
beforeEach(() => {
createComponent({
data: {
integrations: mockIntegrations,
currentIntegration: mockIntegrations[0],
},
loading: false,
});
});
it('renders alerts integrations list', () => {
expect(findLoader().exists()).toBe(false);
expect(findIntegrations()).toHaveLength(mockIntegrations.length);
});
it('renders `Add new integration` button when multiple integrations are supported ', () => {
createComponent({
data: {
integrations: mockIntegrations,
currentIntegration: mockIntegrations[0],
},
provide: {
multiIntegrations: true,
},
loading: false,
});
expect(findAddIntegrationBtn().exists()).toBe(true);
});
it('does NOT render settings form by default', () => {
expect(findAlertsSettingsForm().exists()).toBe(false);
});
it('hides `add new integration` button and displays setting form on btn click', async () => {
createComponent({
data: {
integrations: mockIntegrations,
currentIntegration: mockIntegrations[0],
},
provide: {
multiIntegrations: true,
},
loading: false,
});
const addNewIntegrationBtn = findAddIntegrationBtn();
expect(addNewIntegrationBtn.exists()).toBe(true);
await addNewIntegrationBtn.trigger('click');
expect(findAlertsSettingsForm().exists()).toBe(true);
expect(addNewIntegrationBtn.exists()).toBe(false);
});
it('shows loading indicator inside the IntegrationsList table', () => {
createComponent({
data: { integrations: [] },
loading: true,
});
expect(wrapper.find(IntegrationsList).exists()).toBe(true);
expect(findLoader().exists()).toBe(true);
});
});
describe('Integration updates', () => {
beforeEach(() => {
createComponent({
data: {
integrations: mockIntegrations,
currentIntegration: mockIntegrations[0],
formVisible: true,
},
loading: false,
});
});
describe('Create', () => {
beforeEach(() => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { httpIntegrationCreate: { integration: { id: '1' }, errors: [] } },
});
findAlertsSettingsForm().vm.$emit('create-new-integration', {
type: typeSet.http,
variables: createHttpVariables,
});
});
it('calls `$apollo.mutate` with `createHttpIntegrationMutation`', () => {
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: createHttpIntegrationMutation,
update: expect.anything(),
variables: createHttpVariables,
});
});
it('shows success alert', () => {
expect(findAlert().exists()).toBe(true);
});
});
it('calls `$apollo.mutate` with `updateHttpIntegrationMutation`', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { updateHttpIntegrationMutation: { integration: { id: '1' } } },
});
findAlertsSettingsForm().vm.$emit('update-integration', {
type: typeSet.http,
variables: updateHttpVariables,
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updateHttpIntegrationMutation,
variables: updateHttpVariables,
});
});
it('calls `$apollo.mutate` with `resetHttpTokenMutation`', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { resetHttpTokenMutation: { integration: { id: '1' } } },
});
findAlertsSettingsForm().vm.$emit('reset-token', {
type: typeSet.http,
variables: { id: HTTP_ID },
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: resetHttpTokenMutation,
variables: {
id: HTTP_ID,
},
});
});
it('calls `$apollo.mutate` with `createPrometheusIntegrationMutation`', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { createPrometheusIntegrationMutation: { integration: { id: '2' } } },
});
findAlertsSettingsForm().vm.$emit('create-new-integration', {
type: typeSet.prometheus,
variables: createPrometheusVariables,
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: createPrometheusIntegrationMutation,
update: expect.anything(),
variables: createPrometheusVariables,
});
});
it('calls `$apollo.mutate` with `updatePrometheusIntegrationMutation`', () => {
createComponent({
data: {
integrations: mockIntegrations,
currentIntegration: mockIntegrations[3],
formVisible: true,
},
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { updatePrometheusIntegrationMutation: { integration: { id: '2' } } },
});
findAlertsSettingsForm().vm.$emit('update-integration', {
type: typeSet.prometheus,
variables: updatePrometheusVariables,
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updatePrometheusIntegrationMutation,
variables: updatePrometheusVariables,
});
});
it('calls `$apollo.mutate` with `resetPrometheusTokenMutation`', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { resetPrometheusTokenMutation: { integration: { id: '1' } } },
});
findAlertsSettingsForm().vm.$emit('reset-token', {
type: typeSet.prometheus,
variables: { id: PROMETHEUS_ID },
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: resetPrometheusTokenMutation,
variables: {
id: PROMETHEUS_ID,
},
});
});
it('shows an error alert when integration creation fails ', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(ADD_INTEGRATION_ERROR);
findAlertsSettingsForm().vm.$emit('create-new-integration', {});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: ADD_INTEGRATION_ERROR });
});
it('shows an error alert when integration token reset fails ', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(RESET_INTEGRATION_TOKEN_ERROR);
findAlertsSettingsForm().vm.$emit('reset-token', {});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: RESET_INTEGRATION_TOKEN_ERROR });
});
it('shows an error alert when integration update fails ', async () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(errorMsg);
findAlertsSettingsForm().vm.$emit('update-integration', {});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: UPDATE_INTEGRATION_ERROR });
});
describe('Test alert failure', () => {
let mock;
beforeEach(() => {
mock = new AxiosMockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
it('shows an error alert when integration test payload is invalid ', async () => {
mock.onPost(/(.*)/).replyOnce(httpStatusCodes.UNPROCESSABLE_ENTITY);
await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
expect(createFlash).toHaveBeenCalledTimes(1);
});
it('shows an error alert when integration is not activated ', async () => {
mock.onPost(/(.*)/).replyOnce(httpStatusCodes.FORBIDDEN);
await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
expect(createFlash).toHaveBeenCalledWith({
message: INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
});
expect(createFlash).toHaveBeenCalledTimes(1);
});
});
describe('Edit integration', () => {
describe('HTTP', () => {
beforeEach(() => {
createComponent({
data: {
integrations: mockIntegrations,
currentIntegration: mockIntegrations[0],
currentHttpIntegration: { id: mockIntegrations[0].id, ...httpMappingData },
},
provide: {
multiIntegrations: true,
},
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValueOnce({});
findIntegrationsList().vm.$emit('edit-integration', updateHttpVariables);
});
it('requests `currentHttpIntegration`', () => {
expect(wrapper.vm.$apollo.addSmartQuery).toHaveBeenCalledWith(
'currentHttpIntegration',
expect.objectContaining({
query: getHttpIntegrationQuery,
result: expect.any(Function),
update: expect.any(Function),
variables: expect.any(Function),
}),
);
});
it('calls `$apollo.mutate` with `updateCurrentHttpIntegrationMutation`', () => {
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updateCurrentHttpIntegrationMutation,
variables: { ...mockIntegrations[0], ...httpMappingData },
});
});
});
describe('Prometheus', () => {
it('calls `$apollo.mutate` with `updateCurrentPrometheusIntegrationMutation`', () => {
createComponent({
data: {
integrations: mockIntegrations,
currentIntegration: mockIntegrations[3],
},
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
findIntegrationsList().vm.$emit('edit-integration', updatePrometheusVariables);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updateCurrentPrometheusIntegrationMutation,
variables: mockIntegrations[3],
});
});
});
});
describe('Test alert', () => {
it('makes `updateTestAlert` service call', async () => {
jest.spyOn(alertsUpdateService, 'updateTestAlert').mockResolvedValueOnce();
const testPayload = '{"title":"test"}';
findAlertsSettingsForm().vm.$emit('test-alert-payload', testPayload);
expect(alertsUpdateService.updateTestAlert).toHaveBeenCalledWith(testPayload);
});
it('shows success message on successful test', async () => {
jest.spyOn(alertsUpdateService, 'updateTestAlert').mockResolvedValueOnce({});
findAlertsSettingsForm().vm.$emit('test-alert-payload', '');
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
message: i18n.alertSent,
type: FLASH_TYPES.SUCCESS,
});
});
it('shows error message when test alert fails', async () => {
jest.spyOn(alertsUpdateService, 'updateTestAlert').mockRejectedValueOnce({});
findAlertsSettingsForm().vm.$emit('test-alert-payload', '');
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
message: INTEGRATION_PAYLOAD_TEST_ERROR,
});
});
});
});
describe('with mocked Apollo client', () => {
it('has a selection of integrations loaded via the getIntegrationsQuery', async () => {
createComponentWithApollo();
await jest.runOnlyPendingTimers();
await nextTick();
expect(findIntegrations()).toHaveLength(4);
});
it('calls a mutation with correct parameters and destroys a integration', async () => {
createComponentWithApollo();
await destroyHttpIntegration(wrapper);
expect(destroyIntegrationHandler).toHaveBeenCalled();
await nextTick();
expect(findIntegrations()).toHaveLength(3);
});
it('displays flash if mutation had a recoverable error', async () => {
createComponentWithApollo({
destroyHandler: jest.fn().mockResolvedValue(destroyIntegrationResponseWithErrors),
});
await destroyHttpIntegration(wrapper);
await awaitApolloDomMock();
expect(createFlash).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
});
it('displays flash if mutation had a non-recoverable error', async () => {
createComponentWithApollo({
destroyHandler: jest.fn().mockRejectedValue('Error'),
});
await destroyHttpIntegration(wrapper);
await awaitApolloDomMock();
expect(createFlash).toHaveBeenCalledWith({
message: DELETE_INTEGRATION_ERROR,
});
});
});
});