2022-04-04 11:22:00 +05:30
|
|
|
import Vue from 'vue';
|
2021-09-30 23:02:18 +05:30
|
|
|
import VueApollo from 'vue-apollo';
|
|
|
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
2022-04-04 11:22:00 +05:30
|
|
|
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
2021-09-04 01:27:46 +05:30
|
|
|
import waitForPromises from 'helpers/wait_for_promises';
|
2022-01-26 12:08:38 +05:30
|
|
|
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
2022-03-02 08:16:31 +05:30
|
|
|
import { createAlert } from '~/flash';
|
2022-01-26 12:08:38 +05:30
|
|
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
|
|
|
|
|
|
|
import { captureException } from '~/runner/sentry_utils';
|
2021-09-04 01:27:46 +05:30
|
|
|
import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue';
|
2022-04-04 11:22:00 +05:30
|
|
|
import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
|
|
|
|
import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
|
2022-01-26 12:08:38 +05:30
|
|
|
import RunnerDeleteModal from '~/runner/components/runner_delete_modal.vue';
|
2021-11-18 22:05:49 +05:30
|
|
|
import getGroupRunnersQuery from '~/runner/graphql/get_group_runners.query.graphql';
|
2021-09-04 01:27:46 +05:30
|
|
|
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
|
2021-09-30 23:02:18 +05:30
|
|
|
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
|
2021-12-11 22:18:48 +05:30
|
|
|
import { runnersData } from '../../mock_data';
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2021-11-18 22:05:49 +05:30
|
|
|
const mockRunner = runnersData.data.runners.nodes[0];
|
2021-09-04 01:27:46 +05:30
|
|
|
|
|
|
|
const getRunnersQueryName = getRunnersQuery.definitions[0].name.value;
|
2021-11-18 22:05:49 +05:30
|
|
|
const getGroupRunnersQueryName = getGroupRunnersQuery.definitions[0].name.value;
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
Vue.use(VueApollo);
|
2021-09-30 23:02:18 +05:30
|
|
|
|
|
|
|
jest.mock('~/flash');
|
|
|
|
jest.mock('~/runner/sentry_utils');
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
describe('RunnerTypeCell', () => {
|
|
|
|
let wrapper;
|
2022-01-26 12:08:38 +05:30
|
|
|
|
|
|
|
const mockToastShow = jest.fn();
|
2021-09-30 23:02:18 +05:30
|
|
|
const runnerDeleteMutationHandler = jest.fn();
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
const findEditBtn = () => wrapper.findComponent(RunnerEditButton);
|
|
|
|
const findRunnerPauseBtn = () => wrapper.findComponent(RunnerPauseButton);
|
2022-01-26 12:08:38 +05:30
|
|
|
const findRunnerDeleteModal = () => wrapper.findComponent(RunnerDeleteModal);
|
2021-09-04 01:27:46 +05:30
|
|
|
const findDeleteBtn = () => wrapper.findByTestId('delete-runner');
|
2022-01-26 12:08:38 +05:30
|
|
|
const getTooltip = (w) => getBinding(w.element, 'gl-tooltip')?.value;
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2022-03-02 08:16:31 +05:30
|
|
|
const createComponent = (runner = {}, options) => {
|
2022-04-04 11:22:00 +05:30
|
|
|
wrapper = shallowMountExtended(RunnerActionCell, {
|
|
|
|
propsData: {
|
|
|
|
runner: {
|
|
|
|
id: mockRunner.id,
|
|
|
|
shortSha: mockRunner.shortSha,
|
|
|
|
editAdminUrl: mockRunner.editAdminUrl,
|
|
|
|
userPermissions: mockRunner.userPermissions,
|
|
|
|
active: mockRunner.active,
|
|
|
|
...runner,
|
2021-09-04 01:27:46 +05:30
|
|
|
},
|
2022-04-04 11:22:00 +05:30
|
|
|
},
|
|
|
|
apolloProvider: createMockApollo([[runnerDeleteMutation, runnerDeleteMutationHandler]]),
|
|
|
|
directives: {
|
|
|
|
GlTooltip: createMockDirective(),
|
|
|
|
GlModal: createMockDirective(),
|
|
|
|
},
|
|
|
|
mocks: {
|
|
|
|
$toast: {
|
|
|
|
show: mockToastShow,
|
2022-01-26 12:08:38 +05:30
|
|
|
},
|
2022-04-04 11:22:00 +05:30
|
|
|
},
|
|
|
|
...options,
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2021-09-30 23:02:18 +05:30
|
|
|
runnerDeleteMutationHandler.mockResolvedValue({
|
|
|
|
data: {
|
|
|
|
runnerDelete: {
|
|
|
|
errors: [],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
2022-01-26 12:08:38 +05:30
|
|
|
mockToastShow.mockReset();
|
2021-09-30 23:02:18 +05:30
|
|
|
runnerDeleteMutationHandler.mockReset();
|
|
|
|
|
2021-09-04 01:27:46 +05:30
|
|
|
wrapper.destroy();
|
|
|
|
});
|
|
|
|
|
2022-01-26 12:08:38 +05:30
|
|
|
describe('Edit Action', () => {
|
|
|
|
it('Displays the runner edit link with the correct href', () => {
|
|
|
|
createComponent();
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2022-03-02 08:16:31 +05:30
|
|
|
expect(findEditBtn().attributes('href')).toBe(mockRunner.editAdminUrl);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Does not render the runner edit link when user cannot update', () => {
|
|
|
|
createComponent({
|
|
|
|
userPermissions: {
|
|
|
|
...mockRunner.userPermissions,
|
|
|
|
updateRunner: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(findEditBtn().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Does not render the runner edit link when editAdminUrl is not provided', () => {
|
|
|
|
createComponent({
|
|
|
|
editAdminUrl: null,
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(findEditBtn().exists()).toBe(false);
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
2022-01-26 12:08:38 +05:30
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
describe('Pause action', () => {
|
|
|
|
it('Renders a compact pause button', () => {
|
|
|
|
createComponent();
|
2021-09-30 23:02:18 +05:30
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
expect(findRunnerPauseBtn().props('compact')).toBe(true);
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
2022-03-02 08:16:31 +05:30
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
it('Does not render the runner pause button when user cannot update', () => {
|
2022-03-02 08:16:31 +05:30
|
|
|
createComponent({
|
|
|
|
userPermissions: {
|
|
|
|
...mockRunner.userPermissions,
|
|
|
|
updateRunner: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
expect(findRunnerPauseBtn().exists()).toBe(false);
|
2022-03-02 08:16:31 +05:30
|
|
|
});
|
2022-01-26 12:08:38 +05:30
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2022-01-26 12:08:38 +05:30
|
|
|
describe('Delete action', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
createComponent(
|
|
|
|
{},
|
|
|
|
{
|
|
|
|
stubs: { RunnerDeleteModal },
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2022-03-02 08:16:31 +05:30
|
|
|
it('Renders delete button', () => {
|
|
|
|
expect(findDeleteBtn().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
2022-01-26 12:08:38 +05:30
|
|
|
it('Delete button opens delete modal', () => {
|
|
|
|
const modalId = getBinding(findDeleteBtn().element, 'gl-modal').value;
|
|
|
|
|
|
|
|
expect(findRunnerDeleteModal().attributes('modal-id')).toBeDefined();
|
|
|
|
expect(findRunnerDeleteModal().attributes('modal-id')).toBe(modalId);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Delete modal shows the runner name', () => {
|
|
|
|
expect(findRunnerDeleteModal().props('runnerName')).toBe(
|
|
|
|
`#${getIdFromGraphQLId(mockRunner.id)} (${mockRunner.shortSha})`,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
it('The delete button does not have a loading icon', () => {
|
|
|
|
expect(findDeleteBtn().props('loading')).toBe(false);
|
|
|
|
expect(getTooltip(findDeleteBtn())).toBe('Delete runner');
|
|
|
|
});
|
|
|
|
|
|
|
|
it('When delete mutation is called, current runners are refetched', () => {
|
|
|
|
jest.spyOn(wrapper.vm.$apollo, 'mutate');
|
|
|
|
|
|
|
|
findRunnerDeleteModal().vm.$emit('primary');
|
|
|
|
|
|
|
|
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
|
|
|
|
mutation: runnerDeleteMutation,
|
|
|
|
variables: {
|
|
|
|
input: {
|
|
|
|
id: mockRunner.id,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
awaitRefetchQueries: true,
|
|
|
|
refetchQueries: [getRunnersQueryName, getGroupRunnersQueryName],
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
2022-01-26 12:08:38 +05:30
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2022-03-02 08:16:31 +05:30
|
|
|
it('Does not render the runner delete button when user cannot delete', () => {
|
|
|
|
createComponent({
|
|
|
|
userPermissions: {
|
|
|
|
...mockRunner.userPermissions,
|
|
|
|
deleteRunner: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(findDeleteBtn().exists()).toBe(false);
|
|
|
|
expect(findRunnerDeleteModal().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
2022-01-26 12:08:38 +05:30
|
|
|
describe('When delete is clicked', () => {
|
2022-04-04 11:22:00 +05:30
|
|
|
beforeEach(async () => {
|
2022-01-26 12:08:38 +05:30
|
|
|
findRunnerDeleteModal().vm.$emit('primary');
|
2022-04-04 11:22:00 +05:30
|
|
|
await waitForPromises();
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
2022-01-26 12:08:38 +05:30
|
|
|
it('The delete mutation is called correctly', () => {
|
|
|
|
expect(runnerDeleteMutationHandler).toHaveBeenCalledTimes(1);
|
|
|
|
expect(runnerDeleteMutationHandler).toHaveBeenCalledWith({
|
|
|
|
input: { id: mockRunner.id },
|
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
|
2022-01-26 12:08:38 +05:30
|
|
|
it('The delete button has a loading icon', () => {
|
|
|
|
expect(findDeleteBtn().props('loading')).toBe(true);
|
|
|
|
expect(getTooltip(findDeleteBtn())).toBe('');
|
|
|
|
});
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
it('The toast notification is shown', async () => {
|
|
|
|
await waitForPromises();
|
2022-01-26 12:08:38 +05:30
|
|
|
expect(mockToastShow).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockToastShow).toHaveBeenCalledWith(
|
|
|
|
expect.stringContaining(`#${getIdFromGraphQLId(mockRunner.id)} (${mockRunner.shortSha})`),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('When delete fails', () => {
|
|
|
|
describe('On a network error', () => {
|
|
|
|
const mockErrorMsg = 'Delete error!';
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
beforeEach(async () => {
|
2022-01-26 12:08:38 +05:30
|
|
|
runnerDeleteMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
|
|
|
|
|
|
|
|
findRunnerDeleteModal().vm.$emit('primary');
|
2022-04-04 11:22:00 +05:30
|
|
|
await waitForPromises();
|
2022-01-26 12:08:38 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('error is reported to sentry', () => {
|
|
|
|
expect(captureException).toHaveBeenCalledWith({
|
2022-04-04 11:22:00 +05:30
|
|
|
error: new Error(mockErrorMsg),
|
2022-01-26 12:08:38 +05:30
|
|
|
component: 'RunnerActionsCell',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('error is shown to the user', () => {
|
2022-03-02 08:16:31 +05:30
|
|
|
expect(createAlert).toHaveBeenCalledTimes(1);
|
2022-01-26 12:08:38 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('toast notification is not shown', () => {
|
|
|
|
expect(mockToastShow).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('On a validation error', () => {
|
|
|
|
const mockErrorMsg = 'Runner not found!';
|
|
|
|
const mockErrorMsg2 = 'User not allowed!';
|
|
|
|
|
2022-04-04 11:22:00 +05:30
|
|
|
beforeEach(async () => {
|
2022-01-26 12:08:38 +05:30
|
|
|
runnerDeleteMutationHandler.mockResolvedValue({
|
|
|
|
data: {
|
|
|
|
runnerDelete: {
|
|
|
|
errors: [mockErrorMsg, mockErrorMsg2],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
findRunnerDeleteModal().vm.$emit('primary');
|
2022-04-04 11:22:00 +05:30
|
|
|
await waitForPromises();
|
2022-01-26 12:08:38 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
it('error is reported to sentry', () => {
|
|
|
|
expect(captureException).toHaveBeenCalledWith({
|
|
|
|
error: new Error(`${mockErrorMsg} ${mockErrorMsg2}`),
|
|
|
|
component: 'RunnerActionsCell',
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('error is shown to the user', () => {
|
2022-03-02 08:16:31 +05:30
|
|
|
expect(createAlert).toHaveBeenCalledTimes(1);
|
2022-01-26 12:08:38 +05:30
|
|
|
});
|
2021-09-04 01:27:46 +05:30
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|