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>2023-06-22 03:10:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-22 03:10:13 +0300
commit72023ba299df4aede839a0846688c3d8f0b096a1 (patch)
tree56f7fd59dc800979b3f10729e03c83b2735b5d8c /spec/frontend
parentaa10c5ed22ed20df96b5d12b5e8921971408e410 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js22
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js47
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row_spec.js220
3 files changed, 235 insertions, 54 deletions
diff --git a/spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js b/spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js
index a4c90fa3876..fc8e4c06019 100644
--- a/spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js
+++ b/spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js
@@ -13,13 +13,17 @@ export const job = {
},
name: 'job-name',
retried: false,
+ retryable: true,
stage: {
id: '1',
name: 'build',
},
trace: {
- htmlSummary:
- '<span>To install the missing version, run `gem install bundler:2.4.13`<br/>\tfrom /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path\'<br/>\tfrom /usr/bin/bundle:23:in `&lt;main>\'<br/></span><div class="section-start" data-timestamp="1685044123" data-section="upload-artifacts-on-failure" role="button"></div><span class="term-fg-l-cyan term-bold section section-header js-s-upload-artifacts-on-failure">Uploading artifacts for failed job</span><span class="section section-header js-s-upload-artifacts-on-failure"><br/></span><span class="term-fg-l-green term-bold section line js-s-upload-artifacts-on-failure">Uploading artifacts...</span><span class="section line js-s-upload-artifacts-on-failure"><br/>Runtime platform </span><span class="section line js-s-upload-artifacts-on-failure"> arch</span><span class="section line js-s-upload-artifacts-on-failure">=arm64 os</span><span class="section line js-s-upload-artifacts-on-failure">=darwin pid</span><span class="section line js-s-upload-artifacts-on-failure">=16706 revision</span><span class="section line js-s-upload-artifacts-on-failure">=43b2dc3d version</span><span class="section line js-s-upload-artifacts-on-failure">=15.4.0<br/></span><span class="term-fg-yellow section line js-s-upload-artifacts-on-failure">WARNING: rspec.xml: no matching files. Ensure that the artifact path is relative to the working directory</span><span class="section line js-s-upload-artifacts-on-failure"> <br/></span><span class="term-fg-l-red term-bold section line js-s-upload-artifacts-on-failure">ERROR: No files to upload </span><span class="section line js-s-upload-artifacts-on-failure"> <br/></span><div class="section-end" data-section="upload-artifacts-on-failure"></div><span class="term-fg-l-red term-bold">ERROR: Job failed: exit status 1<br/></span><span><br/></span>',
+ htmlSummary: '<h1>Hello</h1>',
+ },
+ userPermissions: {
+ readBuild: true,
+ updateBuild: true,
},
webPath: '/',
};
@@ -43,3 +47,17 @@ export const failedJobsMock = {
},
},
};
+
+export const failedJobsMock2 = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/20',
+ pipeline: {
+ id: 'gid://gitlab/Pipeline/20',
+ jobs: {
+ nodes: [job],
+ },
+ },
+ },
+ },
+};
diff --git a/spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js b/spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js
index df6d114f683..8e096b98030 100644
--- a/spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { GlButton, GlIcon, GlLoadingIcon, GlPopover } from '@gitlab/ui';
+import { GlButton, GlIcon, GlLoadingIcon, GlPopover, GlToast } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -10,14 +10,17 @@ import { createAlert } from '~/alert';
import WidgetFailedJobRow from '~/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row.vue';
import * as utils from '~/pipelines/components/pipelines_list/failure_widget/utils';
import getPipelineFailedJobs from '~/pipelines/graphql/queries/get_pipeline_failed_jobs.query.graphql';
-import { failedJobsMock } from './mock';
+import { failedJobsMock, failedJobsMock2 } from './mock';
Vue.use(VueApollo);
+Vue.use(GlToast);
+
jest.mock('~/alert');
describe('PipelineFailedJobsWidget component', () => {
let wrapper;
let mockFailedJobsResponse;
+ const showToast = jest.fn();
const defaultProps = {
pipelineIid: 1,
@@ -42,6 +45,11 @@ describe('PipelineFailedJobsWidget component', () => {
...provide,
},
apolloProvider: mockApollo,
+ mocks: {
+ $toast: {
+ show: showToast,
+ },
+ },
});
};
@@ -101,12 +109,13 @@ describe('PipelineFailedJobsWidget component', () => {
await findFailedJobsButton().vm.$emit('click');
await waitForPromises();
});
+
it('does not renders a loading icon', () => {
expect(findLoadingIcon().exists()).toBe(false);
});
it('renders table column', () => {
- expect(findAllHeaders()).toHaveLength(3);
+ expect(findAllHeaders()).toHaveLength(4);
});
it('shows the list of failed jobs', () => {
@@ -141,4 +150,36 @@ describe('PipelineFailedJobsWidget component', () => {
expect(createAlert).toHaveBeenCalledWith({ message: errorMessage, variant: 'danger' });
});
});
+
+ describe('when `refetch-jobs` job is fired from the widget', () => {
+ beforeEach(async () => {
+ mockFailedJobsResponse.mockResolvedValueOnce(failedJobsMock);
+ mockFailedJobsResponse.mockResolvedValueOnce(failedJobsMock2);
+
+ createComponent();
+
+ await findFailedJobsButton().vm.$emit('click');
+ await waitForPromises();
+ });
+
+ it('refetches all failed jobs', async () => {
+ expect(findFailedJobRows()).not.toHaveLength(
+ failedJobsMock2.data.project.pipeline.jobs.nodes.length,
+ );
+
+ await findFailedJobRows().at(0).vm.$emit('job-retried', 'job-name');
+ await waitForPromises();
+
+ expect(findFailedJobRows()).toHaveLength(
+ failedJobsMock2.data.project.pipeline.jobs.nodes.length,
+ );
+ });
+
+ it('shows a toast message', async () => {
+ await findFailedJobRows().at(0).vm.$emit('job-retried', 'job-name');
+ await waitForPromises();
+
+ expect(showToast).toHaveBeenCalledWith('job-name job is being retried');
+ });
+ });
});
diff --git a/spec/frontend/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row_spec.js b/spec/frontend/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row_spec.js
index dfc2806840f..a15edcc1fe8 100644
--- a/spec/frontend/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row_spec.js
@@ -1,36 +1,46 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import { GlIcon, GlLink } from '@gitlab/ui';
+import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import WidgetFailedJobRow from '~/pipelines/components/pipelines_list/failure_widget/widget_failed_job_row.vue';
+import RetryMrFailedJobMutation from '~/pipelines/graphql/mutations/retry_mr_failed_job.mutation.graphql';
+import { job } from './mock';
+
+Vue.use(VueApollo);
+jest.mock('~/alert');
+
+const createFakeEvent = () => ({ stopPropagation: jest.fn() });
describe('WidgetFailedJobRow component', () => {
let wrapper;
+ let mockRetryResponse;
- const defaultProps = {
- job: {
- id: 'gid://gitlab/Ci::Build/5240',
- detailedStatus: {
- group: 'running',
- icon: 'icon_status_running',
- },
- name: 'my-job',
- stage: {
- name: 'build',
- },
- trace: {
- htmlSummary: '<h1>job log</h1>',
+ const retrySuccessResponse = {
+ data: {
+ jobRetry: {
+ errors: [],
},
- webpath: '/',
},
};
+ const defaultProps = {
+ job,
+ };
+
const createComponent = ({ props = {} } = {}) => {
+ const handlers = [[RetryMrFailedJobMutation, mockRetryResponse]];
+
wrapper = shallowMountExtended(WidgetFailedJobRow, {
propsData: {
...defaultProps,
...props,
},
+ apolloProvider: createMockApollo(handlers),
});
};
@@ -40,9 +50,15 @@ describe('WidgetFailedJobRow component', () => {
const findHiddenJobLog = () => wrapper.findByTestId('log-is-hidden');
const findVisibleJobLog = () => wrapper.findByTestId('log-is-visible');
const findJobName = () => wrapper.findByText(defaultProps.job.name);
+ const findRetryButton = () => wrapper.findByLabelText('Retry');
const findRow = () => wrapper.findByTestId('widget-row');
const findStageName = () => wrapper.findByText(defaultProps.job.stage.name);
+ beforeEach(() => {
+ mockRetryResponse = jest.fn();
+ mockRetryResponse.mockResolvedValue(retrySuccessResponse);
+ });
+
describe('ui', () => {
beforeEach(() => {
createComponent();
@@ -77,63 +93,169 @@ describe('WidgetFailedJobRow component', () => {
});
});
- describe('Job log', () => {
- beforeEach(() => {
- createComponent();
- });
+ describe('Retry action', () => {
+ describe('when the job is not retryable', () => {
+ beforeEach(() => {
+ createComponent({ props: { job: { ...job, retryable: false } } });
+ });
- describe('when clicking on the row', () => {
- beforeEach(async () => {
- await findRow().trigger('click');
+ it('disables the retry button', () => {
+ expect(findRetryButton().props().disabled).toBe(true);
});
+ });
- describe('while collapsed', () => {
- it('expands the job log', () => {
- expect(findHiddenJobLog().exists()).toBe(false);
- expect(findVisibleJobLog().exists()).toBe(true);
+ describe('when the job is retryable', () => {
+ describe('and user has permission to update the build', () => {
+ beforeEach(() => {
+ createComponent();
});
- it('renders the down arrow', () => {
- expect(findArrowIcon().props().name).toBe('chevron-down');
+ it('enables the retry button', () => {
+ expect(findRetryButton().props().disabled).toBe(false);
});
- it('renders the received html', () => {
- expect(findVisibleJobLog().html()).toContain(defaultProps.job.trace.htmlSummary);
+ describe('when clicking on the retry button', () => {
+ it('passes the loading state to the button', async () => {
+ await findRetryButton().vm.$emit('click', createFakeEvent());
+
+ expect(findRetryButton().props().loading).toBe(true);
+ });
+
+ describe('and it succeeds', () => {
+ beforeEach(async () => {
+ findRetryButton().vm.$emit('click', createFakeEvent());
+ await waitForPromises();
+ });
+
+ it('is no longer loading', () => {
+ expect(findRetryButton().props().loading).toBe(false);
+ });
+
+ it('calls the retry mutation', () => {
+ expect(mockRetryResponse).toHaveBeenCalled();
+ expect(mockRetryResponse).toHaveBeenCalledWith({
+ id: job.id,
+ });
+ });
+
+ it('emits the `retried-job` event', () => {
+ expect(wrapper.emitted('job-retried')).toStrictEqual([[job.name]]);
+ });
+ });
+
+ describe('and it fails', () => {
+ const customErrorMsg = 'Custom error message from API';
+
+ beforeEach(async () => {
+ mockRetryResponse.mockResolvedValue({
+ data: { jobRetry: { errors: [customErrorMsg] } },
+ });
+ findRetryButton().vm.$emit('click', createFakeEvent());
+
+ await waitForPromises();
+ });
+
+ it('shows an error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({ message: customErrorMsg });
+ });
+
+ it('does not emits the `refetch-jobs` event', () => {
+ expect(wrapper.emitted('refetch-jobs')).toBeUndefined();
+ });
+ });
});
});
- describe('while expanded', () => {
- it('collapes the job log', async () => {
- expect(findHiddenJobLog().exists()).toBe(false);
- expect(findVisibleJobLog().exists()).toBe(true);
+ describe('and user does not have permission to update the build', () => {
+ beforeEach(() => {
+ createComponent({
+ props: { job: { ...job, retryable: true, userPermissions: { updateBuild: false } } },
+ });
+ });
+ it('disables the retry button', () => {
+ expect(findRetryButton().props().disabled).toBe(true);
+ });
+ });
+ });
+ });
+
+ describe('Job log', () => {
+ describe('without permissions', () => {
+ beforeEach(async () => {
+ createComponent({ props: { job: { ...job, userPermissions: { readBuild: false } } } });
+ await findRow().trigger('click');
+ });
+
+ it('does not renders the received html of the job log', () => {
+ expect(findVisibleJobLog().html()).not.toContain(defaultProps.job.trace.htmlSummary);
+ });
+
+ it('shows a permission error message', () => {
+ expect(findVisibleJobLog().text()).toBe(
+ "You do not have permission to read this job's log",
+ );
+ });
+ });
+
+ describe('with permissions', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ describe('when clicking on the row', () => {
+ beforeEach(async () => {
await findRow().trigger('click');
+ });
- expect(findHiddenJobLog().exists()).toBe(true);
- expect(findVisibleJobLog().exists()).toBe(false);
+ describe('while collapsed', () => {
+ it('expands the job log', () => {
+ expect(findHiddenJobLog().exists()).toBe(false);
+ expect(findVisibleJobLog().exists()).toBe(true);
+ });
+
+ it('renders the down arrow', () => {
+ expect(findArrowIcon().props().name).toBe('chevron-down');
+ });
+
+ it('renders the received html of the job log', () => {
+ expect(findVisibleJobLog().html()).toContain(defaultProps.job.trace.htmlSummary);
+ });
});
- it('renders the right arrow', async () => {
- expect(findArrowIcon().props().name).toBe('chevron-down');
+ describe('while expanded', () => {
+ it('collapes the job log', async () => {
+ expect(findHiddenJobLog().exists()).toBe(false);
+ expect(findVisibleJobLog().exists()).toBe(true);
- await findRow().trigger('click');
+ await findRow().trigger('click');
- expect(findArrowIcon().props().name).toBe('chevron-right');
+ expect(findHiddenJobLog().exists()).toBe(true);
+ expect(findVisibleJobLog().exists()).toBe(false);
+ });
+
+ it('renders the right arrow', async () => {
+ expect(findArrowIcon().props().name).toBe('chevron-down');
+
+ await findRow().trigger('click');
+
+ expect(findArrowIcon().props().name).toBe('chevron-right');
+ });
});
});
- });
- describe('when clicking on a link element within the row', () => {
- it('does not expands/collapse the job log', async () => {
- expect(findHiddenJobLog().exists()).toBe(true);
- expect(findVisibleJobLog().exists()).toBe(false);
- expect(findArrowIcon().props().name).toBe('chevron-right');
+ describe('when clicking on a link element within the row', () => {
+ it('does not expands/collapse the job log', async () => {
+ expect(findHiddenJobLog().exists()).toBe(true);
+ expect(findVisibleJobLog().exists()).toBe(false);
+ expect(findArrowIcon().props().name).toBe('chevron-right');
- await findJobId().vm.$emit('click');
+ await findJobId().vm.$emit('click');
- expect(findHiddenJobLog().exists()).toBe(true);
- expect(findVisibleJobLog().exists()).toBe(false);
- expect(findArrowIcon().props().name).toBe('chevron-right');
+ expect(findHiddenJobLog().exists()).toBe(true);
+ expect(findVisibleJobLog().exists()).toBe(false);
+ expect(findArrowIcon().props().name).toBe('chevron-right');
+ });
});
});
});