summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/finders/members_finder_spec.rb31
-rw-r--r--spec/frontend/content_editor/components/toolbar_button_spec.js2
-rw-r--r--spec/frontend/editor/schema/ci/ci_schema_spec.js4
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/negative_tests/rules_needs.yml46
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/positive_tests/rules_needs.yml32
-rw-r--r--spec/frontend/environments/environment_details/deployments_table_spec.js58
-rw-r--r--spec/frontend/fixtures/environments.rb2
-rw-r--r--spec/frontend/vue_shared/components/web_ide_link_spec.js150
-rw-r--r--spec/helpers/avatars_helper_spec.rb17
-rw-r--r--spec/helpers/nav_helper_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/build/rules_spec.rb144
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb98
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb185
-rw-r--r--spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb20
-rw-r--r--spec/requests/api/members_spec.rb16
-rw-r--r--spec/services/ci/create_pipeline_service/rules_spec.rb103
-rw-r--r--spec/spec_helper.rb4
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb55
18 files changed, 812 insertions, 170 deletions
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index dd995e99b9f..afab4514ce2 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -161,6 +161,37 @@ RSpec.describe MembersFinder, feature_category: :subgroups do
expect(result).to eq([member3, member2, member1])
end
+ context 'with :shared_into_ancestors' do
+ let_it_be(:invited_group) do
+ create(:group).tap do |invited_group|
+ create(:group_group_link, shared_group: nested_group, shared_with_group: invited_group)
+ end
+ end
+
+ let_it_be(:invited_group_member) { create(:group_member, :developer, group: invited_group, user: user1) }
+ let_it_be(:namespace_parent_member) { create(:group_member, :owner, group: group, user: user2) }
+ let_it_be(:namespace_member) { create(:group_member, :developer, group: nested_group, user: user3) }
+ let_it_be(:project_member) { create(:project_member, :developer, project: project, user: user4) }
+
+ subject(:result) { described_class.new(project, user4).execute(include_relations: include_relations) }
+
+ context 'when :shared_into_ancestors is included in the relations' do
+ let(:include_relations) { [:inherited, :direct, :invited_groups, :shared_into_ancestors] }
+
+ it "includes members of groups invited into ancestors of project's group" do
+ expect(result).to match_array([namespace_parent_member, namespace_member, invited_group_member, project_member])
+ end
+ end
+
+ context 'when :shared_into_ancestors is not included in the relations' do
+ let(:include_relations) { [:inherited, :direct, :invited_groups] }
+
+ it "does not include members of groups invited into ancestors of project's group" do
+ expect(result).to match_array([namespace_parent_member, namespace_member, project_member])
+ end
+ end
+ end
+
context 'when :invited_groups is passed' do
shared_examples 'with invited_groups param' do
subject { described_class.new(project, user2).execute(include_relations: [:inherited, :direct, :invited_groups]) }
diff --git a/spec/frontend/content_editor/components/toolbar_button_spec.js b/spec/frontend/content_editor/components/toolbar_button_spec.js
index 1556f761682..ffe1ae20ee9 100644
--- a/spec/frontend/content_editor/components/toolbar_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_button_spec.js
@@ -81,7 +81,7 @@ describe('content_editor/components/toolbar_button', () => {
await emitEditorEvent({ event: 'transaction', tiptapEditor });
- expect(findButton().classes().includes('active')).toBe(outcome);
+ expect(findButton().classes().includes('gl-bg-gray-100!')).toBe(outcome);
expect(tiptapEditor.isActive).toHaveBeenCalledWith(CONTENT_TYPE);
},
);
diff --git a/spec/frontend/editor/schema/ci/ci_schema_spec.js b/spec/frontend/editor/schema/ci/ci_schema_spec.js
index 87208ec7aa8..51fcf26c39a 100644
--- a/spec/frontend/editor/schema/ci/ci_schema_spec.js
+++ b/spec/frontend/editor/schema/ci/ci_schema_spec.js
@@ -27,6 +27,7 @@ import CacheYaml from './yaml_tests/positive_tests/cache.yml';
import FilterYaml from './yaml_tests/positive_tests/filter.yml';
import IncludeYaml from './yaml_tests/positive_tests/include.yml';
import RulesYaml from './yaml_tests/positive_tests/rules.yml';
+import RulesNeedsYaml from './yaml_tests/positive_tests/rules_needs.yml';
import ProjectPathYaml from './yaml_tests/positive_tests/project_path.yml';
import VariablesYaml from './yaml_tests/positive_tests/variables.yml';
import JobWhenYaml from './yaml_tests/positive_tests/job_when.yml';
@@ -46,6 +47,7 @@ import ProjectPathIncludeLeadSlashYaml from './yaml_tests/negative_tests/project
import ProjectPathIncludeNoSlashYaml from './yaml_tests/negative_tests/project_path/include/no_slash.yml';
import ProjectPathIncludeTailSlashYaml from './yaml_tests/negative_tests/project_path/include/tailing_slash.yml';
import RulesNegativeYaml from './yaml_tests/negative_tests/rules.yml';
+import RulesNeedsNegativeYaml from './yaml_tests/negative_tests/rules_needs.yml';
import TriggerNegative from './yaml_tests/negative_tests/trigger.yml';
import VariablesInvalidOptionsYaml from './yaml_tests/negative_tests/variables/invalid_options.yml';
import VariablesInvalidSyntaxDescYaml from './yaml_tests/negative_tests/variables/invalid_syntax_desc.yml';
@@ -88,6 +90,7 @@ describe('positive tests', () => {
JobWhenYaml,
HooksYaml,
RulesYaml,
+ RulesNeedsYaml,
VariablesYaml,
ProjectPathYaml,
IdTokensYaml,
@@ -121,6 +124,7 @@ describe('negative tests', () => {
IncludeNegativeYaml,
JobWhenNegativeYaml,
RulesNegativeYaml,
+ RulesNeedsNegativeYaml,
TriggerNegative,
VariablesInvalidOptionsYaml,
VariablesInvalidSyntaxDescYaml,
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/rules_needs.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/rules_needs.yml
new file mode 100644
index 00000000000..f2f1eb118f8
--- /dev/null
+++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/rules_needs.yml
@@ -0,0 +1,46 @@
+# invalid rules:needs
+lint_job:
+ script: exit 0
+ rules:
+ - if: $var == null
+ needs:
+
+# invalid rules:needs
+lint_job_2:
+ script: exit 0
+ rules:
+ - if: $var == null
+ needs: [20]
+
+# invalid rules:needs
+lint_job_3:
+ script: exit 0
+ rules:
+ - if: $var == null
+ needs:
+ - job:
+
+# invalid rules:needs
+lint_job_5:
+ script: exit 0
+ rules:
+ - if: $var == null
+ needs:
+ - pipeline: 5
+
+# invalid rules:needs
+lint_job_6:
+ script: exit 0
+ rules:
+ - if: $var == null
+ needs:
+ - project: namespace/group/project-name
+
+# invalid rules:needs
+lint_job_7:
+ script: exit 0
+ rules:
+ - if: $var == null
+ needs:
+ - pipeline: 5
+ job: lint_job_6
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/rules_needs.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/rules_needs.yml
new file mode 100644
index 00000000000..a4a5183dcf4
--- /dev/null
+++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/rules_needs.yml
@@ -0,0 +1,32 @@
+# valid workflow:rules:needs
+pre_lint_job:
+ script: exit 0
+ rules:
+ - if: $var == null
+
+lint_job:
+ script: exit 0
+ rules:
+ - if: $var == null
+
+rspec_job:
+ script: exit 0
+ rules:
+ - if: $var == null
+ needs: [lint_job]
+
+job:
+ needs: [rspec_job]
+ script: exit 0
+ rules:
+ - if: $var == null
+ needs:
+ - job: lint_job
+ artifacts: false
+ optional: true
+ - job: pre_lint_job
+ artifacts: true
+ optional: false
+ - rspec_job
+ - if: $var == true
+ needs: [lint_job, pre_lint_job] \ No newline at end of file
diff --git a/spec/frontend/environments/environment_details/deployments_table_spec.js b/spec/frontend/environments/environment_details/deployments_table_spec.js
new file mode 100644
index 00000000000..7dad5617383
--- /dev/null
+++ b/spec/frontend/environments/environment_details/deployments_table_spec.js
@@ -0,0 +1,58 @@
+import resolvedEnvironmentDetails from 'test_fixtures/graphql/environments/graphql/queries/environment_details.query.graphql.json';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import Commit from '~/vue_shared/components/commit.vue';
+import DeploymentStatusLink from '~/environments/environment_details/components/deployment_status_link.vue';
+import DeploymentJob from '~/environments/environment_details/components/deployment_job.vue';
+import DeploymentTriggerer from '~/environments/environment_details/components/deployment_triggerer.vue';
+import DeploymentActions from '~/environments/environment_details/components/deployment_actions.vue';
+import DeploymentsTable from '~/environments/environment_details/deployments_table.vue';
+import { convertToDeploymentTableRow } from '~/environments/helpers/deployment_data_transformation_helper';
+
+const { environment } = resolvedEnvironmentDetails.data.project;
+const deployments = environment.deployments.nodes.map((d) =>
+ convertToDeploymentTableRow(d, environment),
+);
+
+describe('~/environments/environment_details/index.vue', () => {
+ let wrapper;
+
+ const createWrapper = (propsData = {}) => {
+ wrapper = mountExtended(DeploymentsTable, {
+ propsData: {
+ deployments,
+ ...propsData,
+ },
+ });
+ };
+
+ describe('deployment row', () => {
+ const [, , deployment] = deployments;
+
+ let row;
+
+ beforeEach(() => {
+ createWrapper();
+
+ row = wrapper.find('tr:nth-child(3)');
+ });
+
+ it.each`
+ cell | component | props
+ ${'status'} | ${DeploymentStatusLink} | ${{ deploymentJob: deployment.job, status: deployment.status }}
+ ${'triggerer'} | ${DeploymentTriggerer} | ${{ triggerer: deployment.triggerer }}
+ ${'commit'} | ${Commit} | ${deployment.commit}
+ ${'job'} | ${DeploymentJob} | ${{ job: deployment.job }}
+ ${'created date'} | ${'[data-testid="deployment-created-at"]'} | ${{ time: deployment.created }}
+ ${'deployed date'} | ${'[data-testid="deployment-deployed-at"]'} | ${{ time: deployment.deployed }}
+ ${'deployment actions'} | ${DeploymentActions} | ${{ actions: deployment.actions, rollback: deployment.rollback, approvalEnvironment: deployment.deploymentApproval }}
+ `('should show the correct component for $cell', ({ component, props }) => {
+ expect(row.findComponent(component).props()).toMatchObject(props);
+ });
+
+ it('hides the deployed at timestamp for not-finished deployments', () => {
+ row = wrapper.find('tr');
+
+ expect(row.find('[data-testid="deployment-deployed-at"]').exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/fixtures/environments.rb b/spec/frontend/fixtures/environments.rb
index 77e2a96b328..81f1eb11e3e 100644
--- a/spec/frontend/fixtures/environments.rb
+++ b/spec/frontend/fixtures/environments.rb
@@ -44,7 +44,7 @@ RSpec.describe 'Environments (JavaScript fixtures)', feature_category: :environm
end
let_it_be(:deployment_success) do
- create(:deployment, :success, environment: environment, deployable: build)
+ create(:deployment, :success, environment: environment, deployable: build, finished_at: 1.hour.since)
end
let_it_be(:deployment_failed) do
diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js
index 4b2ce24a49f..d888abc19ef 100644
--- a/spec/frontend/vue_shared/components/web_ide_link_spec.js
+++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js
@@ -1,4 +1,4 @@
-import { GlButton, GlLink, GlModal, GlPopover } from '@gitlab/ui';
+import { GlButton, GlModal } from '@gitlab/ui';
import { nextTick } from 'vue';
import ActionsButton from '~/vue_shared/components/actions_button.vue';
@@ -9,7 +9,6 @@ import WebIdeLink, {
PREFERRED_EDITOR_KEY,
} from '~/vue_shared/components/web_ide_link.vue';
import ConfirmForkModal from '~/vue_shared/components/confirm_fork_modal.vue';
-import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import { KEY_WEB_IDE } from '~/vue_shared/components/constants';
import { stubComponent } from 'helpers/stub_component';
@@ -95,14 +94,7 @@ describe('Web IDE link component', () => {
let wrapper;
- function createComponent(
- props,
- {
- mountFn = shallowMountExtended,
- glFeatures = {},
- userCalloutDismisserSlotProps = { dismiss: jest.fn() },
- } = {},
- ) {
+ function createComponent(props, { mountFn = shallowMountExtended, glFeatures = {} } = {}) {
wrapper = mountFn(WebIdeLink, {
propsData: {
editUrl: TEST_EDIT_URL,
@@ -124,11 +116,6 @@ describe('Web IDE link component', () => {
<slot name="modal-footer"></slot>
</div>`,
}),
- UserCalloutDismisser: stubComponent(UserCalloutDismisser, {
- render() {
- return this.$scopedSlots.default(userCalloutDismisserSlotProps);
- },
- }),
},
});
}
@@ -141,13 +128,6 @@ describe('Web IDE link component', () => {
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const findModal = () => wrapper.findComponent(GlModal);
const findForkConfirmModal = () => wrapper.findComponent(ConfirmForkModal);
- const findUserCalloutDismisser = () => wrapper.findComponent(UserCalloutDismisser);
- const findNewWebIdeCalloutPopover = () => wrapper.findComponent(GlPopover);
- const findTryItOutLink = () =>
- wrapper
- .findAllComponents(GlLink)
- .filter((link) => link.text().includes('Try it out'))
- .at(0);
it.each([
{
@@ -446,132 +426,6 @@ describe('Web IDE link component', () => {
});
});
- describe('Web IDE callout', () => {
- describe('vscode_web_ide feature flag is enabled and the edit button is not shown', () => {
- let dismiss;
-
- beforeEach(() => {
- dismiss = jest.fn();
- createComponent(
- {
- showEditButton: false,
- },
- {
- glFeatures: { vscodeWebIde: true },
- userCalloutDismisserSlotProps: { dismiss },
- },
- );
- });
- it('does not skip the user_callout_dismisser query', () => {
- expect(findUserCalloutDismisser().props()).toEqual(
- expect.objectContaining({
- skipQuery: false,
- featureName: 'vscode_web_ide_callout',
- }),
- );
- });
-
- it('mounts new web ide callout popover', () => {
- expect(findNewWebIdeCalloutPopover().props()).toEqual(
- expect.objectContaining({
- showCloseButton: '',
- target: 'web-ide-link',
- triggers: 'manual',
- boundaryPadding: 80,
- }),
- );
- });
-
- describe.each`
- calloutStatus | shouldShowCallout | popoverVisibility | tooltipVisibility
- ${'show'} | ${true} | ${true} | ${false}
- ${'hide'} | ${false} | ${false} | ${true}
- `(
- 'when should $calloutStatus web ide callout',
- ({ shouldShowCallout, popoverVisibility, tooltipVisibility }) => {
- beforeEach(() => {
- createComponent(
- {
- showEditButton: false,
- },
- {
- glFeatures: { vscodeWebIde: true },
- userCalloutDismisserSlotProps: { shouldShowCallout, dismiss },
- },
- );
- });
-
- it(`popover visibility = ${popoverVisibility}`, () => {
- expect(findNewWebIdeCalloutPopover().props().show).toBe(popoverVisibility);
- });
-
- it(`action button tooltip visibility = ${tooltipVisibility}`, () => {
- expect(findActionsButton().props().showActionTooltip).toBe(tooltipVisibility);
- });
- },
- );
-
- it('dismisses the callout when popover close button is clicked', () => {
- findNewWebIdeCalloutPopover().vm.$emit('close-button-clicked');
-
- expect(dismiss).toHaveBeenCalled();
- });
-
- it('dismisses the callout when try it now link is clicked', () => {
- findTryItOutLink().vm.$emit('click');
-
- expect(dismiss).toHaveBeenCalled();
- });
-
- it('dismisses the callout when action button is clicked', () => {
- findActionsButton().vm.$emit('actionClicked');
-
- expect(dismiss).toHaveBeenCalled();
- });
- });
-
- describe.each`
- featureFlag | showEditButton
- ${false} | ${true}
- ${true} | ${false}
- ${false} | ${false}
- `(
- 'when vscode_web_ide=$featureFlag and showEditButton = $showEditButton',
- ({ vscodeWebIde, showEditButton }) => {
- let dismiss;
-
- beforeEach(() => {
- dismiss = jest.fn();
-
- createComponent(
- {
- showEditButton,
- },
- { glFeatures: { vscodeWebIde }, userCalloutDismisserSlotProps: { dismiss } },
- );
- });
-
- it('skips the user_callout_dismisser query', () => {
- expect(findUserCalloutDismisser().props().skipQuery).toBe(true);
- });
-
- it('displays actions button tooltip', () => {
- expect(findActionsButton().props().showActionTooltip).toBe(true);
- });
-
- it('mounts new web ide callout popover', () => {
- expect(findNewWebIdeCalloutPopover().exists()).toBe(false);
- });
-
- it('does not dismiss the callout when action button is clicked', () => {
- findActionsButton().vm.$emit('actionClicked');
-
- expect(dismiss).not.toHaveBeenCalled();
- });
- },
- );
- });
-
describe('when vscode_web_ide feature flag is enabled', () => {
describe('when is not showing edit button', () => {
describe(`when ${PREFERRED_EDITOR_RESET_KEY} is unset`, () => {
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index b7fdadbd036..dd0d6d1246f 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -102,7 +102,7 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do
end
describe '#avatar_icon_for_email', :clean_gitlab_redis_cache do
- let(:user) { create(:user, :public_email, avatar: File.open(uploaded_image_temp_path)) }
+ let(:user) { create(:user, :public_email, :commit_email, avatar: File.open(uploaded_image_temp_path)) }
subject { helper.avatar_icon_for_email(user.email).to_s }
@@ -131,13 +131,22 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do
end
context 'without an email passed' do
- it 'calls gravatar_icon' do
- expect(helper).to receive(:gravatar_icon).with(nil, 20, 2)
- expect(User).not_to receive(:find_by_any_email)
+ it 'returns the default avatar' do
+ expect(helper).to receive(:default_avatar)
+ expect(User).not_to receive(:with_public_email)
helper.avatar_icon_for_email(nil, 20, 2)
end
end
+
+ context 'with a blank email address' do
+ it 'returns the default avatar' do
+ expect(helper).to receive(:default_avatar)
+ expect(User).not_to receive(:with_public_email)
+
+ helper.avatar_icon_for_email('', 20, 2)
+ end
+ end
end
end
diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb
index d1e3c7d2240..17d28b07763 100644
--- a/spec/helpers/nav_helper_spec.rb
+++ b/spec/helpers/nav_helper_spec.rb
@@ -161,6 +161,21 @@ RSpec.describe NavHelper, feature_category: :navigation do
context 'with feature flag on' do
let(:new_nav_ff) { true }
+ context 'when user has not interacted with the new nav toggle yet' do
+ let(:user_preference) { nil }
+
+ specify { expect(subject).to eq false }
+
+ context 'when the user was enrolled into the new nav via a special feature flag' do
+ before do
+ # this ff is disabled in globally to keep tests of the old nav working
+ stub_feature_flags(super_sidebar_nav_enrolled: true)
+ end
+
+ specify { expect(subject).to eq true }
+ end
+ end
+
context 'when user has new nav disabled' do
let(:user_preference) { false }
diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb
index e82dcd0254d..1ece0f6b7b9 100644
--- a/spec/lib/gitlab/ci/build/rules_spec.rb
+++ b/spec/lib/gitlab/ci/build/rules_spec.rb
@@ -181,6 +181,108 @@ RSpec.describe Gitlab::Ci::Build::Rules do
end
end
+ context 'with needs' do
+ context 'when single needs is specified' do
+ let(:rule_list) do
+ [{ if: '$VAR == null', needs: [{ name: 'test', artifacts: true, optional: false }] }]
+ end
+
+ it {
+ is_expected.to eq(described_class::Result.new('on_success', nil, nil, nil,
+ [{ name: 'test', artifacts: true, optional: false }], nil))
+ }
+ end
+
+ context 'when multiple needs are specified' do
+ let(:rule_list) do
+ [{ if: '$VAR == null',
+ needs: [{ name: 'test', artifacts: true, optional: false },
+ { name: 'rspec', artifacts: true, optional: false }] }]
+ end
+
+ it {
+ is_expected.to eq(described_class::Result.new('on_success', nil, nil, nil,
+ [{ name: 'test', artifacts: true, optional: false },
+ { name: 'rspec', artifacts: true, optional: false }], nil))
+ }
+ end
+
+ context 'when there are no needs specified' do
+ let(:rule_list) { [{ if: '$VAR == null' }] }
+
+ it { is_expected.to eq(described_class::Result.new('on_success', nil, nil, nil, nil, nil)) }
+ end
+
+ context 'when need is specified with additional attibutes' do
+ let(:rule_list) do
+ [{ if: '$VAR == null', needs: [{
+ artifacts: true,
+ name: 'test',
+ optional: false,
+ when: 'never'
+ }] }]
+ end
+
+ it {
+ is_expected.to eq(
+ described_class::Result.new('on_success', nil, nil, nil,
+ [{ artifacts: true, name: 'test', optional: false, when: 'never' }], nil))
+ }
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(introduce_rules_with_needs: false)
+ end
+
+ context 'with needs' do
+ context 'when single needs is specified' do
+ let(:rule_list) do
+ [{ if: '$VAR == null', needs: [{ name: 'test', artifacts: true, optional: false }] }]
+ end
+
+ it {
+ is_expected.to eq(described_class::Result.new('on_success', nil, nil, nil, nil, nil))
+ }
+ end
+
+ context 'when multiple needs are specified' do
+ let(:rule_list) do
+ [{ if: '$VAR == null',
+ needs: [{ name: 'test', artifacts: true, optional: false },
+ { name: 'rspec', artifacts: true, optional: false }] }]
+ end
+
+ it {
+ is_expected.to eq(described_class::Result.new('on_success', nil, nil, nil, nil, nil))
+ }
+ end
+
+ context 'when there are no needs specified' do
+ let(:rule_list) { [{ if: '$VAR == null' }] }
+
+ it { is_expected.to eq(described_class::Result.new('on_success', nil, nil, nil, nil, nil)) }
+ end
+
+ context 'when need is specified with additional attibutes' do
+ let(:rule_list) do
+ [{ if: '$VAR == null', needs: [{
+ artifacts: true,
+ name: 'test',
+ optional: false,
+ when: 'never'
+ }] }]
+ end
+
+ it {
+ is_expected.to eq(
+ described_class::Result.new('on_success', nil, nil, nil, nil, nil))
+ }
+ end
+ end
+ end
+ end
+
context 'with variables' do
context 'with matching rule' do
let(:rule_list) { [{ if: '$VAR == null', variables: { MY_VAR: 'my var' } }] }
@@ -208,9 +310,10 @@ RSpec.describe Gitlab::Ci::Build::Rules do
let(:start_in) { nil }
let(:allow_failure) { nil }
let(:variables) { nil }
+ let(:needs) { nil }
subject(:result) do
- Gitlab::Ci::Build::Rules::Result.new(when_value, start_in, allow_failure, variables)
+ Gitlab::Ci::Build::Rules::Result.new(when_value, start_in, allow_failure, variables, needs)
end
describe '#build_attributes' do
@@ -221,6 +324,45 @@ RSpec.describe Gitlab::Ci::Build::Rules do
it 'compacts nil values' do
is_expected.to eq(options: {}, when: 'on_success')
end
+
+ context 'scheduling_type' do
+ context 'when rules have needs' do
+ context 'single need' do
+ let(:needs) do
+ { job: [{ name: 'test' }] }
+ end
+
+ it 'saves needs' do
+ expect(subject[:needs_attributes]).to eq([{ name: "test" }])
+ end
+
+ it 'adds schedule type to the build_attributes' do
+ expect(subject[:scheduling_type]).to eq(:dag)
+ end
+ end
+
+ context 'multiple needs' do
+ let(:needs) do
+ { job: [{ name: 'test' }, { name: 'test_2', artifacts: true, optional: false }] }
+ end
+
+ it 'saves needs' do
+ expect(subject[:needs_attributes]).to match_array([{ name: "test" },
+ { name: 'test_2', artifacts: true, optional: false }])
+ end
+
+ it 'adds schedule type to the build_attributes' do
+ expect(subject[:scheduling_type]).to eq(:dag)
+ end
+ end
+ end
+
+ context 'when rules do not have needs' do
+ it 'does not add schedule type to the build_attributes' do
+ expect(subject.key?(:scheduling_type)).to be_falsy
+ end
+ end
+ end
end
describe '#pass?' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 86a11111283..9d5a9bc8058 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -109,6 +109,104 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build, feature_category: :pipeline_co
end
end
+ context 'with job:rules:[needs:]' do
+ context 'with a single rule' do
+ let(:job_needs_attributes) { [{ name: 'rspec' }] }
+
+ context 'when job has needs set' do
+ context 'when rule evaluates to true' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ needs_attributes: job_needs_attributes,
+ rules: [{ if: '$VAR == null', needs: { job: [{ name: 'build-job' }] } }] }
+ end
+
+ it 'overrides the job needs' do
+ expect(subject).to include(needs_attributes: [{ name: 'build-job' }])
+ end
+ end
+
+ context 'when rule evaluates to false' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ needs_attributes: job_needs_attributes,
+ rules: [{ if: '$VAR == true', needs: { job: [{ name: 'build-job' }] } }] }
+ end
+
+ it 'keeps the job needs' do
+ expect(subject).to include(needs_attributes: job_needs_attributes)
+ end
+ end
+
+ context 'with subkeys: artifacts, optional' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ rules:
+ [
+ { if: '$VAR == null',
+ needs: {
+ job: [{
+ name: 'build-job',
+ optional: false,
+ artifacts: true
+ }]
+ } }
+ ] }
+ end
+
+ context 'when rule evaluates to true' do
+ it 'sets the job needs as well as the job subkeys' do
+ expect(subject[:needs_attributes]).to match_array([{ name: 'build-job', optional: false, artifacts: true }])
+ end
+
+ it 'sets the scheduling type to dag' do
+ expect(subject[:scheduling_type]).to eq(:dag)
+ end
+ end
+ end
+ end
+
+ context 'with multiple rules' do
+ context 'when a rule evaluates to true' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ needs_attributes: job_needs_attributes,
+ rules: [
+ { if: '$VAR == true', needs: { job: [{ name: 'rspec-1' }] } },
+ { if: '$VAR2 == true', needs: { job: [{ name: 'rspec-2' }] } },
+ { if: '$VAR3 == null', needs: { job: [{ name: 'rspec' }, { name: 'lint' }] } }
+ ] }
+ end
+
+ it 'overrides the job needs' do
+ expect(subject).to include(needs_attributes: [{ name: 'rspec' }, { name: 'lint' }])
+ end
+ end
+
+ context 'when all rules evaluates to false' do
+ let(:attributes) do
+ { name: 'rspec',
+ ref: 'master',
+ needs_attributes: job_needs_attributes,
+ rules: [
+ { if: '$VAR == true', needs: { job: [{ name: 'rspec-1' }] } },
+ { if: '$VAR2 == true', needs: { job: [{ name: 'rspec-2' }] } },
+ { if: '$VAR3 == true', needs: { job: [{ name: 'rspec-3' }] } }
+ ] }
+ end
+
+ it 'keeps the job needs' do
+ expect(subject).to include(needs_attributes: job_needs_attributes)
+ end
+ end
+ end
+ end
+ end
+
context 'with job:tags' do
let(:attributes) do
{
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index a35dd968cd6..f8c2889798f 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -659,6 +659,191 @@ module Gitlab
it_behaves_like 'has warnings and expected error', /build job: need test is not defined in current or prior stages/
end
+
+ describe '#validate_job_needs!' do
+ context "when all validations pass" do
+ let(:config) do
+ <<-EOYML
+ stages:
+ - lint
+ lint_job:
+ needs: [lint_job_2]
+ stage: lint
+ script: 'echo lint_job'
+ rules:
+ - if: $var == null
+ needs:
+ - lint_job_2
+ - job: lint_job_3
+ optional: true
+ lint_job_2:
+ stage: lint
+ script: 'echo job'
+ rules:
+ - if: $var == null
+ lint_job_3:
+ stage: lint
+ script: 'echo job'
+ rules:
+ - if: $var == null
+ EOYML
+ end
+
+ it 'returns a valid response' do
+ expect(subject).to be_valid
+ expect(subject).to be_instance_of(Gitlab::Ci::YamlProcessor::Result)
+ end
+ end
+
+ context 'needs as array' do
+ context 'single need in following stage' do
+ let(:config) do
+ <<-EOYML
+ stages:
+ - lint
+ - test
+ lint_job:
+ stage: lint
+ script: 'echo lint_job'
+ rules:
+ - if: $var == null
+ needs: [test_job]
+ test_job:
+ stage: test
+ script: 'echo job'
+ rules:
+ - if: $var == null
+ EOYML
+ end
+
+ it_behaves_like 'returns errors', 'lint_job job: need test_job is not defined in current or prior stages'
+ end
+
+ context 'multiple needs in the following stage' do
+ let(:config) do
+ <<-EOYML
+ stages:
+ - lint
+ - test
+ lint_job:
+ stage: lint
+ script: 'echo lint_job'
+ rules:
+ - if: $var == null
+ needs: [test_job, test_job_2]
+ test_job:
+ stage: test
+ script: 'echo job'
+ rules:
+ - if: $var == null
+ test_job_2:
+ stage: test
+ script: 'echo job'
+ rules:
+ - if: $var == null
+ EOYML
+ end
+
+ it_behaves_like 'returns errors', 'lint_job job: need test_job is not defined in current or prior stages'
+ end
+
+ context 'single need in following state - hyphen need' do
+ let(:config) do
+ <<-EOYML
+ stages:
+ - lint
+ - test
+ lint_job:
+ stage: lint
+ script: 'echo lint_job'
+ rules:
+ - if: $var == null
+ needs:
+ - test_job
+ test_job:
+ stage: test
+ script: 'echo job'
+ rules:
+ - if: $var == null
+ EOYML
+ end
+
+ it_behaves_like 'returns errors', 'lint_job job: need test_job is not defined in current or prior stages'
+ end
+
+ context 'when there are duplicate needs (string and hash)' do
+ let(:config) do
+ <<-EOYML
+ stages:
+ - test
+ test_job_1:
+ stage: test
+ script: 'echo lint_job'
+ rules:
+ - if: $var == null
+ needs:
+ - test_job_2
+ - job: test_job_2
+ test_job_2:
+ stage: test
+ script: 'echo job'
+ rules:
+ - if: $var == null
+ EOYML
+ end
+
+ it_behaves_like 'returns errors', 'test_job_1 has the following needs duplicated: test_job_2.'
+ end
+ end
+
+ context 'rule needs as hash' do
+ context 'single hash need in following stage' do
+ let(:config) do
+ <<-EOYML
+ stages:
+ - lint
+ - test
+ lint_job:
+ stage: lint
+ script: 'echo lint_job'
+ rules:
+ - if: $var == null
+ needs:
+ - job: test_job
+ artifacts: false
+ optional: false
+ test_job:
+ stage: test
+ script: 'echo job'
+ rules:
+ - if: $var == null
+ EOYML
+ end
+
+ it_behaves_like 'returns errors', 'lint_job job: need test_job is not defined in current or prior stages'
+ end
+ end
+
+ context 'job rule need does not exist' do
+ let(:config) do
+ <<-EOYML
+ build:
+ stage: build
+ script: echo
+ rules:
+ - when: always
+ test:
+ stage: test
+ script: echo
+ rules:
+ - if: $var == null
+ needs: [unknown_job]
+ EOYML
+ end
+
+ it_behaves_like 'has warnings and expected error', /test job: undefined need: unknown_job/
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb b/spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb
index 81b00f82803..8e2a53ea76f 100644
--- a/spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb
@@ -286,6 +286,26 @@ RSpec.describe Gitlab::Database::Partitioning::List::ConvertTable, feature_categ
expect(migration_context.has_loose_foreign_key?(table_name)).to be_truthy
expect(migration_context.has_loose_foreign_key?(parent_table_name)).to be_truthy
end
+
+ context 'with locking tables' do
+ let(:lock_tables) { [table_name] }
+
+ it 'locks the table before dropping the triggers' do
+ recorder = ActiveRecord::QueryRecorder.new { partition }
+
+ lock_index = recorder.log.find_index do |log|
+ log.start_with?('LOCK "_test_table_to_partition" IN ACCESS EXCLUSIVE MODE')
+ end
+
+ trigger_index = recorder.log.find_index do |log|
+ log.start_with?('DROP TRIGGER IF EXISTS _test_table_to_partition_loose_fk_trigger')
+ end
+
+ expect(lock_index).to be_present
+ expect(trigger_index).to be_present
+ expect(lock_index).to be < trigger_index
+ end
+ end
end
end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 85ccfa3cf51..353fddcb08d 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -130,6 +130,8 @@ RSpec.describe API::Members, feature_category: :subgroups do
let(:project_user) { create(:user) }
let(:linked_group_user) { create(:user) }
let!(:project_group_link) { create(:project_group_link, project: project, group: linked_group) }
+ let(:invited_group_developer) { create(:user, username: 'invited_group_developer') }
+ let(:invited_group) { create(:group) { |group| group.add_developer(invited_group_developer) } }
let(:project) do
create(:project, :public, group: nested_group) do |project|
@@ -146,19 +148,21 @@ RSpec.describe API::Members, feature_category: :subgroups do
let(:nested_group) do
create(:group, parent: group) do |nested_group|
nested_group.add_developer(nested_user)
+ create(:group_group_link, :guest, shared_with_group: invited_group, shared_group: nested_group)
end
end
- it 'finds all project members including inherited members' do
+ it 'finds all project members including inherited members and members shared into ancestor groups' do
get api("/projects/#{project.id}/members/all", developer)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id, nested_user.id, project_user.id, linked_group_user.id]
+ expected_user_ids = [maintainer.id, developer.id, nested_user.id, project_user.id, linked_group_user.id, invited_group_developer.id]
+ expect(json_response.map { |u| u['id'] }).to match_array expected_user_ids
end
- it 'returns only one member for each user without returning duplicated members' do
+ it 'returns only one member for each user without returning duplicated members with correct access levels' do
linked_group.add_developer(developer)
get api("/projects/#{project.id}/members/all", developer)
@@ -172,7 +176,8 @@ RSpec.describe API::Members, feature_category: :subgroups do
[maintainer.id, Gitlab::Access::OWNER],
[nested_user.id, Gitlab::Access::DEVELOPER],
[project_user.id, Gitlab::Access::DEVELOPER],
- [linked_group_user.id, Gitlab::Access::DEVELOPER]
+ [linked_group_user.id, Gitlab::Access::DEVELOPER],
+ [invited_group_developer.id, Gitlab::Access::GUEST]
]
expect(json_response.map { |u| [u['id'], u['access_level']] }).to match_array(expected_users_and_access_levels)
end
@@ -183,7 +188,8 @@ RSpec.describe API::Members, feature_category: :subgroups do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id, nested_user.id]
+ expected_user_ids = [maintainer.id, developer.id, nested_user.id, invited_group_developer.id]
+ expect(json_response.map { |u| u['id'] }).to match_array expected_user_ids
end
context 'with a subgroup' do
diff --git a/spec/services/ci/create_pipeline_service/rules_spec.rb b/spec/services/ci/create_pipeline_service/rules_spec.rb
index 19f9e7e3e4a..87112137675 100644
--- a/spec/services/ci/create_pipeline_service/rules_spec.rb
+++ b/spec/services/ci/create_pipeline_service/rules_spec.rb
@@ -386,6 +386,109 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes
expect(regular_job.allow_failure).to eq(true)
end
end
+
+ context 'with needs:' do
+ let(:config) do
+ <<-EOY
+ job1:
+ script: ls
+
+ job2:
+ script: ls
+ rules:
+ - if: $var == null
+ needs: [job1]
+ - when: on_success
+
+ job3:
+ script: ls
+ rules:
+ - if: $var == null
+ needs: [job1]
+ - needs: [job2]
+
+ job4:
+ script: ls
+ needs: [job1]
+ rules:
+ - if: $var == null
+ needs: [job2]
+ - when: on_success
+ needs: [job3]
+ EOY
+ end
+
+ let(:job1) { pipeline.builds.find_by(name: 'job1') }
+ let(:job2) { pipeline.builds.find_by(name: 'job2') }
+ let(:job3) { pipeline.builds.find_by(name: 'job3') }
+ let(:job4) { pipeline.builds.find_by(name: 'job4') }
+
+ context 'when the `$var` rule matches' do
+ it 'creates a pipeline with overridden needs' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('job1', 'job2', 'job3', 'job4')
+
+ expect(job1.needs).to be_empty
+ expect(job2.needs).to contain_exactly(an_object_having_attributes(name: 'job1'))
+ expect(job3.needs).to contain_exactly(an_object_having_attributes(name: 'job1'))
+ expect(job4.needs).to contain_exactly(an_object_having_attributes(name: 'job2'))
+ end
+ end
+
+ context 'when the `$var` rule does not match' do
+ let(:initialization_params) { base_initialization_params.merge(variables_attributes: variables_attributes) }
+
+ let(:variables_attributes) do
+ [{ key: 'var', secret_value: 'SOME_VAR' }]
+ end
+
+ it 'creates a pipeline with overridden needs' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('job1', 'job2', 'job3', 'job4')
+
+ expect(job1.needs).to be_empty
+ expect(job2.needs).to be_empty
+ expect(job3.needs).to contain_exactly(an_object_having_attributes(name: 'job2'))
+ expect(job4.needs).to contain_exactly(an_object_having_attributes(name: 'job3'))
+ end
+ end
+
+ context 'when the FF introduce_rules_with_needs is disabled' do
+ before do
+ stub_feature_flags(introduce_rules_with_needs: false)
+ end
+
+ context 'when the `$var` rule matches' do
+ it 'creates a pipeline without overridden needs' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('job1', 'job2', 'job3', 'job4')
+
+ expect(job1.needs).to be_empty
+ expect(job2.needs).to be_empty
+ expect(job3.needs).to be_empty
+ expect(job4.needs).to contain_exactly(an_object_having_attributes(name: 'job1'))
+ end
+ end
+
+ context 'when the `$var` rule does not match' do
+ let(:initialization_params) { base_initialization_params.merge(variables_attributes: variables_attributes) }
+
+ let(:variables_attributes) do
+ [{ key: 'var', secret_value: 'SOME_VAR' }]
+ end
+
+ it 'creates a pipeline without overridden needs' do
+ expect(pipeline).to be_persisted
+ expect(build_names).to contain_exactly('job1', 'job2', 'job3', 'job4')
+
+ expect(job1.needs).to be_empty
+ expect(job2.needs).to be_empty
+ expect(job3.needs).to be_empty
+ expect(job4.needs).to contain_exactly(an_object_having_attributes(name: 'job1'))
+ end
+ end
+ end
+ end
end
context 'changes:' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 470df53051e..f8bbad393e6 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -269,6 +269,10 @@ RSpec.configure do |config|
stub_feature_flags(ci_queueing_disaster_recovery_disable_fair_scheduling: false)
stub_feature_flags(ci_queueing_disaster_recovery_disable_quota: false)
+ # Only a few percent of users will be "enrolled" into the new nav with this flag.
+ # Having it enabled globally would make it impossible to test the current nav.
+ stub_feature_flags(super_sidebar_nav_enrolled: false)
+
# It's disabled in specs because we don't support certain features which
# cause spec failures.
stub_feature_flags(use_click_house_database_for_error_tracking: false)
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index 58659775d8c..493a96b8dae 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -79,7 +79,8 @@ RSpec.shared_examples 'an accessible' do
let(:access) do
[{ 'type' => 'repository',
'name' => project.full_path,
- 'actions' => actions }]
+ 'actions' => actions,
+ 'meta' => { 'project_path' => project.full_path } }]
end
it_behaves_like 'a valid token'
@@ -244,12 +245,14 @@ RSpec.shared_examples 'a container registry auth service' do
{
'type' => 'repository',
'name' => project.full_path,
- 'actions' => ['pull']
+ 'actions' => ['pull'],
+ 'meta' => { 'project_path' => project.full_path }
},
{
'type' => 'repository',
'name' => "#{project.full_path}/*",
- 'actions' => ['pull']
+ 'actions' => ['pull'],
+ 'meta' => { 'project_path' => project.full_path }
}
]
end
@@ -822,16 +825,20 @@ RSpec.shared_examples 'a container registry auth service' do
[
{ 'type' => 'repository',
'name' => internal_project.full_path,
- 'actions' => ['pull'] },
+ 'actions' => ['pull'],
+ 'meta' => { 'project_path' => internal_project.full_path } },
{ 'type' => 'repository',
'name' => private_project.full_path,
- 'actions' => ['pull'] },
+ 'actions' => ['pull'],
+ 'meta' => { 'project_path' => private_project.full_path } },
{ 'type' => 'repository',
'name' => public_project.full_path,
- 'actions' => ['pull'] },
+ 'actions' => ['pull'],
+ 'meta' => { 'project_path' => public_project.full_path } },
{ 'type' => 'repository',
'name' => public_project_private_container_registry.full_path,
- 'actions' => ['pull'] }
+ 'actions' => ['pull'],
+ 'meta' => { 'project_path' => public_project_private_container_registry.full_path } }
]
end
end
@@ -845,10 +852,12 @@ RSpec.shared_examples 'a container registry auth service' do
[
{ 'type' => 'repository',
'name' => internal_project.full_path,
- 'actions' => ['pull'] },
+ 'actions' => ['pull'],
+ 'meta' => { 'project_path' => internal_project.full_path } },
{ 'type' => 'repository',
'name' => public_project.full_path,
- 'actions' => ['pull'] }
+ 'actions' => ['pull'],
+ 'meta' => { 'project_path' => public_project.full_path } }
]
end
end
@@ -862,7 +871,8 @@ RSpec.shared_examples 'a container registry auth service' do
[
{ 'type' => 'repository',
'name' => public_project.full_path,
- 'actions' => ['pull'] }
+ 'actions' => ['pull'],
+ 'meta' => { 'project_path' => public_project.full_path } }
]
end
end
@@ -1258,4 +1268,29 @@ RSpec.shared_examples 'a container registry auth service' do
end
end
end
+
+ context 'with a project with a path containing special characters' do
+ let_it_be(:bad_project) { create(:project) }
+
+ before do
+ bad_project.update_attribute(:path, "#{bad_project.path}_")
+ end
+
+ describe '#access_token' do
+ let(:token) { described_class.access_token(['pull'], [bad_project.full_path]) }
+ let(:access) do
+ [{ 'type' => 'repository',
+ 'name' => bad_project.full_path,
+ 'actions' => ['pull'] }]
+ end
+
+ subject { { token: token } }
+
+ it_behaves_like 'a valid token'
+
+ it 'has the correct scope' do
+ expect(payload).to include('access' => access)
+ end
+ end
+ end
end