Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-06-11 21:10:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-06-11 21:10:13 +0300
commite58ce90f147742c314b9cc08c2d1c0b585e39cf9 (patch)
tree467a1716bb63f4061e57b824c0e07532ca2fba4c /spec/frontend/runner
parent62cd7010ef91dcaa5a5a36790985053db0b38671 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/runner')
-rw-r--r--spec/frontend/runner/components/cells/runner_actions_cell_spec.js4
-rw-r--r--spec/frontend/runner/components/runner_type_alert_spec.js61
-rw-r--r--spec/frontend/runner/components/runner_type_badge_spec.js10
-rw-r--r--spec/frontend/runner/components/runner_update_form_spec.js263
4 files changed, 334 insertions, 4 deletions
diff --git a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
index 9bf47b7ee7a..12651a82a0c 100644
--- a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
+++ b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
@@ -4,7 +4,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue';
import deleteRunnerMutation from '~/runner/graphql/delete_runner.mutation.graphql';
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
-import updateRunnerMutation from '~/runner/graphql/update_runner.mutation.graphql';
+import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql';
const mockId = '1';
@@ -101,7 +101,7 @@ describe('RunnerTypeCell', () => {
it(`The apollo mutation to set active to ${newActiveValue} is called`, () => {
expect(mutate).toHaveBeenCalledTimes(1);
expect(mutate).toHaveBeenCalledWith({
- mutation: updateRunnerMutation,
+ mutation: runnerUpdateMutation,
variables: {
input: {
id: `gid://gitlab/Ci::Runner/${mockId}`,
diff --git a/spec/frontend/runner/components/runner_type_alert_spec.js b/spec/frontend/runner/components/runner_type_alert_spec.js
new file mode 100644
index 00000000000..5b136a77eeb
--- /dev/null
+++ b/spec/frontend/runner/components/runner_type_alert_spec.js
@@ -0,0 +1,61 @@
+import { GlAlert, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import RunnerTypeAlert from '~/runner/components/runner_type_alert.vue';
+import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
+
+describe('RunnerTypeAlert', () => {
+ let wrapper;
+
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findLink = () => wrapper.findComponent(GlLink);
+
+ const createComponent = ({ props = {} } = {}) => {
+ wrapper = shallowMount(RunnerTypeAlert, {
+ propsData: {
+ type: INSTANCE_TYPE,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe.each`
+ type | exampleText | anchor | variant
+ ${INSTANCE_TYPE} | ${'Shared runners are available to every project'} | ${'#shared-runners'} | ${'success'}
+ ${GROUP_TYPE} | ${'Use Group runners when you want all projects in a group'} | ${'#group-runners'} | ${'success'}
+ ${PROJECT_TYPE} | ${'You can set up a specific runner to be used by multiple projects'} | ${'#specific-runners'} | ${'info'}
+ `('When it is an $type level runner', ({ type, exampleText, anchor, variant }) => {
+ beforeEach(() => {
+ createComponent({ props: { type } });
+ });
+
+ it('Describes runner type', () => {
+ expect(wrapper.text()).toMatch(exampleText);
+ });
+
+ it(`Shows a ${variant} variant`, () => {
+ expect(findAlert().props('variant')).toBe(variant);
+ });
+
+ it(`Links to anchor "${anchor}"`, () => {
+ expect(findLink().attributes('href')).toBe(`/help/ci/runners/runners_scope${anchor}`);
+ });
+ });
+
+ describe('When runner type is not correct', () => {
+ it('Does not render content when type is missing', () => {
+ createComponent({ props: { type: undefined } });
+
+ expect(wrapper.html()).toBe('');
+ });
+
+ it('Validation fails for an incorrect type', () => {
+ expect(() => {
+ createComponent({ props: { type: 'NOT_A_TYPE' } });
+ }).toThrow();
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/runner_type_badge_spec.js b/spec/frontend/runner/components/runner_type_badge_spec.js
index 8e52d3398bd..ab5ccf6390f 100644
--- a/spec/frontend/runner/components/runner_type_badge_spec.js
+++ b/spec/frontend/runner/components/runner_type_badge_spec.js
@@ -32,8 +32,14 @@ describe('RunnerTypeBadge', () => {
expect(findBadge().props('variant')).toBe(variant);
});
- it('does not display a badge when type is unknown', () => {
- createComponent({ props: { type: 'AN_UNKNOWN_VALUE' } });
+ it('validation fails for an incorrect type', () => {
+ expect(() => {
+ createComponent({ props: { type: 'AN_UNKNOWN_VALUE' } });
+ }).toThrow();
+ });
+
+ it('does not render content when type is missing', () => {
+ createComponent({ props: { type: undefined } });
expect(findBadge().exists()).toBe(false);
});
diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js
new file mode 100644
index 00000000000..6333ed7118a
--- /dev/null
+++ b/spec/frontend/runner/components/runner_update_form_spec.js
@@ -0,0 +1,263 @@
+import { GlForm } from '@gitlab/ui';
+import { createLocalVue, mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createFlash, { FLASH_TYPES } from '~/flash';
+import RunnerUpdateForm from '~/runner/components/runner_update_form.vue';
+import {
+ INSTANCE_TYPE,
+ GROUP_TYPE,
+ PROJECT_TYPE,
+ ACCESS_LEVEL_REF_PROTECTED,
+ ACCESS_LEVEL_NOT_PROTECTED,
+} from '~/runner/constants';
+import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql';
+import { runnerData } from '../mock_data';
+
+jest.mock('~/flash');
+
+const mockRunner = runnerData.data.runner;
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('RunnerUpdateForm', () => {
+ let wrapper;
+ let runnerUpdateHandler;
+
+ const findForm = () => wrapper.findComponent(GlForm);
+ const findPausedCheckbox = () => wrapper.findByTestId('runner-field-paused');
+ const findProtectedCheckbox = () => wrapper.findByTestId('runner-field-protected');
+ const findRunUntaggedCheckbox = () => wrapper.findByTestId('runner-field-run-untagged');
+ const findLockedCheckbox = () => wrapper.findByTestId('runner-field-locked');
+
+ const findIpInput = () => wrapper.findByTestId('runner-field-ip-address').find('input');
+
+ const findDescriptionInput = () => wrapper.findByTestId('runner-field-description').find('input');
+ const findMaxJobTimeoutInput = () =>
+ wrapper.findByTestId('runner-field-max-timeout').find('input');
+ const findTagsInput = () => wrapper.findByTestId('runner-field-tags').find('input');
+
+ const findSubmit = () => wrapper.find('[type="submit"]');
+ const findSubmitDisabledAttr = () => findSubmit().attributes('disabled');
+ const submitForm = () => findForm().trigger('submit');
+ const submitFormAndWait = () => submitForm().then(waitForPromises);
+
+ const getFieldsModel = () => ({
+ active: !findPausedCheckbox().element.checked,
+ accessLevel: findProtectedCheckbox().element.checked
+ ? ACCESS_LEVEL_REF_PROTECTED
+ : ACCESS_LEVEL_NOT_PROTECTED,
+ runUntagged: findRunUntaggedCheckbox().element.checked,
+ locked: findLockedCheckbox().element.checked,
+ ipAddress: findIpInput().element.value,
+ maximumTimeout: findMaxJobTimeoutInput().element.value || null,
+ tagList: findTagsInput().element.value.split(',').filter(Boolean),
+ });
+
+ const createComponent = ({ props } = {}) => {
+ wrapper = extendedWrapper(
+ mount(RunnerUpdateForm, {
+ localVue,
+ propsData: {
+ runner: mockRunner,
+ ...props,
+ },
+ apolloProvider: createMockApollo([[runnerUpdateMutation, runnerUpdateHandler]]),
+ }),
+ );
+ };
+
+ const expectToHaveSubmittedRunnerContaining = (submittedRunner) => {
+ expect(runnerUpdateHandler).toHaveBeenCalledTimes(1);
+ expect(runnerUpdateHandler).toHaveBeenCalledWith({
+ input: expect.objectContaining(submittedRunner),
+ });
+
+ expect(createFlash).toHaveBeenLastCalledWith({
+ message: expect.stringContaining('saved'),
+ type: FLASH_TYPES.SUCCESS,
+ });
+
+ expect(findSubmitDisabledAttr()).toBeUndefined();
+ };
+
+ beforeEach(() => {
+ runnerUpdateHandler = jest.fn().mockImplementation(({ input }) => {
+ return Promise.resolve({
+ data: {
+ runnerUpdate: {
+ runner: {
+ ...mockRunner,
+ ...input,
+ },
+ errors: [],
+ },
+ },
+ });
+ });
+
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('Form has a submit button', () => {
+ expect(findSubmit().exists()).toBe(true);
+ });
+
+ it('Form fields match data', () => {
+ expect(mockRunner).toMatchObject(getFieldsModel());
+ });
+
+ it('Form prevent multiple submissions', async () => {
+ await submitForm();
+
+ expect(findSubmitDisabledAttr()).toBe('disabled');
+ });
+
+ it('Updates runner with no changes', async () => {
+ await submitFormAndWait();
+
+ // Some fields are not submitted
+ const { ipAddress, runnerType, ...submitted } = mockRunner;
+
+ expectToHaveSubmittedRunnerContaining(submitted);
+ });
+
+ describe('When data is being loaded', () => {
+ beforeEach(() => {
+ createComponent({ props: { runner: null } });
+ });
+
+ it('Form cannot be submitted', () => {
+ expect(findSubmit().props('loading')).toBe(true);
+ });
+
+ it('Form is updated when data loads', async () => {
+ wrapper.setProps({
+ runner: mockRunner,
+ });
+
+ await nextTick();
+
+ expect(mockRunner).toMatchObject(getFieldsModel());
+ });
+ });
+
+ it.each`
+ runnerType | attrDisabled | outcome
+ ${INSTANCE_TYPE} | ${'disabled'} | ${'disabled'}
+ ${GROUP_TYPE} | ${'disabled'} | ${'disabled'}
+ ${PROJECT_TYPE} | ${undefined} | ${'enabled'}
+ `(`When runner is $runnerType, locked field is $outcome`, ({ runnerType, attrDisabled }) => {
+ const runner = { ...mockRunner, runnerType };
+ createComponent({ props: { runner } });
+
+ expect(findLockedCheckbox().attributes('disabled')).toBe(attrDisabled);
+ });
+
+ describe('On submit, runner gets updated', () => {
+ it.each`
+ test | initialValue | findCheckbox | checked | submitted
+ ${'pauses'} | ${{ active: true }} | ${findPausedCheckbox} | ${true} | ${{ active: false }}
+ ${'activates'} | ${{ active: false }} | ${findPausedCheckbox} | ${false} | ${{ active: true }}
+ ${'unprotects'} | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED }} | ${findProtectedCheckbox} | ${true} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED }}
+ ${'protects'} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED }} | ${findProtectedCheckbox} | ${false} | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED }}
+ ${'"runs untagged jobs"'} | ${{ runUntagged: true }} | ${findRunUntaggedCheckbox} | ${false} | ${{ runUntagged: false }}
+ ${'"runs tagged jobs"'} | ${{ runUntagged: false }} | ${findRunUntaggedCheckbox} | ${true} | ${{ runUntagged: true }}
+ ${'locks'} | ${{ runnerType: PROJECT_TYPE, locked: true }} | ${findLockedCheckbox} | ${false} | ${{ locked: false }}
+ ${'unlocks'} | ${{ runnerType: PROJECT_TYPE, locked: false }} | ${findLockedCheckbox} | ${true} | ${{ locked: true }}
+ `('Checkbox $test runner', async ({ initialValue, findCheckbox, checked, submitted }) => {
+ const runner = { ...mockRunner, ...initialValue };
+ createComponent({ props: { runner } });
+
+ await findCheckbox().setChecked(checked);
+ await submitFormAndWait();
+
+ expectToHaveSubmittedRunnerContaining({
+ id: runner.id,
+ ...submitted,
+ });
+ });
+
+ it.each`
+ test | initialValue | findInput | value | submitted
+ ${'description'} | ${{ description: 'Desc. 1' }} | ${findDescriptionInput} | ${'Desc. 2'} | ${{ description: 'Desc. 2' }}
+ ${'max timeout'} | ${{ maximumTimeout: 36000 }} | ${findMaxJobTimeoutInput} | ${'40000'} | ${{ maximumTimeout: 40000 }}
+ ${'tags'} | ${{ tagList: ['tag1'] }} | ${findTagsInput} | ${'tag2, tag3'} | ${{ tagList: ['tag2', 'tag3'] }}
+ `("Field updates runner's $test", async ({ initialValue, findInput, value, submitted }) => {
+ const runner = { ...mockRunner, ...initialValue };
+ createComponent({ props: { runner } });
+
+ await findInput().setValue(value);
+ await submitFormAndWait();
+
+ expectToHaveSubmittedRunnerContaining({
+ id: runner.id,
+ ...submitted,
+ });
+ });
+
+ it.each`
+ value | submitted
+ ${''} | ${{ tagList: [] }}
+ ${'tag1, tag2'} | ${{ tagList: ['tag1', 'tag2'] }}
+ ${'with spaces'} | ${{ tagList: ['with spaces'] }}
+ ${',,,,, commas'} | ${{ tagList: ['commas'] }}
+ ${'more ,,,,, commas'} | ${{ tagList: ['more', 'commas'] }}
+ ${' trimmed , trimmed2 '} | ${{ tagList: ['trimmed', 'trimmed2'] }}
+ `('Field updates runner\'s tags for "$value"', async ({ value, submitted }) => {
+ const runner = { ...mockRunner, tagList: ['tag1'] };
+ createComponent({ props: { runner } });
+
+ await findTagsInput().setValue(value);
+ await submitFormAndWait();
+
+ expectToHaveSubmittedRunnerContaining({
+ id: runner.id,
+ ...submitted,
+ });
+ });
+ });
+
+ describe('On error', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('On network error, error message is shown', async () => {
+ runnerUpdateHandler.mockRejectedValue(new Error('Something went wrong'));
+
+ await submitFormAndWait();
+
+ expect(createFlash).toHaveBeenLastCalledWith({
+ message: 'Network error: Something went wrong',
+ });
+ expect(findSubmitDisabledAttr()).toBeUndefined();
+ });
+
+ it('On validation error, error message is shown', async () => {
+ runnerUpdateHandler.mockResolvedValue({
+ data: {
+ runnerUpdate: {
+ runner: mockRunner,
+ errors: ['A value is invalid'],
+ },
+ },
+ });
+
+ await submitFormAndWait();
+
+ expect(createFlash).toHaveBeenLastCalledWith({
+ message: 'A value is invalid',
+ });
+ expect(findSubmitDisabledAttr()).toBeUndefined();
+ });
+ });
+});