summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-09-24 09:06:04 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-09-24 09:06:04 +0000
commitbc89882970d6a14b1f72eb9c715fae90b26d066c (patch)
treef5cb59d5130d7585980eb39437071e07ebc12f87 /spec
parent4a45a787703cb78c6101750cfbdc9f656b934b42 (diff)
downloadgitlab-ce-bc89882970d6a14b1f72eb9c715fae90b26d066c.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js74
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/region_dropdown_spec.js55
-rw-r--r--spec/frontend/create_cluster/eks_cluster/store/actions_spec.js98
-rw-r--r--spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js40
-rw-r--r--spec/frontend/ide/components/jobs/list_spec.js115
-rw-r--r--spec/frontend/ide/components/pipelines/__snapshots__/list_spec.js.snap15
-rw-r--r--spec/frontend/ide/components/pipelines/list_spec.js193
-rw-r--r--spec/frontend/jobs/components/log/collapsible_section_spec.js29
-rw-r--r--spec/frontend/jobs/components/log/mock_data.js74
-rw-r--r--spec/frontend/jobs/store/utils_spec.js39
-rw-r--r--spec/javascripts/ide/components/jobs/list_spec.js67
-rw-r--r--spec/javascripts/ide/components/pipelines/list_spec.js137
-rw-r--r--spec/javascripts/ide/mock_data.js6
-rw-r--r--spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb44
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb3
-rw-r--r--spec/lib/gitlab/tracking_spec.rb4
16 files changed, 720 insertions, 273 deletions
diff --git a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
new file mode 100644
index 00000000000..5b04328bb78
--- /dev/null
+++ b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
@@ -0,0 +1,74 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import Vue from 'vue';
+import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
+import RegionDropdown from '~/create_cluster/eks_cluster/components/region_dropdown.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('EksClusterConfigurationForm', () => {
+ let store;
+ let actions;
+ let state;
+ let vm;
+
+ beforeEach(() => {
+ actions = {
+ fetchRegions: jest.fn(),
+ setRegion: jest.fn(),
+ };
+ state = {
+ regions: [{ name: 'region 1' }],
+ isLoadingRegions: false,
+ loadingRegionsError: { message: '' },
+ };
+ store = new Vuex.Store({
+ state,
+ actions,
+ });
+ });
+
+ beforeEach(() => {
+ vm = shallowMount(EksClusterConfigurationForm, {
+ localVue,
+ store,
+ });
+ });
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ const findRegionDropdown = () => vm.find(RegionDropdown);
+
+ describe('when mounted', () => {
+ it('fetches available regions', () => {
+ expect(actions.fetchRegions).toHaveBeenCalled();
+ });
+ });
+
+ it('sets isLoadingRegions to RegionDropdown loading property', () => {
+ state.isLoadingRegions = true;
+
+ return Vue.nextTick().then(() => {
+ expect(findRegionDropdown().props('loading')).toEqual(state.isLoadingRegions);
+ });
+ });
+
+ it('sets regions to RegionDropdown regions property', () => {
+ expect(findRegionDropdown().props('regions')).toEqual(state.regions);
+ });
+
+ it('sets loadingRegionsError to RegionDropdown error property', () => {
+ expect(findRegionDropdown().props('error')).toEqual(state.loadingRegionsError);
+ });
+
+ it('dispatches setRegion action when region is selected', () => {
+ const region = { region: 'us-west-2' };
+
+ findRegionDropdown().vm.$emit('input', region);
+
+ expect(actions.setRegion).toHaveBeenCalledWith(expect.anything(), { region }, undefined);
+ });
+});
diff --git a/spec/frontend/create_cluster/eks_cluster/components/region_dropdown_spec.js b/spec/frontend/create_cluster/eks_cluster/components/region_dropdown_spec.js
new file mode 100644
index 00000000000..0ebb5026a4b
--- /dev/null
+++ b/spec/frontend/create_cluster/eks_cluster/components/region_dropdown_spec.js
@@ -0,0 +1,55 @@
+import { shallowMount } from '@vue/test-utils';
+
+import ClusterFormDropdown from '~/create_cluster/eks_cluster/components/cluster_form_dropdown.vue';
+import RegionDropdown from '~/create_cluster/eks_cluster/components/region_dropdown.vue';
+
+describe('RegionDropdown', () => {
+ let vm;
+
+ const getClusterFormDropdown = () => vm.find(ClusterFormDropdown);
+
+ beforeEach(() => {
+ vm = shallowMount(RegionDropdown);
+ });
+ afterEach(() => vm.destroy());
+
+ it('renders a cluster-form-dropdown', () => {
+ expect(getClusterFormDropdown().exists()).toBe(true);
+ });
+
+ it('sets regions to cluster-form-dropdown items property', () => {
+ const regions = [{ name: 'basic' }];
+
+ vm.setProps({ regions });
+
+ expect(getClusterFormDropdown().props('items')).toEqual(regions);
+ });
+
+ it('sets a loading text', () => {
+ expect(getClusterFormDropdown().props('loadingText')).toEqual('Loading Regions');
+ });
+
+ it('sets a placeholder', () => {
+ expect(getClusterFormDropdown().props('placeholder')).toEqual('Select a region');
+ });
+
+ it('sets an empty results text', () => {
+ expect(getClusterFormDropdown().props('emptyText')).toEqual('No region found');
+ });
+
+ it('sets a search field placeholder', () => {
+ expect(getClusterFormDropdown().props('searchFieldPlaceholder')).toEqual('Search regions');
+ });
+
+ it('sets hasErrors property', () => {
+ vm.setProps({ error: {} });
+
+ expect(getClusterFormDropdown().props('hasErrors')).toEqual(true);
+ });
+
+ it('sets an error message', () => {
+ expect(getClusterFormDropdown().props('errorMessage')).toEqual(
+ 'Could not load regions from your AWS account',
+ );
+ });
+});
diff --git a/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
new file mode 100644
index 00000000000..9a3970813ed
--- /dev/null
+++ b/spec/frontend/create_cluster/eks_cluster/store/actions_spec.js
@@ -0,0 +1,98 @@
+import testAction from 'helpers/vuex_action_helper';
+
+import * as awsServicesFacade from '~/create_cluster/eks_cluster/services/aws_services_facade';
+import createState from '~/create_cluster/eks_cluster/store/state';
+import * as types from '~/create_cluster/eks_cluster/store/mutation_types';
+import * as actions from '~/create_cluster/eks_cluster/store/actions';
+
+describe('EKS Cluster Store Actions', () => {
+ const regions = [{ name: 'region 1' }];
+
+ describe('fetchRegions', () => {
+ describe('on success', () => {
+ beforeEach(() => {
+ jest.spyOn(awsServicesFacade, 'fetchRegions').mockResolvedValueOnce(regions);
+ });
+
+ it('dispatches success with received regions', () =>
+ testAction(
+ actions.fetchRegions,
+ null,
+ createState(),
+ [],
+ [
+ { type: 'requestRegions' },
+ {
+ type: 'receiveRegionsSuccess',
+ payload: { regions },
+ },
+ ],
+ ));
+ });
+
+ describe('on failure', () => {
+ const error = new Error('Could not fetch regions');
+
+ beforeEach(() => {
+ jest.spyOn(awsServicesFacade, 'fetchRegions').mockRejectedValueOnce(error);
+ });
+
+ it('dispatches success with received regions', () =>
+ testAction(
+ actions.fetchRegions,
+ null,
+ createState(),
+ [],
+ [
+ { type: 'requestRegions' },
+ {
+ type: 'receiveRegionsError',
+ payload: { error },
+ },
+ ],
+ ));
+ });
+ });
+
+ describe('requestRegions', () => {
+ it(`commits ${types.REQUEST_REGIONS} mutation`, () =>
+ testAction(actions.requestRegions, null, createState(), [{ type: types.REQUEST_REGIONS }]));
+ });
+
+ describe('receiveRegionsSuccess', () => {
+ it(`commits ${types.RECEIVE_REGIONS_SUCCESS} mutation`, () =>
+ testAction(actions.receiveRegionsSuccess, { regions }, createState(), [
+ {
+ type: types.RECEIVE_REGIONS_SUCCESS,
+ payload: {
+ regions,
+ },
+ },
+ ]));
+ });
+
+ describe('receiveRegionsError', () => {
+ it(`commits ${types.RECEIVE_REGIONS_ERROR} mutation`, () => {
+ const error = new Error('Error fetching regions');
+
+ testAction(actions.receiveRegionsError, { error }, createState(), [
+ {
+ type: types.RECEIVE_REGIONS_ERROR,
+ payload: {
+ error,
+ },
+ },
+ ]);
+ });
+ });
+
+ describe('setRegion', () => {
+ it(`commits ${types.SET_REGION} mutation`, () => {
+ const region = { name: 'west-1' };
+
+ testAction(actions.setRegion, { region }, createState(), [
+ { type: types.SET_REGION, payload: { region } },
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js
new file mode 100644
index 00000000000..f2d48635f8c
--- /dev/null
+++ b/spec/frontend/create_cluster/eks_cluster/store/mutations_spec.js
@@ -0,0 +1,40 @@
+import {
+ REQUEST_REGIONS,
+ RECEIVE_REGIONS_ERROR,
+ RECEIVE_REGIONS_SUCCESS,
+ SET_REGION,
+} from '~/create_cluster/eks_cluster/store/mutation_types';
+import createState from '~/create_cluster/eks_cluster/store/state';
+import mutations from '~/create_cluster/eks_cluster/store/mutations';
+
+describe('Create EKS cluster store mutations', () => {
+ let state;
+ let emptyPayload;
+ let regions;
+ let region;
+ let error;
+
+ beforeEach(() => {
+ emptyPayload = {};
+ region = { name: 'regions-1' };
+ regions = [region];
+ error = new Error('could not load error');
+ state = createState();
+ });
+
+ it.each`
+ mutation | mutatedProperty | payload | expectedValue | expectedValueDescription
+ ${REQUEST_REGIONS} | ${'isLoadingRegions'} | ${emptyPayload} | ${true} | ${true}
+ ${REQUEST_REGIONS} | ${'loadingRegionsError'} | ${emptyPayload} | ${null} | ${null}
+ ${RECEIVE_REGIONS_SUCCESS} | ${'isLoadingRegions'} | ${{ regions }} | ${false} | ${false}
+ ${RECEIVE_REGIONS_SUCCESS} | ${'regions'} | ${{ regions }} | ${regions} | ${'regions payload'}
+ ${RECEIVE_REGIONS_ERROR} | ${'isLoadingRegions'} | ${{ error }} | ${false} | ${false}
+ ${RECEIVE_REGIONS_ERROR} | ${'error'} | ${{ error }} | ${error} | ${'received error object'}
+ ${SET_REGION} | ${'selectedRegion'} | ${{ region }} | ${region} | ${'selected region payload'}
+ `(`$mutation sets $mutatedProperty to $expectedValueDescription`, data => {
+ const { mutation, mutatedProperty, payload, expectedValue } = data;
+
+ mutations[mutation](state, payload);
+ expect(state[mutatedProperty]).toBe(expectedValue);
+ });
+});
diff --git a/spec/frontend/ide/components/jobs/list_spec.js b/spec/frontend/ide/components/jobs/list_spec.js
new file mode 100644
index 00000000000..ec2e5b05048
--- /dev/null
+++ b/spec/frontend/ide/components/jobs/list_spec.js
@@ -0,0 +1,115 @@
+import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import Vuex from 'vuex';
+import StageList from '~/ide/components/jobs/list.vue';
+import Stage from '~/ide/components/jobs/stage.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+const storeActions = {
+ fetchJobs: jest.fn(),
+ toggleStageCollapsed: jest.fn(),
+ setDetailJob: jest.fn(),
+};
+
+const store = new Vuex.Store({
+ modules: {
+ pipelines: {
+ namespaced: true,
+ actions: storeActions,
+ },
+ },
+});
+
+describe('IDE stages list', () => {
+ let wrapper;
+
+ const defaultProps = {
+ stages: [],
+ loading: false,
+ };
+
+ const stages = ['build', 'test', 'deploy'].map((name, id) => ({
+ id,
+ name,
+ jobs: [],
+ status: { icon: 'status_success' },
+ }));
+
+ const createComponent = props => {
+ wrapper = shallowMount(StageList, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ localVue,
+ store,
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ Object.values(storeActions).forEach(actionMock => actionMock.mockClear());
+ });
+
+ afterAll(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders loading icon when no stages & loading', () => {
+ createComponent({ loading: true, stages: [] });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('renders stages components for each stage', () => {
+ createComponent({ stages });
+ expect(wrapper.findAll(Stage).length).toBe(stages.length);
+ });
+
+ it('triggers fetchJobs action when stage emits fetch event', () => {
+ createComponent({ stages });
+ wrapper.find(Stage).vm.$emit('fetch');
+ expect(storeActions.fetchJobs).toHaveBeenCalled();
+ });
+
+ it('triggers toggleStageCollapsed action when stage emits toggleCollapsed event', () => {
+ createComponent({ stages });
+ wrapper.find(Stage).vm.$emit('toggleCollapsed');
+ expect(storeActions.toggleStageCollapsed).toHaveBeenCalled();
+ });
+
+ it('triggers setDetailJob action when stage emits clickViewLog event', () => {
+ createComponent({ stages });
+ wrapper.find(Stage).vm.$emit('clickViewLog');
+ expect(storeActions.setDetailJob).toHaveBeenCalled();
+ });
+
+ describe('integration tests', () => {
+ const findCardHeader = () => wrapper.find('.card-header');
+
+ beforeEach(() => {
+ wrapper = mount(StageList, {
+ propsData: { ...defaultProps, stages },
+ store,
+ sync: false,
+ localVue,
+ });
+ });
+
+ it('calls toggleStageCollapsed when clicking stage header', () => {
+ findCardHeader().trigger('click');
+
+ expect(storeActions.toggleStageCollapsed).toHaveBeenCalledWith(
+ expect.any(Object),
+ 0,
+ undefined,
+ );
+ });
+
+ it('calls fetchJobs when stage is mounted', () => {
+ expect(storeActions.fetchJobs.mock.calls.map(([, stage]) => stage)).toEqual(stages);
+ });
+ });
+});
diff --git a/spec/frontend/ide/components/pipelines/__snapshots__/list_spec.js.snap b/spec/frontend/ide/components/pipelines/__snapshots__/list_spec.js.snap
new file mode 100644
index 00000000000..5fbe6af750d
--- /dev/null
+++ b/spec/frontend/ide/components/pipelines/__snapshots__/list_spec.js.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`IDE pipelines list when loaded renders empty state when no latestPipeline 1`] = `
+<div
+ class="ide-pipeline"
+>
+ <!---->
+
+ <emptystate-stub
+ cansetci="true"
+ emptystatesvgpath="http://test.host"
+ helppagepath="http://test.host"
+ />
+</div>
+`;
diff --git a/spec/frontend/ide/components/pipelines/list_spec.js b/spec/frontend/ide/components/pipelines/list_spec.js
new file mode 100644
index 00000000000..a974139a8f9
--- /dev/null
+++ b/spec/frontend/ide/components/pipelines/list_spec.js
@@ -0,0 +1,193 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import List from '~/ide/components/pipelines/list.vue';
+import JobsList from '~/ide/components/jobs/list.vue';
+import Tab from '~/vue_shared/components/tabs/tab.vue';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { TEST_HOST } from 'helpers/test_constants';
+import { pipelines } from '../../../../javascripts/ide/mock_data';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('IDE pipelines list', () => {
+ let wrapper;
+
+ const defaultState = {
+ links: { ciHelpPagePath: TEST_HOST },
+ pipelinesEmptyStateSvgPath: TEST_HOST,
+ pipelines: {
+ stages: [],
+ failedStages: [],
+ isLoadingJobs: false,
+ },
+ };
+
+ const fetchLatestPipelineMock = jest.fn();
+ const failedStagesGetterMock = jest.fn().mockReturnValue([]);
+
+ const createComponent = (state = {}) => {
+ const { pipelines: pipelinesState, ...restOfState } = state;
+ const { defaultPipelines, ...defaultRestOfState } = defaultState;
+
+ const fakeStore = new Vuex.Store({
+ getters: { currentProject: () => ({ web_url: 'some/url ' }) },
+ state: {
+ ...defaultRestOfState,
+ ...restOfState,
+ },
+ modules: {
+ pipelines: {
+ namespaced: true,
+ state: {
+ ...defaultPipelines,
+ ...pipelinesState,
+ },
+ actions: {
+ fetchLatestPipeline: fetchLatestPipelineMock,
+ },
+ getters: {
+ jobsCount: () => 1,
+ failedJobsCount: () => 1,
+ failedStages: failedStagesGetterMock,
+ pipelineFailed: () => false,
+ },
+ methods: {
+ fetchLatestPipeline: jest.fn(),
+ },
+ },
+ },
+ });
+
+ wrapper = shallowMount(List, {
+ localVue,
+ store: fakeStore,
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('fetches latest pipeline', () => {
+ createComponent();
+
+ expect(fetchLatestPipelineMock).toHaveBeenCalled();
+ });
+
+ describe('when loading', () => {
+ let defaultPipelinesLoadingState;
+ beforeAll(() => {
+ defaultPipelinesLoadingState = {
+ ...defaultState.pipelines,
+ isLoadingPipeline: true,
+ };
+ });
+
+ it('does not render when pipeline has loaded before', () => {
+ createComponent({
+ pipelines: {
+ ...defaultPipelinesLoadingState,
+ hasLoadedPipeline: true,
+ },
+ });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ });
+
+ it('renders loading state', () => {
+ createComponent({
+ pipelines: {
+ ...defaultPipelinesLoadingState,
+ hasLoadedPipeline: false,
+ },
+ });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+ });
+
+ describe('when loaded', () => {
+ let defaultPipelinesLoadedState;
+ beforeAll(() => {
+ defaultPipelinesLoadedState = {
+ ...defaultState.pipelines,
+ isLoadingPipeline: false,
+ hasLoadedPipeline: true,
+ };
+ });
+
+ it('renders empty state when no latestPipeline', () => {
+ createComponent({ pipelines: { ...defaultPipelinesLoadedState, latestPipeline: null } });
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('with latest pipeline loaded', () => {
+ let withLatestPipelineState;
+ beforeAll(() => {
+ withLatestPipelineState = {
+ ...defaultPipelinesLoadedState,
+ latestPipeline: pipelines[0],
+ };
+ });
+
+ it('renders ci icon', () => {
+ createComponent({ pipelines: withLatestPipelineState });
+ expect(wrapper.find(CiIcon).exists()).toBe(true);
+ });
+
+ it('renders pipeline data', () => {
+ createComponent({ pipelines: withLatestPipelineState });
+
+ expect(wrapper.text()).toContain('#1');
+ });
+
+ it('renders list of jobs', () => {
+ const stages = [];
+ const isLoadingJobs = true;
+ createComponent({ pipelines: { ...withLatestPipelineState, stages, isLoadingJobs } });
+
+ const jobProps = wrapper
+ .findAll(Tab)
+ .at(0)
+ .find(JobsList)
+ .props();
+ expect(jobProps.stages).toBe(stages);
+ expect(jobProps.loading).toBe(isLoadingJobs);
+ });
+
+ it('renders list of failed jobs', () => {
+ const failedStages = [];
+ failedStagesGetterMock.mockReset().mockReturnValue(failedStages);
+ const isLoadingJobs = true;
+ createComponent({ pipelines: { ...withLatestPipelineState, isLoadingJobs } });
+
+ const jobProps = wrapper
+ .findAll(Tab)
+ .at(1)
+ .find(JobsList)
+ .props();
+ expect(jobProps.stages).toBe(failedStages);
+ expect(jobProps.loading).toBe(isLoadingJobs);
+ });
+
+ describe('with YAML error', () => {
+ it('renders YAML error', () => {
+ const yamlError = 'test yaml error';
+ createComponent({
+ pipelines: {
+ ...defaultPipelinesLoadedState,
+ latestPipeline: { ...pipelines[0], yamlError },
+ },
+ });
+
+ expect(wrapper.text()).toContain('Found errors in your .gitlab-ci.yml:');
+ expect(wrapper.text()).toContain(yamlError);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/jobs/components/log/collapsible_section_spec.js b/spec/frontend/jobs/components/log/collapsible_section_spec.js
index 6c1ebf0a7c1..01184a51193 100644
--- a/spec/frontend/jobs/components/log/collapsible_section_spec.js
+++ b/spec/frontend/jobs/components/log/collapsible_section_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import CollpasibleSection from '~/jobs/components/log/collapsible_section.vue';
-import { nestedSectionOpened, nestedSectionClosed } from './mock_data';
+import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data';
describe('Job Log Collapsible Section', () => {
let wrapper;
@@ -8,6 +8,7 @@ describe('Job Log Collapsible Section', () => {
const traceEndpoint = 'jobs/335';
const findCollapsibleLine = () => wrapper.find('.collapsible-line');
+ const findCollapsibleLineSvg = () => wrapper.find('.collapsible-line svg');
const createComponent = (props = {}) => {
wrapper = mount(CollpasibleSection, {
@@ -22,10 +23,10 @@ describe('Job Log Collapsible Section', () => {
wrapper.destroy();
});
- describe('with closed nested section', () => {
+ describe('with closed section', () => {
beforeEach(() => {
createComponent({
- section: nestedSectionClosed,
+ section: collapsibleSectionClosed,
traceEndpoint,
});
});
@@ -33,24 +34,36 @@ describe('Job Log Collapsible Section', () => {
it('renders clickable header line', () => {
expect(findCollapsibleLine().attributes('role')).toBe('button');
});
+
+ it('renders an icon with the closed state', () => {
+ expect(findCollapsibleLineSvg().classes()).toContain('ic-angle-right');
+ });
});
- describe('with opened nested section', () => {
+ describe('with opened section', () => {
beforeEach(() => {
createComponent({
- section: nestedSectionOpened,
+ section: collapsibleSectionOpened,
traceEndpoint,
});
});
- it('renders all sections opened', () => {
- expect(wrapper.findAll('.collapsible-line').length).toBe(2);
+ it('renders clickable header line', () => {
+ expect(findCollapsibleLine().attributes('role')).toBe('button');
+ });
+
+ it('renders an icon with the open state', () => {
+ expect(findCollapsibleLineSvg().classes()).toContain('ic-angle-down');
+ });
+
+ it('renders collapsible lines content', () => {
+ expect(wrapper.findAll('.js-line').length).toEqual(collapsibleSectionOpened.lines.length);
});
});
it('emits onClickCollapsibleLine on click', () => {
createComponent({
- section: nestedSectionOpened,
+ section: collapsibleSectionOpened,
traceEndpoint,
});
diff --git a/spec/frontend/jobs/components/log/mock_data.js b/spec/frontend/jobs/components/log/mock_data.js
index 0dae306dcc7..d375d82d3ca 100644
--- a/spec/frontend/jobs/components/log/mock_data.js
+++ b/spec/frontend/jobs/components/log/mock_data.js
@@ -14,13 +14,13 @@ export const jobLog = [
text: 'Using Docker executor with image dev.gitlab.org3',
},
],
- sections: ['prepare-executor'],
+ section: 'prepare-executor',
section_header: true,
},
{
offset: 1003,
content: [{ text: 'Starting service postgres:9.6.14 ...', style: 'text-green' }],
- sections: ['prepare-executor'],
+ section: 'prepare-executor',
},
];
@@ -37,23 +37,23 @@ export const utilsMockData = [
'Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33',
},
],
- sections: ['prepare-executor'],
+ section: 'prepare-executor',
section_header: true,
},
{
offset: 1003,
content: [{ text: 'Starting service postgres:9.6.14 ...' }],
- sections: ['prepare-executor'],
+ section: 'prepare-executor',
},
{
offset: 1004,
content: [{ text: 'Pulling docker image postgres:9.6.14 ...', style: 'term-fg-l-green' }],
- sections: ['prepare-executor'],
+ section: 'prepare-executor',
},
{
offset: 1005,
content: [],
- sections: ['prepare-executor'],
+ section: 'prepare-executor',
section_duration: '10:00',
},
];
@@ -100,7 +100,7 @@ export const headerTrace = [
text: 'log line',
},
],
- sections: ['section'],
+ section: 'section',
},
];
@@ -113,7 +113,7 @@ export const headerTraceIncremental = [
text: 'updated log line',
},
],
- sections: ['section'],
+ section: 'section',
},
];
@@ -126,7 +126,7 @@ export const collapsibleTrace = [
text: 'log line',
},
],
- sections: ['section'],
+ section: 'section',
},
{
offset: 2,
@@ -135,7 +135,7 @@ export const collapsibleTrace = [
text: 'log line',
},
],
- sections: ['section'],
+ section: 'section',
},
];
@@ -147,76 +147,48 @@ export const collapsibleTraceIncremental = [
text: 'updated log line',
},
],
- sections: ['section'],
+ section: 'section',
},
];
-export const nestedSectionClosed = {
+export const collapsibleSectionClosed = {
offset: 5,
section_header: true,
isHeader: true,
isClosed: true,
line: {
content: [{ text: 'foo' }],
- sections: ['prepare-script'],
+ section: 'prepare-script',
lineNumber: 1,
},
section_duration: '00:03',
lines: [
{
- section_header: true,
- section_duration: '00:02',
- isHeader: true,
- isClosed: true,
- line: {
- offset: 52,
- content: [{ text: 'bar' }],
- sections: ['prepare-script', 'prepare-script-nested'],
- lineNumber: 2,
- },
- lines: [
- {
- offset: 80,
- content: [{ text: 'this is a collapsible nested section' }],
- sections: ['prepare-script', 'prepare-script-nested'],
- lineNumber: 3,
- },
- ],
+ offset: 80,
+ content: [{ text: 'this is a collapsible nested section' }],
+ section: 'prepare-script',
+ lineNumber: 3,
},
],
};
-export const nestedSectionOpened = {
+export const collapsibleSectionOpened = {
offset: 5,
section_header: true,
isHeader: true,
isClosed: false,
line: {
content: [{ text: 'foo' }],
- sections: ['prepare-script'],
+ section: 'prepare-script',
lineNumber: 1,
},
section_duration: '00:03',
lines: [
{
- section_header: true,
- section_duration: '00:02',
- isHeader: true,
- isClosed: false,
- line: {
- offset: 52,
- content: [{ text: 'bar' }],
- sections: ['prepare-script', 'prepare-script-nested'],
- lineNumber: 2,
- },
- lines: [
- {
- offset: 80,
- content: [{ text: 'this is a collapsible nested section' }],
- sections: ['prepare-script', 'prepare-script-nested'],
- lineNumber: 3,
- },
- ],
+ offset: 80,
+ content: [{ text: 'this is a collapsible nested section' }],
+ section: 'prepare-script',
+ lineNumber: 3,
},
],
};
diff --git a/spec/frontend/jobs/store/utils_spec.js b/spec/frontend/jobs/store/utils_spec.js
index 780d42fd6a1..1e885b6b788 100644
--- a/spec/frontend/jobs/store/utils_spec.js
+++ b/spec/frontend/jobs/store/utils_spec.js
@@ -1,4 +1,9 @@
-import { logLinesParser, updateIncrementalTrace } from '~/jobs/store/utils';
+import {
+ logLinesParser,
+ updateIncrementalTrace,
+ parseHeaderLine,
+ parseLine,
+} from '~/jobs/store/utils';
import {
utilsMockData,
originalTrace,
@@ -11,6 +16,32 @@ import {
} from '../components/log/mock_data';
describe('Jobs Store Utils', () => {
+ describe('parseHeaderLine', () => {
+ it('returns a new object with the header keys and the provided line parsed', () => {
+ const headerLine = { content: [{ text: 'foo' }] };
+ const parsedHeaderLine = parseHeaderLine(headerLine, 2);
+
+ expect(parsedHeaderLine).toEqual({
+ isClosed: true,
+ isHeader: true,
+ line: {
+ ...headerLine,
+ lineNumber: 2,
+ },
+ lines: [],
+ });
+ });
+ });
+
+ describe('parseLine', () => {
+ it('returns a new object with the lineNumber key added to the provided line object', () => {
+ const line = { content: [{ text: 'foo' }] };
+ const parsed = parseLine(line, 1);
+ expect(parsed.content).toEqual(line.content);
+ expect(parsed.lineNumber).toEqual(1);
+ });
+ });
+
describe('logLinesParser', () => {
let result;
@@ -117,7 +148,7 @@ describe('Jobs Store Utils', () => {
text: 'updated log line',
},
],
- sections: ['section'],
+ section: 'section',
lineNumber: 0,
},
lines: [],
@@ -147,7 +178,7 @@ describe('Jobs Store Utils', () => {
text: 'log line',
},
],
- sections: ['section'],
+ section: 'section',
lineNumber: 0,
},
lines: [
@@ -158,7 +189,7 @@ describe('Jobs Store Utils', () => {
text: 'updated log line',
},
],
- sections: ['section'],
+ section: 'section',
lineNumber: 1,
},
],
diff --git a/spec/javascripts/ide/components/jobs/list_spec.js b/spec/javascripts/ide/components/jobs/list_spec.js
deleted file mode 100644
index b24853c56fa..00000000000
--- a/spec/javascripts/ide/components/jobs/list_spec.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import Vue from 'vue';
-import StageList from '~/ide/components/jobs/list.vue';
-import { createStore } from '~/ide/stores';
-import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
-import { stages, jobs } from '../../mock_data';
-
-describe('IDE stages list', () => {
- const Component = Vue.extend(StageList);
- let vm;
-
- beforeEach(() => {
- const store = createStore();
-
- vm = createComponentWithStore(Component, store, {
- stages: stages.map((mappedState, i) => ({
- ...mappedState,
- id: i,
- dropdownPath: mappedState.dropdown_path,
- jobs: [...jobs],
- isLoading: false,
- isCollapsed: false,
- })),
- loading: false,
- });
-
- spyOn(vm, 'fetchJobs');
- spyOn(vm, 'toggleStageCollapsed');
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders list of stages', () => {
- expect(vm.$el.querySelectorAll('.card').length).toBe(2);
- });
-
- it('renders loading icon when no stages & is loading', done => {
- vm.stages = [];
- vm.loading = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
-
- done();
- });
- });
-
- it('calls toggleStageCollapsed when clicking stage header', done => {
- vm.$el.querySelector('.card-header').click();
-
- vm.$nextTick(() => {
- expect(vm.toggleStageCollapsed).toHaveBeenCalledWith(0);
-
- done();
- });
- });
-
- it('calls fetchJobs when stage is mounted', () => {
- expect(vm.fetchJobs.calls.count()).toBe(stages.length);
-
- expect(vm.fetchJobs.calls.argsFor(0)).toEqual([vm.stages[0]]);
- expect(vm.fetchJobs.calls.argsFor(1)).toEqual([vm.stages[1]]);
- });
-});
diff --git a/spec/javascripts/ide/components/pipelines/list_spec.js b/spec/javascripts/ide/components/pipelines/list_spec.js
deleted file mode 100644
index 80829f2358a..00000000000
--- a/spec/javascripts/ide/components/pipelines/list_spec.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import Vue from 'vue';
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import { createStore } from '~/ide/stores';
-import List from '~/ide/components/pipelines/list.vue';
-import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
-import { pipelines, projectData, stages, jobs } from '../../mock_data';
-
-describe('IDE pipelines list', () => {
- const Component = Vue.extend(List);
- let vm;
- let mock;
-
- const findLoadingState = () => vm.$el.querySelector('.loading-container');
-
- beforeEach(done => {
- const store = createStore();
-
- mock = new MockAdapter(axios);
-
- store.state.currentProjectId = 'abc/def';
- store.state.currentBranchId = 'master';
- store.state.projects['abc/def'] = {
- ...projectData,
- path_with_namespace: 'abc/def',
- branches: {
- master: { commit: { id: '123' } },
- },
- };
- store.state.links = { ciHelpPagePath: gl.TEST_HOST };
- store.state.pipelinesEmptyStateSvgPath = gl.TEST_HOST;
- store.state.pipelines.stages = stages.map((mappedState, i) => ({
- ...mappedState,
- id: i,
- dropdownPath: mappedState.dropdown_path,
- jobs: [...jobs],
- isLoading: false,
- isCollapsed: false,
- }));
-
- mock
- .onGet('/abc/def/commit/123/pipelines')
- .replyOnce(200, { pipelines: [...pipelines] }, { 'poll-interval': '-1' });
-
- vm = createComponentWithStore(Component, store).$mount();
-
- setTimeout(done);
- });
-
- afterEach(done => {
- vm.$destroy();
- mock.restore();
-
- vm.$store
- .dispatch('pipelines/stopPipelinePolling')
- .then(() => vm.$store.dispatch('pipelines/clearEtagPoll'))
- .then(done)
- .catch(done.fail);
- });
-
- it('renders pipeline data', () => {
- expect(vm.$el.textContent).toContain('#1');
- });
-
- it('renders CI icon', () => {
- expect(vm.$el.querySelector('.ci-status-icon-failed')).not.toBe(null);
- });
-
- it('renders list of jobs', () => {
- expect(vm.$el.querySelectorAll('.tab-pane:first-child .ide-job-item').length).toBe(
- jobs.length * stages.length,
- );
- });
-
- it('renders list of failed jobs on failed jobs tab', done => {
- vm.$el.querySelectorAll('.tab-links a')[1].click();
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelectorAll('.tab-pane.active .ide-job-item').length).toBe(2);
-
- done();
- });
- });
-
- describe('YAML error', () => {
- it('renders YAML error', done => {
- vm.$store.state.pipelines.latestPipeline.yamlError = 'test yaml error';
-
- vm.$nextTick(() => {
- expect(vm.$el.textContent).toContain('Found errors in your .gitlab-ci.yml:');
- expect(vm.$el.textContent).toContain('test yaml error');
-
- done();
- });
- });
- });
-
- describe('empty state', () => {
- it('renders pipelines empty state', done => {
- vm.$store.state.pipelines.latestPipeline = null;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.empty-state')).not.toBe(null);
-
- done();
- });
- });
- });
-
- describe('loading state', () => {
- beforeEach(() => {
- vm.$store.state.pipelines.isLoadingPipeline = true;
- });
-
- it('does not render when pipeline has loaded before', done => {
- vm.$store.state.pipelines.hasLoadedPipeline = true;
-
- vm.$nextTick()
- .then(() => {
- expect(findLoadingState()).toBe(null);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('renders loading state when there is no latest pipeline', done => {
- vm.$store.state.pipelines.hasLoadedPipeline = false;
-
- vm.$nextTick()
- .then(() => {
- expect(findLoadingState()).not.toBe(null);
- })
- .then(done)
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index c02c7e5d45e..1c2e082489e 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -1,3 +1,5 @@
+import { TEST_HOST } from '../test_constants';
+
export const projectData = {
id: 1,
name: 'abcproject',
@@ -50,7 +52,7 @@ export const pipelines = [
export const stages = [
{
- dropdown_path: `${gl.TEST_HOST}/testing`,
+ dropdown_path: `${TEST_HOST}/testing`,
name: 'build',
status: {
icon: 'status_failed',
@@ -163,7 +165,7 @@ export const mergeRequests = [
iid: 1,
title: 'Test merge request',
project_id: 1,
- web_url: `${gl.TEST_HOST}/namespace/project-path/merge_requests/1`,
+ web_url: `${TEST_HOST}/namespace/project-path/merge_requests/1`,
},
];
diff --git a/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
new file mode 100644
index 00000000000..24b6090cb19
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_logging/exception_handler_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqLogging::ExceptionHandler do
+ describe '#call' do
+ let(:job) do
+ {
+ "class" => "TestWorker",
+ "args" => [1234, 'hello'],
+ "retry" => false,
+ "queue" => "cronjob:test_queue",
+ "queue_namespace" => "cronjob",
+ "jid" => "da883554ee4fe414012f5f42",
+ "correlation_id" => 'cid'
+ }
+ end
+
+ let(:exception_message) { 'An error was thrown' }
+ let(:backtrace) { caller }
+ let(:exception) { RuntimeError.new(exception_message) }
+ let(:logger) { double }
+
+ before do
+ allow(Sidekiq).to receive(:logger).and_return(logger)
+ allow(exception).to receive(:backtrace).and_return(backtrace)
+ end
+
+ subject { described_class.new.call(exception, { context: 'Test', job: job }) }
+
+ it 'logs job data into root tree' do
+ expected_data = job.merge(
+ error_class: 'RuntimeError',
+ error_message: exception_message,
+ context: 'Test',
+ error_backtrace: Gitlab::Profiler.clean_backtrace(backtrace)
+ )
+
+ expect(logger).to receive(:warn).with(expected_data)
+
+ subject
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 1b89c094a6b..6e6a8e14fc9 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -47,7 +47,7 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
end_payload.merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: fail: 0.0 sec',
'job_status' => 'fail',
- 'error' => ArgumentError,
+ 'error_class' => 'ArgumentError',
'error_message' => 'some exception'
)
end
@@ -86,7 +86,6 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
it 'logs an exception in job' do
Timecop.freeze(timestamp) do
expect(logger).to receive(:info).with(start_payload)
- # This excludes the exception_backtrace
expect(logger).to receive(:warn).with(hash_including(exception_payload))
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index 3cce82e522b..6891349a1dc 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -49,8 +49,8 @@ describe Gitlab::Tracking do
it 'can track events' do
tracker = double
- expect(SnowplowTracker::Emitter).to receive(:new).with(
- 'gitfoo.com'
+ expect(SnowplowTracker::AsyncEmitter).to receive(:new).with(
+ 'gitfoo.com', { protocol: 'https' }
).and_return('_emitter_')
expect(SnowplowTracker::Tracker).to receive(:new).with(