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
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-16 18:10:18 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-16 18:10:18 +0300
commit6364c14cc1f445d471bca118dca5af5a85b2c5dc (patch)
tree2579c5592f207e86ff7a0c5c7499caad723cdec1 /spec
parent5a2284f3500088e04cf3a5854fb06dc9db2b6077 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/file_uploads/git_lfs_spec.rb2
-rw-r--r--spec/features/file_uploads/multipart_invalid_uploads_spec.rb2
-rw-r--r--spec/features/file_uploads/nuget_package_spec.rb2
-rw-r--r--spec/features/groups/navbar_spec.rb1
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_app_spec.js97
-rw-r--r--spec/frontend/pipelines/pipeline_graph/mock_data.js10
-rw-r--r--spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js148
-rw-r--r--spec/frontend/pipelines/pipeline_graph/utils_spec.js79
-rw-r--r--spec/frontend/pipelines/unwrapping_utils_spec.js24
-rw-r--r--spec/helpers/projects_helper_spec.rb1
-rw-r--r--spec/models/label_priority_spec.rb6
-rw-r--r--spec/models/sentry_issue_spec.rb6
-rw-r--r--spec/models/suggestion_spec.rb6
-rw-r--r--spec/models/timelog_spec.rb8
-rw-r--r--spec/models/zoom_meeting_spec.rb14
-rw-r--r--spec/requests/api/features_spec.rb12
-rw-r--r--spec/requests/api/projects_spec.rb7
-rw-r--r--spec/requests/ide_controller_spec.rb (renamed from spec/controllers/ide_controller_spec.rb)2
-rw-r--r--spec/rubocop/cop/rspec/htt_party_basic_auth_spec.rb42
-rw-r--r--spec/spec_helper.rb3
-rw-r--r--spec/support/shared_contexts/navbar_structure_context.rb26
-rw-r--r--spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb24
22 files changed, 398 insertions, 124 deletions
diff --git a/spec/features/file_uploads/git_lfs_spec.rb b/spec/features/file_uploads/git_lfs_spec.rb
index 3824c04ada5..239afb1a1bb 100644
--- a/spec/features/file_uploads/git_lfs_spec.rb
+++ b/spec/features/file_uploads/git_lfs_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe 'Upload a git lfs object', :js do
HTTParty.put(
url,
headers: headers,
- basic_auth: { user: user.username, password: personal_access_token.token },
+ basic_auth: { username: user.username, password: personal_access_token.token },
body: file.read
)
end
diff --git a/spec/features/file_uploads/multipart_invalid_uploads_spec.rb b/spec/features/file_uploads/multipart_invalid_uploads_spec.rb
index b3ace2e30ff..91c8e100e6a 100644
--- a/spec/features/file_uploads/multipart_invalid_uploads_spec.rb
+++ b/spec/features/file_uploads/multipart_invalid_uploads_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe 'Invalid uploads that must be rejected', :api, :js do
subject do
HTTParty.put(
url,
- basic_auth: { user: user.username, password: personal_access_token.token },
+ basic_auth: { username: user.username, password: personal_access_token.token },
body: body
)
end
diff --git a/spec/features/file_uploads/nuget_package_spec.rb b/spec/features/file_uploads/nuget_package_spec.rb
index fb1e0a54744..6e05e5d1a6e 100644
--- a/spec/features/file_uploads/nuget_package_spec.rb
+++ b/spec/features/file_uploads/nuget_package_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'Upload a nuget package', :api, :js do
subject do
HTTParty.put(
url,
- basic_auth: { user: user.username, password: personal_access_token.token },
+ basic_auth: { username: user.username, password: personal_access_token.token },
body: { package: file }
)
end
diff --git a/spec/features/groups/navbar_spec.rb b/spec/features/groups/navbar_spec.rb
index dec07eb3783..a4c450c9a2c 100644
--- a/spec/features/groups/navbar_spec.rb
+++ b/spec/features/groups/navbar_spec.rb
@@ -33,6 +33,7 @@ RSpec.describe 'Group navbar' do
nav_item: _('Merge Requests'),
nav_sub_items: []
},
+ (security_and_compliance_nav_item if Gitlab.ee?),
(push_rules_nav_item if Gitlab.ee?),
{
nav_item: _('Kubernetes'),
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index ca54c97d2bb..14d6b03645c 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -51,9 +51,15 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
const createComponent = ({
props = {},
- loading = false,
+ blobLoading = false,
+ lintLoading = false,
options = {},
mountFn = shallowMount,
+ provide = {
+ glFeatures: {
+ ciConfigVisualizationTab: true,
+ },
+ },
} = {}) => {
mockMutate = jest.fn().mockResolvedValue({
data: {
@@ -73,6 +79,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
newMergeRequestPath: mockNewMergeRequestPath,
...props,
},
+ provide,
stubs: {
GlTabs,
GlButton,
@@ -86,7 +93,10 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
$apollo: {
queries: {
content: {
- loading,
+ loading: blobLoading,
+ },
+ ciConfigData: {
+ loading: lintLoading,
},
},
mutate: mockMutate,
@@ -124,9 +134,12 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findAlert = () => wrapper.find(GlAlert);
+ const findBlobFailureAlert = () => wrapper.find(GlAlert);
const findTabAt = i => wrapper.findAll(GlTab).at(i);
+ const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]');
const findTextEditor = () => wrapper.find(TextEditor);
const findCommitForm = () => wrapper.find(CommitForm);
+ const findPipelineGraph = () => wrapper.find(PipelineGraph);
const findCommitBtnLoadingIcon = () => wrapper.find('[type="submit"]').find(GlLoadingIcon);
beforeEach(() => {
@@ -145,39 +158,65 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
wrapper = null;
});
- it('displays a loading icon if the query is loading', () => {
- createComponent({ loading: true });
+ it('displays a loading icon if the blob query is loading', () => {
+ createComponent({ blobLoading: true });
expect(findLoadingIcon().exists()).toBe(true);
expect(findTextEditor().exists()).toBe(false);
});
describe('tabs', () => {
- beforeEach(() => {
- createComponent();
- });
+ describe('editor tab', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('displays the tab and its content', async () => {
+ expect(
+ findTabAt(0)
+ .find(TextEditor)
+ .exists(),
+ ).toBe(true);
+ });
- it('displays tabs and their content', async () => {
- expect(
- findTabAt(0)
- .find(TextEditor)
- .exists(),
- ).toBe(true);
- expect(
- findTabAt(1)
- .find(PipelineGraph)
- .exists(),
- ).toBe(true);
+ it('displays tab lazily, until editor is ready', async () => {
+ expect(findTabAt(0).attributes('lazy')).toBe('true');
+
+ findTextEditor().vm.$emit('editor-ready');
+
+ await nextTick();
+
+ expect(findTabAt(0).attributes('lazy')).toBe(undefined);
+ });
});
- it('displays editor tab lazily, until editor is ready', async () => {
- expect(findTabAt(0).attributes('lazy')).toBe('true');
+ describe('visualization tab', () => {
+ describe('with feature flag on', () => {
+ beforeEach(() => {
+ createComponent();
+ });
- findTextEditor().vm.$emit('editor-ready');
+ it('display the tab', () => {
+ expect(findVisualizationTab().exists()).toBe(true);
+ });
+
+ it('displays a loading icon if the lint query is loading', () => {
+ createComponent({ lintLoading: true });
+
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
+ });
- await nextTick();
+ describe('with feature flag off', () => {
+ beforeEach(() => {
+ createComponent({ provide: { glFeatures: { ciConfigVisualizationTab: false } } });
+ });
- expect(findTabAt(0).attributes('lazy')).toBe(undefined);
+ it('does not display the tab', () => {
+ expect(findVisualizationTab().exists()).toBe(false);
+ });
+ });
});
});
@@ -359,7 +398,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
await waitForPromises();
- expect(findAlert().exists()).toBe(false);
+ expect(findBlobFailureAlert().exists()).toBe(false);
expect(findTextEditor().attributes('value')).toBe(mockCiYml);
});
@@ -373,7 +412,9 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
await waitForPromises();
- expect(findAlert().text()).toMatch('No CI file found in this repository, please add one.');
+ expect(findBlobFailureAlert().text()).toBe(
+ 'No CI file found in this repository, please add one.',
+ );
});
it('shows a 400 error message', async () => {
@@ -386,7 +427,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
await waitForPromises();
- expect(findAlert().text()).toMatch(
+ expect(findBlobFailureAlert().text()).toBe(
'Repository does not have a default branch, please set one.',
);
});
@@ -396,7 +437,9 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
createComponentWithApollo();
await waitForPromises();
- expect(findAlert().text()).toMatch('The CI configuration was not loaded, please try again.');
+ expect(findBlobFailureAlert().text()).toBe(
+ 'The CI configuration was not loaded, please try again.',
+ );
});
});
});
diff --git a/spec/frontend/pipelines/pipeline_graph/mock_data.js b/spec/frontend/pipelines/pipeline_graph/mock_data.js
index 4f55fdd6b28..a77973b293c 100644
--- a/spec/frontend/pipelines/pipeline_graph/mock_data.js
+++ b/spec/frontend/pipelines/pipeline_graph/mock_data.js
@@ -1,4 +1,4 @@
-import { createUniqueJobId } from '~/pipelines/utils';
+import { createUniqueLinkId } from '~/pipelines/utils';
export const yamlString = `stages:
- empty
@@ -41,10 +41,10 @@ deploy_a:
script: echo hello
`;
-const jobId1 = createUniqueJobId('build', 'build_1');
-const jobId2 = createUniqueJobId('test', 'test_1');
-const jobId3 = createUniqueJobId('test', 'test_2');
-const jobId4 = createUniqueJobId('deploy', 'deploy_1');
+const jobId1 = createUniqueLinkId('build', 'build_1');
+const jobId2 = createUniqueLinkId('test', 'test_1');
+const jobId3 = createUniqueLinkId('test', 'test_2');
+const jobId4 = createUniqueLinkId('deploy', 'deploy_1');
export const pipelineData = {
stages: [
diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
index 7c8ebc27974..6704ee06c1a 100644
--- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
@@ -1,5 +1,8 @@
import { shallowMount } from '@vue/test-utils';
+import { GlAlert } from '@gitlab/ui';
import { pipelineData, singleStageData } from './mock_data';
+import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
+import { DRAW_FAILURE, EMPTY_PIPELINE_DATA, INVALID_CI_CONFIG } from '~/pipelines/constants';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import StagePill from '~/pipelines/components/pipeline_graph/stage_pill.vue';
import JobPill from '~/pipelines/components/pipeline_graph/job_pill.vue';
@@ -8,15 +11,16 @@ describe('pipeline graph component', () => {
const defaultProps = { pipelineData };
let wrapper;
- const createComponent = props => {
+ const createComponent = (props = defaultProps) => {
return shallowMount(PipelineGraph, {
propsData: {
- ...defaultProps,
...props,
},
});
};
+ const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]');
+ const findAlert = () => wrapper.find(GlAlert);
const findAllStagePills = () => wrapper.findAll(StagePill);
const findAllStageBackgroundElements = () => wrapper.findAll('[data-testid="stage-background"]');
const findStageBackgroundElementAt = index => findAllStageBackgroundElements().at(index);
@@ -33,54 +37,92 @@ describe('pipeline graph component', () => {
});
it('renders an empty section', () => {
- expect(wrapper.text()).toContain(
- 'The visualization will appear in this tab when the CI/CD configuration file is populated with valid syntax.',
- );
+ expect(wrapper.text()).toBe(wrapper.vm.$options.warningTexts[EMPTY_PIPELINE_DATA]);
+ expect(findPipelineGraph().exists()).toBe(false);
expect(findAllStagePills()).toHaveLength(0);
expect(findAllJobPills()).toHaveLength(0);
});
});
- describe('with data', () => {
+ describe('with `INVALID` status', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ pipelineData: { status: CI_CONFIG_STATUS_INVALID } });
+ });
+
+ it('renders an error message and does not render the graph', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.warningTexts[INVALID_CI_CONFIG]);
+ expect(findPipelineGraph().exists()).toBe(false);
+ });
+ });
+
+ describe('without `INVALID` status', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ it('renders the graph with no status error', () => {
+ expect(findAlert().text()).not.toBe(wrapper.vm.$options.warningTexts[INVALID_CI_CONFIG]);
+ expect(findPipelineGraph().exists()).toBe(true);
+ });
+ });
+
+ describe('with error while rendering the links', () => {
beforeEach(() => {
wrapper = createComponent();
});
+ it('renders the error that link could not be drawn', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[DRAW_FAILURE]);
+ });
+ });
+
+ describe('with only one stage', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ pipelineData: singleStageData });
+ });
+
it('renders the right number of stage pills', () => {
- const expectedStagesLength = pipelineData.stages.length;
+ const expectedStagesLength = singleStageData.stages.length;
expect(findAllStagePills()).toHaveLength(expectedStagesLength);
});
- it.each`
- cssClass | expectedState
- ${'gl-rounded-bottom-left-6'} | ${true}
- ${'gl-rounded-top-left-6'} | ${true}
- ${'gl-rounded-top-right-6'} | ${false}
- ${'gl-rounded-bottom-right-6'} | ${false}
- `(
- 'rounds corner: $class should be $expectedState on the first element',
- ({ cssClass, expectedState }) => {
+ it('renders the right number of job pills', () => {
+ // We count the number of jobs in the mock data
+ const expectedJobsLength = singleStageData.stages.reduce((acc, val) => {
+ return acc + val.groups.length;
+ }, 0);
+
+ expect(findAllJobPills()).toHaveLength(expectedJobsLength);
+ });
+
+ describe('rounds corner', () => {
+ it.each`
+ cssClass | expectedState
+ ${'gl-rounded-bottom-left-6'} | ${true}
+ ${'gl-rounded-top-left-6'} | ${true}
+ ${'gl-rounded-top-right-6'} | ${true}
+ ${'gl-rounded-bottom-right-6'} | ${true}
+ `('$cssClass should be $expectedState on the only element', ({ cssClass, expectedState }) => {
const classes = findStageBackgroundElementAt(0).classes();
expect(classes.includes(cssClass)).toBe(expectedState);
- },
- );
-
- it.each`
- cssClass | expectedState
- ${'gl-rounded-bottom-left-6'} | ${false}
- ${'gl-rounded-top-left-6'} | ${false}
- ${'gl-rounded-top-right-6'} | ${true}
- ${'gl-rounded-bottom-right-6'} | ${true}
- `(
- 'rounds corner: $class should be $expectedState on the last element',
- ({ cssClass, expectedState }) => {
- const classes = findStageBackgroundElementAt(pipelineData.stages.length - 1).classes();
+ });
+ });
+ });
- expect(classes.includes(cssClass)).toBe(expectedState);
- },
- );
+ describe('with multiple stages and jobs', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ it('renders the right number of stage pills', () => {
+ const expectedStagesLength = pipelineData.stages.length;
+
+ expect(findAllStagePills()).toHaveLength(expectedStagesLength);
+ });
it('renders the right number of job pills', () => {
// We count the number of jobs in the mock data
@@ -90,26 +132,34 @@ describe('pipeline graph component', () => {
expect(findAllJobPills()).toHaveLength(expectedJobsLength);
});
- });
- describe('with only one stage', () => {
- beforeEach(() => {
- wrapper = createComponent({ pipelineData: singleStageData });
- });
+ describe('rounds corner', () => {
+ it.each`
+ cssClass | expectedState
+ ${'gl-rounded-bottom-left-6'} | ${true}
+ ${'gl-rounded-top-left-6'} | ${true}
+ ${'gl-rounded-top-right-6'} | ${false}
+ ${'gl-rounded-bottom-right-6'} | ${false}
+ `(
+ '$cssClass should be $expectedState on the first element',
+ ({ cssClass, expectedState }) => {
+ const classes = findStageBackgroundElementAt(0).classes();
+
+ expect(classes.includes(cssClass)).toBe(expectedState);
+ },
+ );
- it.each`
- cssClass | expectedState
- ${'gl-rounded-bottom-left-6'} | ${true}
- ${'gl-rounded-top-left-6'} | ${true}
- ${'gl-rounded-top-right-6'} | ${true}
- ${'gl-rounded-bottom-right-6'} | ${true}
- `(
- 'rounds corner: $class should be $expectedState on the only element',
- ({ cssClass, expectedState }) => {
- const classes = findStageBackgroundElementAt(0).classes();
+ it.each`
+ cssClass | expectedState
+ ${'gl-rounded-bottom-left-6'} | ${false}
+ ${'gl-rounded-top-left-6'} | ${false}
+ ${'gl-rounded-top-right-6'} | ${true}
+ ${'gl-rounded-bottom-right-6'} | ${true}
+ `('$cssClass should be $expectedState on the last element', ({ cssClass, expectedState }) => {
+ const classes = findStageBackgroundElementAt(pipelineData.stages.length - 1).classes();
expect(classes.includes(cssClass)).toBe(expectedState);
- },
- );
+ });
+ });
});
});
diff --git a/spec/frontend/pipelines/pipeline_graph/utils_spec.js b/spec/frontend/pipelines/pipeline_graph/utils_spec.js
index b073ad7647e..12154df6fcf 100644
--- a/spec/frontend/pipelines/pipeline_graph/utils_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/utils_spec.js
@@ -1,14 +1,24 @@
-import { createUniqueJobId, generateJobNeedsDict } from '~/pipelines/utils';
+import { createJobsHash, generateJobNeedsDict } from '~/pipelines/utils';
describe('utils functions', () => {
const jobName1 = 'build_1';
const jobName2 = 'build_2';
const jobName3 = 'test_1';
const jobName4 = 'deploy_1';
- const job1 = { script: 'echo hello', stage: 'build' };
- const job2 = { script: 'echo build', stage: 'build' };
- const job3 = { script: 'echo test', stage: 'test', needs: [jobName1, jobName2] };
- const job4 = { script: 'echo deploy', stage: 'deploy', needs: [jobName3] };
+ const job1 = { name: jobName1, script: 'echo hello', stage: 'build' };
+ const job2 = { name: jobName2, script: 'echo build', stage: 'build' };
+ const job3 = {
+ name: jobName3,
+ script: 'echo test',
+ stage: 'test',
+ needs: [jobName1, jobName2],
+ };
+ const job4 = {
+ name: jobName4,
+ script: 'echo deploy',
+ stage: 'deploy',
+ needs: [jobName3],
+ };
const userDefinedStage = 'myStage';
const pipelineGraphData = {
@@ -23,7 +33,6 @@ describe('utils functions', () => {
{
name: jobName4,
jobs: [{ ...job4 }],
- id: createUniqueJobId(job4.stage, jobName4),
},
],
},
@@ -33,12 +42,10 @@ describe('utils functions', () => {
{
name: jobName1,
jobs: [{ ...job1 }],
- id: createUniqueJobId(job1.stage, jobName1),
},
{
name: jobName2,
jobs: [{ ...job2 }],
- id: createUniqueJobId(job2.stage, jobName2),
},
],
},
@@ -48,49 +55,59 @@ describe('utils functions', () => {
{
name: jobName3,
jobs: [{ ...job3 }],
- id: createUniqueJobId(job3.stage, jobName3),
},
],
},
],
- jobs: {
- [jobName1]: { ...job1, id: createUniqueJobId(job1.stage, jobName1) },
- [jobName2]: { ...job2, id: createUniqueJobId(job2.stage, jobName2) },
- [jobName3]: { ...job3, id: createUniqueJobId(job3.stage, jobName3) },
- [jobName4]: { ...job4, id: createUniqueJobId(job4.stage, jobName4) },
- },
};
+ describe('createJobsHash', () => {
+ it('returns an empty object if there are no jobs received as argument', () => {
+ expect(createJobsHash([])).toEqual({});
+ });
+
+ it('returns a hash with the jobname as key and all its data as value', () => {
+ const jobs = {
+ [jobName1]: job1,
+ [jobName2]: job2,
+ [jobName3]: job3,
+ [jobName4]: job4,
+ };
+
+ expect(createJobsHash(pipelineGraphData.stages)).toEqual(jobs);
+ });
+ });
+
describe('generateJobNeedsDict', () => {
it('generates an empty object if it receives no jobs', () => {
- expect(generateJobNeedsDict({ jobs: {} })).toEqual({});
+ expect(generateJobNeedsDict({})).toEqual({});
});
it('generates a dict with empty needs if there are no dependencies', () => {
const smallGraph = {
- jobs: {
- [jobName1]: { ...job1, id: createUniqueJobId(job1.stage, jobName1) },
- [jobName2]: { ...job2, id: createUniqueJobId(job2.stage, jobName2) },
- },
+ [jobName1]: job1,
+ [jobName2]: job2,
};
expect(generateJobNeedsDict(smallGraph)).toEqual({
- [pipelineGraphData.jobs[jobName1].id]: [],
- [pipelineGraphData.jobs[jobName2].id]: [],
+ [jobName1]: [],
+ [jobName2]: [],
});
});
it('generates a dict where key is the a job and its value is an array of all its needs', () => {
- const uniqueJobName1 = pipelineGraphData.jobs[jobName1].id;
- const uniqueJobName2 = pipelineGraphData.jobs[jobName2].id;
- const uniqueJobName3 = pipelineGraphData.jobs[jobName3].id;
- const uniqueJobName4 = pipelineGraphData.jobs[jobName4].id;
+ const jobsWithNeeds = {
+ [jobName1]: job1,
+ [jobName2]: job2,
+ [jobName3]: job3,
+ [jobName4]: job4,
+ };
- expect(generateJobNeedsDict(pipelineGraphData)).toEqual({
- [uniqueJobName1]: [],
- [uniqueJobName2]: [],
- [uniqueJobName3]: [uniqueJobName1, uniqueJobName2],
- [uniqueJobName4]: [uniqueJobName3, uniqueJobName1, uniqueJobName2],
+ expect(generateJobNeedsDict(jobsWithNeeds)).toEqual({
+ [jobName1]: [],
+ [jobName2]: [],
+ [jobName3]: [jobName1, jobName2],
+ [jobName4]: [jobName3, jobName1, jobName2],
});
});
});
diff --git a/spec/frontend/pipelines/unwrapping_utils_spec.js b/spec/frontend/pipelines/unwrapping_utils_spec.js
index 34413ad3ef3..3533599611f 100644
--- a/spec/frontend/pipelines/unwrapping_utils_spec.js
+++ b/spec/frontend/pipelines/unwrapping_utils_spec.js
@@ -1,4 +1,5 @@
import {
+ unwrapArrayOfJobs,
unwrapGroups,
unwrapNodesWithName,
unwrapStagesWithNeeds,
@@ -94,6 +95,29 @@ const completeMock = [
];
describe('Shared pipeline unwrapping utils', () => {
+ describe('unwrapArrayOfJobs', () => {
+ it('returns an empty array if the input is an empty undefined', () => {
+ expect(unwrapArrayOfJobs(undefined)).toEqual([]);
+ });
+
+ it('returns an empty array if the input is an empty array', () => {
+ expect(unwrapArrayOfJobs([])).toEqual([]);
+ });
+
+ it('returns a flatten array of each job with their data and stage name', () => {
+ expect(
+ unwrapArrayOfJobs([
+ { name: 'build', groups: [{ name: 'job_a_1' }, { name: 'job_a_2' }] },
+ { name: 'test', groups: [{ name: 'job_b' }] },
+ ]),
+ ).toMatchObject([
+ { category: 'build', name: 'job_a_1' },
+ { category: 'build', name: 'job_a_2' },
+ { category: 'test', name: 'job_b' },
+ ]);
+ });
+ });
+
describe('unwrapGroups', () => {
it('takes stages without nodes and returns the unwrapped groups', () => {
expect(unwrapGroups(stagesAndGroups)[0].groups).toEqual(groupsArray);
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index e67e8cf3dff..d28d5ecda1b 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -416,6 +416,7 @@ RSpec.describe ProjectsHelper do
describe '#get_project_nav_tabs' do
before do
+ allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?) { true }
end
diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb
index db961d5a4e6..adeccd999f3 100644
--- a/spec/models/label_priority_spec.rb
+++ b/spec/models/label_priority_spec.rb
@@ -18,5 +18,11 @@ RSpec.describe LabelPriority do
expect(subject).to validate_uniqueness_of(:label_id).scoped_to(:project_id)
end
+
+ describe 'when importing' do
+ subject { create(:label_priority, importing: true) }
+
+ it { is_expected.not_to validate_presence_of(:label) }
+ end
end
end
diff --git a/spec/models/sentry_issue_spec.rb b/spec/models/sentry_issue_spec.rb
index 33654bf5e1a..c24350d7067 100644
--- a/spec/models/sentry_issue_spec.rb
+++ b/spec/models/sentry_issue_spec.rb
@@ -27,6 +27,12 @@ RSpec.describe SentryIssue do
expect(duplicate_sentry_issue).to be_invalid
expect(duplicate_sentry_issue.errors.full_messages.first).to include('is already associated')
end
+
+ describe 'when importing' do
+ subject { create(:sentry_issue, importing: true) }
+
+ it { is_expected.not_to validate_presence_of(:issue) }
+ end
end
describe 'callbacks' do
diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb
index e88fc13ecee..9a7624c253a 100644
--- a/spec/models/suggestion_spec.rb
+++ b/spec/models/suggestion_spec.rb
@@ -12,6 +12,12 @@ RSpec.describe Suggestion do
describe 'validations' do
it { is_expected.to validate_presence_of(:note) }
+ context 'when importing' do
+ subject { create(:suggestion, importing: true) }
+
+ it { is_expected.not_to validate_presence_of(:note) }
+ end
+
context 'when suggestion is applied' do
before do
allow(subject).to receive(:applied?).and_return(true)
diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb
index ae1697fb7e6..e9019b55635 100644
--- a/spec/models/timelog_spec.rb
+++ b/spec/models/timelog_spec.rb
@@ -40,6 +40,14 @@ RSpec.describe Timelog do
expect(subject).to be_valid
end
+
+ describe 'when importing' do
+ it 'is valid if issue_id and merge_request_id are missing' do
+ subject.attributes = { issue: nil, merge_request: nil, importing: true }
+
+ expect(subject).to be_valid
+ end
+ end
end
describe 'scopes' do
diff --git a/spec/models/zoom_meeting_spec.rb b/spec/models/zoom_meeting_spec.rb
index 00a0f92e848..2b45533035d 100644
--- a/spec/models/zoom_meeting_spec.rb
+++ b/spec/models/zoom_meeting_spec.rb
@@ -12,8 +12,8 @@ RSpec.describe ZoomMeeting do
end
describe 'Associations' do
- it { is_expected.to belong_to(:project).required }
- it { is_expected.to belong_to(:issue).required }
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:issue) }
end
describe 'scopes' do
@@ -40,6 +40,16 @@ RSpec.describe ZoomMeeting do
end
describe 'Validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:issue) }
+
+ describe 'when importing' do
+ subject { build(:zoom_meeting, importing: true) }
+
+ it { is_expected.not_to validate_presence_of(:project) }
+ it { is_expected.not_to validate_presence_of(:issue) }
+ end
+
describe 'url' do
it { is_expected.to validate_presence_of(:url) }
it { is_expected.to validate_length_of(:url).is_at_most(255) }
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index acc49768545..0e163ec2154 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -117,6 +117,12 @@ RSpec.describe API::Features, stub_feature_flags: false do
)
end
+ it 'logs the event' do
+ expect(Feature.logger).to receive(:info).once
+
+ post api("/features/#{feature_name}", admin), params: { value: 'true' }
+ end
+
it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do
post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' }
@@ -444,6 +450,12 @@ RSpec.describe API::Features, stub_feature_flags: false do
expect(response).to have_gitlab_http_status(:no_content)
end
+
+ it 'logs the event' do
+ expect(Feature.logger).to receive(:info).once
+
+ delete api("/features/#{feature_name}", admin)
+ end
end
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 83b32d9b3fd..c50fe2c7e1d 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -883,7 +883,9 @@ RSpec.describe API::Projects do
only_allow_merge_if_all_discussions_are_resolved: false,
ci_config_path: 'a/custom/path',
merge_method: 'ff'
- })
+ }).tap do |attrs|
+ attrs[:operations_access_level] = 'disabled'
+ end
post api('/projects', user), params: project
@@ -900,6 +902,7 @@ RSpec.describe API::Projects do
expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED)
expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.operations_access_level).to eq(ProjectFeature::DISABLED)
end
it 'creates a project using a template' do
@@ -1579,6 +1582,7 @@ RSpec.describe API::Projects do
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
expect(json_response['allow_merge_on_skipped_pipeline']).to eq(project.allow_merge_on_skipped_pipeline)
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
+ expect(json_response['operations_access_level']).to be_present
end
end
@@ -1621,6 +1625,7 @@ RSpec.describe API::Projects do
expect(json_response['forking_access_level']).to be_present
expect(json_response['wiki_access_level']).to be_present
expect(json_response['builds_access_level']).to be_present
+ expect(json_response['operations_access_level']).to be_present
expect(json_response).to have_key('emails_disabled')
expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['remove_source_branch_after_merge']).to be_truthy
diff --git a/spec/controllers/ide_controller_spec.rb b/spec/requests/ide_controller_spec.rb
index 39d92846863..805c1f1d82b 100644
--- a/spec/controllers/ide_controller_spec.rb
+++ b/spec/requests/ide_controller_spec.rb
@@ -12,6 +12,6 @@ RSpec.describe IdeController do
it 'increases the views counter' do
expect(Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_views_count)
- get :index
+ get ide_url
end
end
diff --git a/spec/rubocop/cop/rspec/htt_party_basic_auth_spec.rb b/spec/rubocop/cop/rspec/htt_party_basic_auth_spec.rb
new file mode 100644
index 00000000000..8c3703a488a
--- /dev/null
+++ b/spec/rubocop/cop/rspec/htt_party_basic_auth_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require_relative '../../../../rubocop/cop/rspec/httparty_basic_auth'
+
+RSpec.describe RuboCop::Cop::RSpec::HTTPartyBasicAuth, type: :rubocop do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when passing `basic_auth: { user: ... }`' do
+ it 'registers an offence' do
+ expect_offense(<<~SOURCE, 'spec/foo.rb')
+ HTTParty.put(
+ url,
+ basic_auth: { user: user, password: token },
+ ^^^^ #{described_class::MESSAGE}
+ body: body
+ )
+ SOURCE
+ end
+
+ it 'can autocorrect the source' do
+ bad = 'HTTParty.put(url, basic_auth: { user: user, password: token })'
+ good = 'HTTParty.put(url, basic_auth: { username: user, password: token })'
+ expect(autocorrect_source(bad)).to eq(good)
+ end
+ end
+
+ context 'when passing `basic_auth: { username: ... }`' do
+ it 'does not register an offence' do
+ expect_no_offenses(<<~SOURCE, 'spec/frontend/fixtures/foo.rb')
+ HTTParty.put(
+ url,
+ basic_auth: { username: user, password: token },
+ body: body
+ )
+ SOURCE
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 93867ef2990..c19c26f9a0b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -142,7 +142,9 @@ RSpec.configure do |config|
config.include TestEnv
config.include FileReadHelpers
config.include Devise::Test::ControllerHelpers, type: :controller
+ config.include Devise::Test::ControllerHelpers, type: :view
config.include Devise::Test::IntegrationHelpers, type: :feature
+ config.include Devise::Test::IntegrationHelpers, type: :request
config.include LoginHelpers, type: :feature
config.include SearchHelpers, type: :feature
config.include WaitHelpers, type: :feature
@@ -150,7 +152,6 @@ RSpec.configure do |config|
config.include EmailHelpers, :mailer, type: :mailer
config.include Warden::Test::Helpers, type: :request
config.include Gitlab::Routing, type: :routing
- config.include Devise::Test::ControllerHelpers, type: :view
config.include ApiHelpers, :api
config.include CookieHelper, :js
config.include InputHelper, :js
diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb
index ed74c3f179f..549dc1cff1d 100644
--- a/spec/support/shared_contexts/navbar_structure_context.rb
+++ b/spec/support/shared_contexts/navbar_structure_context.rb
@@ -14,6 +14,15 @@ RSpec.shared_context 'project navbar structure' do
}
end
+ let(:security_and_compliance_nav_item) do
+ {
+ nav_item: _('Security & Compliance'),
+ nav_sub_items: [
+ _('Audit Events')
+ ]
+ }
+ end
+
let(:structure) do
[
{
@@ -62,6 +71,7 @@ RSpec.shared_context 'project navbar structure' do
_('Schedules')
]
},
+ (security_and_compliance_nav_item if Gitlab.ee?),
{
nav_item: _('Operations'),
nav_sub_items: [
@@ -101,8 +111,7 @@ RSpec.shared_context 'project navbar structure' do
_('Access Tokens'),
_('Repository'),
_('CI / CD'),
- _('Operations'),
- (_('Audit Events') if Gitlab.ee?)
+ _('Operations')
].compact
}
].compact
@@ -128,8 +137,7 @@ RSpec.shared_context 'group navbar structure' do
_('Projects'),
_('Repository'),
_('CI / CD'),
- _('Webhooks'),
- _('Audit Events')
+ _('Webhooks')
]
}
end
@@ -143,6 +151,15 @@ RSpec.shared_context 'group navbar structure' do
}
end
+ let(:security_and_compliance_nav_item) do
+ {
+ nav_item: _('Security & Compliance'),
+ nav_sub_items: [
+ _('Audit Events')
+ ]
+ }
+ end
+
let(:push_rules_nav_item) do
{
nav_item: _('Push Rules'),
@@ -172,6 +189,7 @@ RSpec.shared_context 'group navbar structure' do
nav_item: _('Merge Requests'),
nav_sub_items: []
},
+ (security_and_compliance_nav_item if Gitlab.ee?),
(push_rules_nav_item if Gitlab.ee?),
{
nav_item: _('Kubernetes'),
diff --git a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
index 9c0b398a5c1..2b93d174653 100644
--- a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
@@ -40,6 +40,30 @@ RSpec.shared_examples 'boards create mutation' do
end
end
+ context 'when hide_backlog_list parameter is true' do
+ before do
+ params[:hide_backlog_list] = true
+ end
+
+ it 'returns the board with correct hide_backlog_list field' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['board']['hideBacklogList']).to eq(true)
+ end
+ end
+
+ context 'when hide_closed_list parameter is true' do
+ before do
+ params[:hide_closed_list] = true
+ end
+
+ it 'returns the board with correct hide_closed_list field' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['board']['hideClosedList']).to eq(true)
+ end
+ end
+
context 'when the Boards::CreateService returns an error response' do
before do
allow_next_instance_of(Boards::CreateService) do |service|