| Java |
- 8, 11, 13, 14, 15, or 16 |
+ 8, 11, 13, 14, 15, 16, or 17 |
Gradle1 |
@@ -335,26 +335,60 @@ To support the following package managers, the GitLab analyzers proceed in two s
1. Execute the package manager or a specific task, to export the dependency information.
1. Parse the exported dependency information.
-| Package Manager | Preinstalled Versions | Tested Versions |
-| ------ | ------ | ------ |
-| Bundler | [2.1.4](https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit/-/blob/v2.11.3/Dockerfile#L15)1 | [1.17.3](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/master/Gemfile.lock#L118), [2.1.4](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/bundler2-FREEZE/Gemfile.lock#L118) |
-| sbt | [1.6.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/config/.tool-versions#L4) | [1.0.4](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L330), [1.1.4](https://gitlab.com/gitlab-org/security-products/tests/scala-sbt-multiproject/-/blob/main/project/build.properties#L1), [1.1.6](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L339), [1.2.8](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L348), [1.3.12](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L357), [1.4.6](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L366), [1.6.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L384) |
-| Maven | [3.6.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.23.0/config/.tool-versions#L3) | [3.6.3](https://gitlab.com/gitlab-org/security-products/tests/java-maven/-/blob/master/pom.xml#L3) |
-| Gradle | [6.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.23.0/config/.tool-versions#L5) | [5.6.4](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/master/gradle/wrapper/gradle-wrapper.properties#L3), [6.5](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-14/gradle/wrapper/gradle-wrapper.properties#L3), [6.7-rc-1](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-15/gradle/wrapper/gradle-wrapper.properties#L3), [6.9](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-14-gradle-6-9/gradle/wrapper/gradle-wrapper.properties#L3), [7.0-rc-2](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-16/gradle/wrapper/gradle-wrapper.properties#L3) |
-| setuptools | [50.3.2](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v2.29.9/Dockerfile#L27) | [57.5.0](https://gitlab.com/gitlab-org/security-products/tests/python-setuptools/-/blob/main/setup.py) |
-| pip | [20.2.4](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v2.29.9/Dockerfile#L26) | [20.x](https://gitlab.com/gitlab-org/security-products/tests/python-pip/-/blob/master/requirements.txt) |
-| Pipenv | [2018.11.26](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python/-/blob/v2.18.4/requirements.txt#L13) | [2018.11.26](https://gitlab.com/gitlab-org/security-products/tests/python-pipenv/-/blob/pipfile-lock-FREEZE/Pipfile.lock#L6)2, [2018.11.26](https://gitlab.com/gitlab-org/security-products/tests/python-pipenv/-/blob/master/Pipfile) |
+| Package Manager | Pre-installed Versions | Tested Versions |
+| ------ | ------ | ------ |
+| Bundler | [2.1.4](https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit/-/blob/v2.11.3/Dockerfile#L15)1 | [1.17.3](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/master/Gemfile.lock#L118), [2.1.4](https://gitlab.com/gitlab-org/security-products/tests/ruby-bundler/-/blob/bundler2-FREEZE/Gemfile.lock#L118) |
+| sbt | [1.6.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/config/.tool-versions#L4) | [1.0.4](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L330), [1.1.4](https://gitlab.com/gitlab-org/security-products/tests/scala-sbt-multiproject/-/blob/main/project/build.properties#L1), [1.1.6](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L339), [1.2.8](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L348), [1.3.12](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L357), [1.4.6](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L366), [1.6.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.24.6/.gitlab-ci.yml#L384) |
+| Maven | [3.6.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.23.0/config/.tool-versions#L3) | [3.6.3](https://gitlab.com/gitlab-org/security-products/tests/java-maven/-/blob/master/pom.xml#L3) |
+| Gradle | [6.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.23.0/config/.tool-versions#L5)2, [7.3.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.26.0/config/.tool-versions#L5)2 | [5.6.4](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/master/gradle/wrapper/gradle-wrapper.properties#L3), [6.5](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-14/gradle/wrapper/gradle-wrapper.properties#L3), [6.7-rc-1](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-15/gradle/wrapper/gradle-wrapper.properties#L3), [6.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.27.1/.gitlab-ci.yml#L289-297)3, [6.9](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-14-gradle-6-9/gradle/wrapper/gradle-wrapper.properties#L3), [7.0-rc-2](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-16/gradle/wrapper/gradle-wrapper.properties#L3), [7.3](https://gitlab.com/gitlab-org/security-products/tests/java-gradle/-/blob/java-14-gradle-7-3/gradle/wrapper/gradle-wrapper.properties#L3), [7.3.3](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven/-/blob/v2.27.1/.gitlab-ci.yml#L299-317)3 |
+| setuptools | [50.3.2](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v2.29.9/Dockerfile#L27) | [57.5.0](https://gitlab.com/gitlab-org/security-products/tests/python-setuptools/-/blob/main/setup.py) |
+| pip | [20.2.4](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium/-/blob/v2.29.9/Dockerfile#L26) | [20.x](https://gitlab.com/gitlab-org/security-products/tests/python-pip/-/blob/master/requirements.txt) |
+| Pipenv | [2018.11.26](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python/-/blob/v2.18.4/requirements.txt#L13) | [2018.11.26](https://gitlab.com/gitlab-org/security-products/tests/python-pipenv/-/blob/pipfile-lock-FREEZE/Pipfile.lock#L6)4, [2018.11.26](https://gitlab.com/gitlab-org/security-products/tests/python-pipenv/-/blob/master/Pipfile) |
-
- The installed version of Bundler is only used for the bundler-audit analyzer, and is not used for gemnasium
+ The pre-installed version of Bundler is only used for the bundler-audit analyzer, and is not used for gemnasium.
-
+
+ Different versions of Java require different versions of Gradle. The versions of Gradle listed in the above table are pre-installed
+ in the analyzer image. The version of Gradle used by the analyzer depends on whether your project uses a gradlew
+ (Gradle wrapper) file or not:
+
+
+ -
+
+ If your project does not use a gradlew file, then the analyzer automatically switches to one of the
+ pre-installed Gradle versions, based on the version of Java specified by the
+ DS_JAVA_VERSION variable.
+
+ You can view the
+ Gradle Java compatibility matrix to see which version
+ of Gradle is selected for each Java version. Note that we only support switching to one of these pre-installed Gradle versions
+ for Java versions 13 to 17.
+
+
+ -
+
+ If your project does use a gradlew file, then the version of Gradle pre-installed in the analyzer image is
+ ignored, and the version specified in your gradlew file is used instead.
+
+
+
+
+ -
+
+
+ These tests confirms that if a gradlew file does not exist, the version of Gradle pre-installed in the analyzer image is used.
+
+
+ -
+
This test confirms that if a Pipfile.lock file is found, it will be used by Gemnasium to scan the exact package versions listed in this file.
@@ -563,7 +597,7 @@ The following variables are used for configuring specific analyzers (used for a
| `GEMNASIUM_DB_REF_NAME` | `gemnasium` | `master` | Branch name for remote repository database. `GEMNASIUM_DB_REMOTE_URL` is required. |
| `DS_REMEDIATE` | `gemnasium` | `"true"` | Enable automatic remediation of vulnerable dependencies. |
| `GEMNASIUM_LIBRARY_SCAN_ENABLED` | `gemnasium` | `"true"` | Enable detecting vulnerabilities in vendored JavaScript libraries. For now, `gemnasium` leverages [`Retire.js`](https://github.com/RetireJS/retire.js) to do this job. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/350512) in GitLab 14.8. |
-| `DS_JAVA_VERSION` | `gemnasium-maven` | `11` | Version of Java. Available versions: `8`, `11`, `13`, `14`, `15`, `16`. |
+| `DS_JAVA_VERSION` | `gemnasium-maven` | `11` | Version of Java. Available versions: `8`, `11`, `13`, `14`, `15`, `16`, `17`. |
| `MAVEN_CLI_OPTS` | `gemnasium-maven` | `"-DskipTests --batch-mode"` | List of command line arguments that are passed to `maven` by the analyzer. See an example for [using private repositories](../index.md#using-private-maven-repositories). |
| `GRADLE_CLI_OPTS` | `gemnasium-maven` | | List of command line arguments that are passed to `gradle` by the analyzer. |
| `SBT_CLI_OPTS` | `gemnasium-maven` | | List of command-line arguments that the analyzer passes to `sbt`. |
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 0e01a47872f..f9a4726c21e 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -217,7 +217,11 @@ cannot change them:
This ensures that your job uses the settings you intend and that they are not overridden by
project-level pipelines.
-##### Avoid parent and child pipelines
+##### Avoid parent and child pipelines in GitLab 14.7 and earlier
+
+NOTE:
+This advice does not apply to GitLab 14.8 and later because [a fix](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78878) added
+compatibility for combining compliance pipelines, and parent and child pipelines.
Compliance pipelines start on the run of _every_ pipeline in a relevant project. This means that if a pipeline in the relevant project
triggers a child pipeline, the compliance pipeline runs first. This can trigger the parent pipeline, instead of the child pipeline.
diff --git a/lib/gitlab/analytics/cycle_analytics/request_params.rb b/lib/gitlab/analytics/cycle_analytics/request_params.rb
index bc9d94ef09c..bc270fd784a 100644
--- a/lib/gitlab/analytics/cycle_analytics/request_params.rb
+++ b/lib/gitlab/analytics/cycle_analytics/request_params.rb
@@ -86,6 +86,7 @@ module Gitlab
def to_data_attributes
{}.tap do |attrs|
+ attrs[:aggregation] = aggregation_attributes if group
attrs[:group] = group_data_attributes if group
attrs[:value_stream] = value_stream_data_attributes.to_json if value_stream
attrs[:created_after] = created_after.to_date.iso8601
@@ -103,6 +104,15 @@ module Gitlab
private
+ def aggregation_attributes
+ aggregation = ::Analytics::CycleAnalytics::Aggregation.safe_create_for_group(group)
+ {
+ enabled: aggregation.enabled.to_s,
+ last_run_at: aggregation.last_incremental_run_at,
+ next_run_at: aggregation.estimated_next_run_at
+ }
+ end
+
def group_data_attributes
{
id: group.id,
diff --git a/lib/gitlab/omniauth_initializer.rb b/lib/gitlab/omniauth_initializer.rb
index a9ff186c7cb..f4984e11c14 100644
--- a/lib/gitlab/omniauth_initializer.rb
+++ b/lib/gitlab/omniauth_initializer.rb
@@ -3,6 +3,7 @@
module Gitlab
class OmniauthInitializer
OAUTH2_TIMEOUT_SECONDS = 10
+ ConfigurationError = Class.new(StandardError)
def initialize(devise_config)
@devise_config = devise_config
@@ -75,16 +76,29 @@ module Gitlab
provider_arguments << provider[argument] if provider[argument]
end
- case provider['args']
+ arguments = provider.fetch('args', {})
+ defaults = provider_defaults(provider)
+
+ case arguments
when Array
- # An Array from the configuration will be expanded.
- provider_arguments.concat provider['args']
+ # An Array from the configuration will be expanded
+ provider_arguments.concat arguments
+ provider_arguments << defaults unless defaults.empty?
when Hash
- defaults = provider_defaults(provider)
- hash_arguments = provider['args'].deep_symbolize_keys.deep_merge(defaults)
+ hash_arguments = arguments.deep_symbolize_keys.deep_merge(defaults)
+ normalized = normalize_hash_arguments(hash_arguments)
# A Hash from the configuration will be passed as is.
- provider_arguments << normalize_hash_arguments(hash_arguments)
+ provider_arguments << normalized unless normalized.empty?
+ else
+ # this will prevent the application from starting in development mode.
+ # we still set defaults, and let the application start in prod.
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(
+ ConfigurationError.new("Arguments were provided for #{provider['name']}, but not as an array or a hash"),
+ provider_name: provider['name'],
+ arguments_type: arguments.class.name
+ )
+ provider_arguments << defaults unless defaults.empty?
end
provider_arguments
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index c0108d85365..a0b42598962 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -23,6 +23,12 @@ module QA
end
end
+ def filter_by_name(name)
+ within_element(:project_filter_form) do
+ fill_in :name, with: name
+ end
+ end
+
def go_to_project(name)
filter_by_name(name)
@@ -40,14 +46,6 @@ module QA
def clear_project_filter
fill_element(:project_filter_form, "")
end
-
- private
-
- def filter_by_name(name)
- within_element(:project_filter_form) do
- fill_in :name, with: name
- end
- end
end
end
end
diff --git a/qa/qa/page/modal/delete_issue.rb b/qa/qa/page/modal/delete_issue.rb
new file mode 100644
index 00000000000..9b51e969b48
--- /dev/null
+++ b/qa/qa/page/modal/delete_issue.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Modal
+ class DeleteIssue < Base
+ view 'app/assets/javascripts/issues/show/components/delete_issue_modal.vue' do
+ element :confirm_delete_issue_button, required: true
+ end
+
+ def confirm_delete_issue
+ click_element :confirm_delete_issue_button
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index b37210f4d3f..fe468de60cd 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -18,6 +18,8 @@ module QA
view 'app/assets/javascripts/issues/show/components/header_actions.vue' do
element :close_issue_button
element :reopen_issue_button
+ element :issue_actions_ellipsis_dropdown
+ element :delete_issue_button
end
view 'app/assets/javascripts/related_issues/components/add_issuable_form.vue' do
@@ -69,6 +71,20 @@ module QA
def has_reopen_issue_button?
has_element?(:reopen_issue_button)
end
+
+ def has_delete_issue_button?
+ click_element(:issue_actions_ellipsis_dropdown)
+ has_element?(:delete_issue_button)
+ end
+
+ def delete_issue
+ click_element(:issue_actions_ellipsis_dropdown)
+ click_element(:delete_issue_button, Page::Modal::DeleteIssue)
+
+ Page::Modal::DeleteIssue.perform(&:confirm_delete_issue)
+
+ wait_for_requests
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb
new file mode 100644
index 00000000000..2aefa1c39ed
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage' do
+ describe 'Personal project permissions' do
+ let!(:owner) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
+
+ let!(:owner_api_client) { Runtime::API::Client.new(:gitlab, user: owner) }
+
+ let!(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.api_client = owner_api_client
+ project.name = 'qa-owner-personal-project'
+ project.personal_namespace = owner.username
+ end
+ end
+
+ after do
+ project&.remove_via_api!
+ end
+
+ context 'when user is added as Owner' do
+ let(:issue) do
+ Resource::Issue.fabricate_via_api! do |issue|
+ issue.api_client = owner_api_client
+ issue.project = project
+ issue.title = 'Test Owner deletes issue'
+ end
+ end
+
+ before do
+ Flow::Login.sign_in(as: owner)
+ end
+
+ it "has Owner role with Owner permissions", testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352542' do
+ Page::Dashboard::Projects.perform do |projects|
+ projects.filter_by_name(project.name)
+
+ expect(projects).to have_project_with_access_role(project.name, 'Owner')
+ end
+
+ expect_owner_permissions_allow_delete_issue
+ end
+ end
+
+ context 'when user is added as Maintainer' do
+ let(:maintainer) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) }
+
+ let(:issue) do
+ Resource::Issue.fabricate_via_api! do |issue|
+ issue.api_client = owner_api_client
+ issue.project = project
+ issue.title = 'Test Maintainer deletes issue'
+ end
+ end
+
+ before do
+ project.add_member(maintainer, Resource::Members::AccessLevel::MAINTAINER)
+ Flow::Login.sign_in(as: maintainer)
+ end
+
+ it "has Maintainer role without Owner permissions", testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352607' do
+ Page::Dashboard::Projects.perform do |projects|
+ projects.filter_by_name(project.name)
+
+ expect(projects).to have_project_with_access_role(project.name, 'Maintainer')
+ end
+
+ expect_maintainer_permissions_do_not_allow_delete_issue
+ end
+ end
+
+ private
+
+ def expect_owner_permissions_allow_delete_issue
+ expect do
+ issue.visit!
+
+ Page::Project::Issue::Show.perform(&:delete_issue)
+
+ Page::Project::Issue::Index.perform do |index|
+ expect(index).not_to have_issue(issue)
+ end
+ end.not_to raise_error
+ end
+
+ def expect_maintainer_permissions_do_not_allow_delete_issue
+ expect do
+ issue.visit!
+
+ Page::Project::Issue::Show.perform do |issue|
+ expect(issue).not_to have_delete_issue_button
+ end
+ end.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/fast_spec_helper.rb b/spec/fast_spec_helper.rb
index 1485edcd97d..ce3c9af22f1 100644
--- a/spec/fast_spec_helper.rb
+++ b/spec/fast_spec_helper.rb
@@ -18,6 +18,9 @@ require_relative '../config/settings'
require_relative 'support/rspec'
require 'active_support/all'
+require_relative 'simplecov_env'
+SimpleCovEnv.start!
+
unless ActiveSupport::Dependencies.autoload_paths.frozen?
ActiveSupport::Dependencies.autoload_paths << 'lib'
ActiveSupport::Dependencies.autoload_paths << 'ee/lib'
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index 0397e72502a..baa691d244e 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -72,6 +72,10 @@ RSpec.describe 'Global search' do
# TODO: Remove this along with feature flag #339348
stub_feature_flags(new_header_search: true)
visit dashboard_projects_path
+
+ # intialize javascript loaded input search input field
+ find('#search').click
+ find('body').click
end
it 'renders updated search bar' do
diff --git a/spec/frontend/admin/users/components/user_actions_spec.js b/spec/frontend/admin/users/components/user_actions_spec.js
index 43313424553..b90a30b5b89 100644
--- a/spec/frontend/admin/users/components/user_actions_spec.js
+++ b/spec/frontend/admin/users/components/user_actions_spec.js
@@ -77,6 +77,12 @@ describe('AdminUserActions component', () => {
expect(findActionsDropdown().exists()).toBe(true);
});
+ it('renders the tooltip', () => {
+ const tooltip = getBinding(findActionsDropdown().element, 'gl-tooltip');
+
+ expect(tooltip.value).toBe(I18N_USER_ACTIONS.userAdministration);
+ });
+
describe('when there are actions that require confirmation', () => {
beforeEach(() => {
initComponent({ actions: CONFIRMATION_ACTIONS });
@@ -152,7 +158,7 @@ describe('AdminUserActions component', () => {
describe('when `showButtonLabels` prop is `false`', () => {
beforeEach(() => {
- initComponent({ actions: [EDIT, ...CONFIRMATION_ACTIONS] });
+ initComponent({ actions: [EDIT] });
});
it('does not render "Edit" button label', () => {
@@ -163,16 +169,11 @@ describe('AdminUserActions component', () => {
expect(tooltip).toBeDefined();
expect(tooltip.value).toBe(I18N_USER_ACTIONS.edit);
});
-
- it('does not render "User administration" dropdown button label', () => {
- expect(findActionsDropdown().props('text')).toBe(I18N_USER_ACTIONS.userAdministration);
- expect(findActionsDropdown().props('textSrOnly')).toBe(true);
- });
});
describe('when `showButtonLabels` prop is `true`', () => {
beforeEach(() => {
- initComponent({ actions: [EDIT, ...CONFIRMATION_ACTIONS], showButtonLabels: true });
+ initComponent({ actions: [EDIT], showButtonLabels: true });
});
it('renders "Edit" button label', () => {
@@ -181,10 +182,5 @@ describe('AdminUserActions component', () => {
expect(findEditButton().text()).toBe(I18N_USER_ACTIONS.edit);
expect(tooltip).not.toBeDefined();
});
-
- it('renders "User administration" dropdown button label', () => {
- expect(findActionsDropdown().props('text')).toBe(I18N_USER_ACTIONS.userAdministration);
- expect(findActionsDropdown().props('textSrOnly')).toBe(false);
- });
});
});
diff --git a/spec/frontend/clusters_list/components/clusters_actions_spec.js b/spec/frontend/clusters_list/components/clusters_actions_spec.js
index 331690fc642..2cbd12a680b 100644
--- a/spec/frontend/clusters_list/components/clusters_actions_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_actions_spec.js
@@ -14,10 +14,13 @@ describe('ClustersActionsComponent', () => {
newClusterPath,
addClusterPath,
canAddCluster: true,
+ displayClusterAgents: true,
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findDropdownItemIds = () =>
+ findDropdownItems().wrappers.map((x) => x.attributes('data-testid'));
const findNewClusterLink = () => wrapper.findByTestId('new-cluster-link');
const findConnectClusterLink = () => wrapper.findByTestId('connect-cluster-link');
const findConnectNewAgentLink = () => wrapper.findByTestId('connect-new-agent-link');
@@ -47,26 +50,11 @@ describe('ClustersActionsComponent', () => {
expect(findDropdown().props('text')).toBe(CLUSTERS_ACTIONS.actionsButton);
});
- it('renders a dropdown with 3 actions items', () => {
- expect(findDropdownItems()).toHaveLength(3);
- });
-
it('renders correct href attributes for the links', () => {
expect(findNewClusterLink().attributes('href')).toBe(newClusterPath);
expect(findConnectClusterLink().attributes('href')).toBe(addClusterPath);
});
- it('renders correct modal id for the agent link', () => {
- const binding = getBinding(findConnectNewAgentLink().element, 'gl-modal-directive');
-
- expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
- });
-
- it('shows tooltip', () => {
- const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
- expect(tooltip.value).toBe(CLUSTERS_ACTIONS.connectWithAgent);
- });
-
describe('when user cannot add clusters', () => {
beforeEach(() => {
createWrapper({ canAddCluster: false });
@@ -80,5 +68,67 @@ describe('ClustersActionsComponent', () => {
const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
expect(tooltip.value).toBe(CLUSTERS_ACTIONS.dropdownDisabledHint);
});
+
+ it('does not bind split dropdown button', () => {
+ const binding = getBinding(findDropdown().element, 'gl-modal-directive');
+
+ expect(binding.value).toBe(false);
+ });
+ });
+
+ describe('when on project level', () => {
+ it('renders a dropdown with 3 actions items', () => {
+ expect(findDropdownItemIds()).toEqual([
+ 'connect-new-agent-link',
+ 'new-cluster-link',
+ 'connect-cluster-link',
+ ]);
+ });
+
+ it('renders correct modal id for the agent link', () => {
+ const binding = getBinding(findConnectNewAgentLink().element, 'gl-modal-directive');
+
+ expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
+ });
+
+ it('shows tooltip', () => {
+ const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
+ expect(tooltip.value).toBe(CLUSTERS_ACTIONS.connectWithAgent);
+ });
+
+ it('shows split button in dropdown', () => {
+ expect(findDropdown().props('split')).toBe(true);
+ });
+
+ it('binds split button with modal id', () => {
+ const binding = getBinding(findDropdown().element, 'gl-modal-directive');
+
+ expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
+ });
+ });
+
+ describe('when on group or admin level', () => {
+ beforeEach(() => {
+ createWrapper({ displayClusterAgents: false });
+ });
+
+ it('renders a dropdown with 2 actions items', () => {
+ expect(findDropdownItemIds()).toEqual(['new-cluster-link', 'connect-cluster-link']);
+ });
+
+ it('shows tooltip', () => {
+ const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
+ expect(tooltip.value).toBe(CLUSTERS_ACTIONS.connectExistingCluster);
+ });
+
+ it('does not show split button in dropdown', () => {
+ expect(findDropdown().props('split')).toBe(false);
+ });
+
+ it('does not bind dropdown button to modal', () => {
+ const binding = getBinding(findDropdown().element, 'gl-modal-directive');
+
+ expect(binding.value).toBe(false);
+ });
});
});
diff --git a/spec/frontend/clusters_list/components/clusters_main_view_spec.js b/spec/frontend/clusters_list/components/clusters_main_view_spec.js
index 4306a906737..20754ffe5b7 100644
--- a/spec/frontend/clusters_list/components/clusters_main_view_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_main_view_spec.js
@@ -7,6 +7,7 @@ import {
AGENT,
CERTIFICATE_BASED,
CLUSTERS_TABS,
+ CERTIFICATE_TAB,
MAX_CLUSTERS_LIST,
MAX_LIST_COUNT,
EVENT_LABEL_TABS,
@@ -23,12 +24,12 @@ describe('ClustersMainViewComponent', () => {
defaultBranchName,
};
- beforeEach(() => {
+ const createWrapper = ({ displayClusterAgents }) => {
wrapper = shallowMountExtended(ClustersMainView, {
propsData,
+ provide: { displayClusterAgents },
});
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
+ };
afterEach(() => {
wrapper.destroy();
@@ -40,66 +41,90 @@ describe('ClustersMainViewComponent', () => {
const findComponent = () => wrapper.findByTestId('clusters-tab-component');
const findModal = () => wrapper.findComponent(InstallAgentModal);
- it('renders `GlTabs` with `syncActiveTabWithQueryParams` and `queryParamName` props set', () => {
- expect(findTabs().exists()).toBe(true);
- expect(findTabs().props('syncActiveTabWithQueryParams')).toBe(true);
- });
+ describe('when on project level', () => {
+ beforeEach(() => {
+ createWrapper({ displayClusterAgents: true });
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ });
- it('renders correct number of tabs', () => {
- expect(findAllTabs()).toHaveLength(CLUSTERS_TABS.length);
- });
+ it('renders `GlTabs` with `syncActiveTabWithQueryParams` and `queryParamName` props set', () => {
+ expect(findTabs().exists()).toBe(true);
+ expect(findTabs().props('syncActiveTabWithQueryParams')).toBe(true);
+ });
- describe('tabs', () => {
- it.each`
- tabTitle | queryParamValue | lineNumber
- ${'All'} | ${'all'} | ${0}
- ${'Agent'} | ${AGENT} | ${1}
- ${'Certificate'} | ${CERTIFICATE_BASED} | ${2}
+ it('renders correct number of tabs', () => {
+ expect(findAllTabs()).toHaveLength(CLUSTERS_TABS.length);
+ });
+
+ describe('tabs', () => {
+ it.each`
+ tabTitle | queryParamValue | lineNumber
+ ${'All'} | ${'all'} | ${0}
+ ${'Agent'} | ${AGENT} | ${1}
+ ${'Certificate'} | ${CERTIFICATE_BASED} | ${2}
+ `(
+ 'renders correct tab title and query param value',
+ ({ tabTitle, queryParamValue, lineNumber }) => {
+ expect(findGlTabAtIndex(lineNumber).attributes('title')).toBe(tabTitle);
+ expect(findGlTabAtIndex(lineNumber).props('queryParamValue')).toBe(queryParamValue);
+ },
+ );
+ });
+
+ describe.each`
+ tab | tabName
+ ${'1'} | ${AGENT}
+ ${'2'} | ${CERTIFICATE_BASED}
`(
- 'renders correct tab title and query param value',
- ({ tabTitle, queryParamValue, lineNumber }) => {
- expect(findGlTabAtIndex(lineNumber).attributes('title')).toBe(tabTitle);
- expect(findGlTabAtIndex(lineNumber).props('queryParamValue')).toBe(queryParamValue);
+ 'when the child component emits the tab change event for $tabName tab',
+ ({ tab, tabName }) => {
+ beforeEach(() => {
+ findComponent().vm.$emit('changeTab', tabName);
+ });
+
+ it(`changes the tab value to ${tab}`, () => {
+ expect(findTabs().attributes('value')).toBe(tab);
+ });
},
);
- });
- describe.each`
- tab | tabName
- ${'1'} | ${AGENT}
- ${'2'} | ${CERTIFICATE_BASED}
- `('when the child component emits the tab change event for $tabName tab', ({ tab, tabName }) => {
- beforeEach(() => {
- findComponent().vm.$emit('changeTab', tabName);
- });
+ describe.each`
+ tab | tabName | maxAgents
+ ${1} | ${AGENT} | ${MAX_LIST_COUNT}
+ ${2} | ${CERTIFICATE_BASED} | ${MAX_CLUSTERS_LIST}
+ `('when the active tab is $tabName', ({ tab, tabName, maxAgents }) => {
+ beforeEach(() => {
+ findTabs().vm.$emit('input', tab);
+ });
- it(`changes the tab value to ${tab}`, () => {
- expect(findTabs().attributes('value')).toBe(tab);
- });
- });
+ it('passes child-component param to the component', () => {
+ expect(findComponent().props('defaultBranchName')).toBe(defaultBranchName);
+ });
- describe.each`
- tab | tabName | maxAgents
- ${1} | ${AGENT} | ${MAX_LIST_COUNT}
- ${2} | ${CERTIFICATE_BASED} | ${MAX_CLUSTERS_LIST}
- `('when the active tab is $tabName', ({ tab, tabName, maxAgents }) => {
- beforeEach(() => {
- findTabs().vm.$emit('input', tab);
- });
+ it(`sets max-agents param to ${maxAgents} and passes it to the modal`, () => {
+ expect(findModal().props('maxAgents')).toBe(maxAgents);
+ });
- it('passes child-component param to the component', () => {
- expect(findComponent().props('defaultBranchName')).toBe(defaultBranchName);
- });
-
- it(`sets max-agents param to ${maxAgents} and passes it to the modal`, () => {
- expect(findModal().props('maxAgents')).toBe(maxAgents);
- });
-
- it(`sends the correct tracking event with the property '${tabName}'`, () => {
- expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_CHANGE, {
- label: EVENT_LABEL_TABS,
- property: tabName,
+ it(`sends the correct tracking event with the property '${tabName}'`, () => {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_CHANGE, {
+ label: EVENT_LABEL_TABS,
+ property: tabName,
+ });
});
});
});
+
+ describe('when on group or admin level', () => {
+ beforeEach(() => {
+ createWrapper({ displayClusterAgents: false });
+ });
+
+ it('renders correct number of tabs', () => {
+ expect(findAllTabs()).toHaveLength(1);
+ });
+
+ it('renders correct tab title', () => {
+ expect(findGlTabAtIndex(0).attributes('title')).toBe(CERTIFICATE_TAB.title);
+ });
+ });
});
diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb
index 18d233fcd63..433de813e3d 100644
--- a/spec/helpers/clusters_helper_spec.rb
+++ b/spec/helpers/clusters_helper_spec.rb
@@ -92,6 +92,10 @@ RSpec.describe ClustersHelper do
expect(subject[:new_cluster_path]).to eq("#{project_path(project)}/-/clusters/new?tab=create")
end
+ it 'displays add cluster using certificate path' do
+ expect(subject[:add_cluster_path]).to eq("#{project_path(project)}/-/clusters/new?tab=add")
+ end
+
context 'user has no permissions to create a cluster' do
it 'displays that user can\'t add cluster' do
expect(subject[:can_add_cluster]).to eq("false")
@@ -114,6 +118,10 @@ RSpec.describe ClustersHelper do
it 'doesn\'t display empty state help text' do
expect(subject[:empty_state_help_text]).to be_nil
end
+
+ it 'displays display_cluster_agents as true' do
+ expect(subject[:display_cluster_agents]).to eq("true")
+ end
end
context 'group cluster' do
@@ -123,6 +131,10 @@ RSpec.describe ClustersHelper do
it 'displays empty state help text' do
expect(subject[:empty_state_help_text]).to eq(s_('ClusterIntegration|Adding an integration to your group will share the cluster across all your projects.'))
end
+
+ it 'displays display_cluster_agents as false' do
+ expect(subject[:display_cluster_agents]).to eq("false")
+ end
end
end
@@ -145,10 +157,6 @@ RSpec.describe ClustersHelper do
expect(subject[:project_path]).to eq(project.full_path)
end
- it 'displays add cluster using certificate path' do
- expect(subject[:add_cluster_path]).to eq("#{project_path(project)}/-/clusters/new?tab=add")
- end
-
it 'displays kas address' do
expect(subject[:kas_address]).to eq(Gitlab::Kas.external_url)
end
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index 42ae5844b95..8b959cf787f 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -5,7 +5,161 @@ require 'spec_helper'
RSpec.describe Gitlab::OmniauthInitializer do
let(:devise_config) { class_double(Devise) }
- subject { described_class.new(devise_config) }
+ subject(:initializer) { described_class.new(devise_config) }
+
+ describe '.arguments_for' do
+ let(:devise_config) { nil }
+
+ let(:arguments) { initializer.send(:arguments_for, provider) }
+
+ context 'when there are no args at all' do
+ let(:provider) { { 'name' => 'unknown' } }
+
+ it 'returns an empty array' do
+ expect(arguments).to eq []
+ end
+ end
+
+ context 'when there is an app_id and an app_secret' do
+ let(:provider) { { 'name' => 'unknown', 'app_id' => 1, 'app_secret' => 2 } }
+
+ it 'includes both of them, in positional order' do
+ expect(arguments).to eq [1, 2]
+ end
+ end
+
+ context 'when there is an app_id and an app_secret, and an array of args' do
+ let(:provider) do
+ {
+ 'name' => 'unknown',
+ 'app_id' => 1,
+ 'app_secret' => 2,
+ 'args' => %w[one two three]
+ }
+ end
+
+ it 'concatenates the args on the end' do
+ expect(arguments).to eq [1, 2, 'one', 'two', 'three']
+ end
+ end
+
+ context 'when there is an app_id and an app_secret, and an array of args, and default values' do
+ let(:provider) do
+ {
+ 'name' => 'unknown',
+ 'app_id' => 1,
+ 'app_secret' => 2,
+ 'args' => %w[one two three]
+ }
+ end
+
+ before do
+ expect(described_class)
+ .to receive(:default_arguments_for).with('unknown')
+ .and_return({ default_arg: :some_value })
+ end
+
+ it 'concatenates the args on the end' do
+ expect(arguments)
+ .to eq [1, 2, 'one', 'two', 'three', { default_arg: :some_value }]
+ end
+ end
+
+ context 'when there is an app_id and an app_secret, and a hash of args' do
+ let(:provider) do
+ {
+ 'name' => 'unknown',
+ 'app_id' => 1,
+ 'app_secret' => 2,
+ 'args' => { 'foo' => 100, 'bar' => 200, 'nested' => { 'value' => 300 } }
+ }
+ end
+
+ it 'concatenates the args on the end' do
+ expect(arguments)
+ .to eq [1, 2, { foo: 100, bar: 200, nested: { value: 300 } }]
+ end
+ end
+
+ context 'when there is an app_id and an app_secret, and a hash of args, and default arguments' do
+ let(:provider) do
+ {
+ 'name' => 'unknown',
+ 'app_id' => 1,
+ 'app_secret' => 2,
+ 'args' => { 'foo' => 100, 'bar' => 200, 'nested' => { 'value' => 300 } }
+ }
+ end
+
+ before do
+ expect(described_class)
+ .to receive(:default_arguments_for).with('unknown')
+ .and_return({ default_arg: :some_value })
+ end
+
+ it 'concatenates the args on the end' do
+ expect(arguments)
+ .to eq [1, 2, { default_arg: :some_value, foo: 100, bar: 200, nested: { value: 300 } }]
+ end
+ end
+
+ context 'when there is an app_id and an app_secret, no args, and default values' do
+ let(:provider) do
+ {
+ 'name' => 'unknown',
+ 'app_id' => 1,
+ 'app_secret' => 2
+ }
+ end
+
+ before do
+ expect(described_class)
+ .to receive(:default_arguments_for).with('unknown')
+ .and_return({ default_arg: :some_value })
+ end
+
+ it 'concatenates the args on the end' do
+ expect(arguments)
+ .to eq [1, 2, { default_arg: :some_value }]
+ end
+ end
+
+ context 'when there are args, of an unsupported type' do
+ let(:provider) do
+ {
+ 'name' => 'unknown',
+ 'args' => 1
+ }
+ end
+
+ context 'when there are default arguments' do
+ before do
+ expect(described_class)
+ .to receive(:default_arguments_for).with('unknown')
+ .and_return({ default_arg: :some_value })
+ end
+
+ it 'tracks a configuration error' do
+ expect(::Gitlab::ErrorTracking)
+ .to receive(:track_and_raise_for_dev_exception)
+ .with(described_class::ConfigurationError, provider_name: 'unknown', arguments_type: 'Integer')
+
+ expect(arguments)
+ .to eq [{ default_arg: :some_value }]
+ end
+ end
+
+ context 'when there are no default arguments' do
+ it 'tracks a configuration error' do
+ expect(::Gitlab::ErrorTracking)
+ .to receive(:track_and_raise_for_dev_exception)
+ .with(described_class::ConfigurationError, provider_name: 'unknown', arguments_type: 'Integer')
+
+ expect(arguments).to be_empty
+ end
+ end
+ end
+ end
describe '#execute' do
it 'configures providers from array' do
@@ -105,9 +259,48 @@ RSpec.describe Gitlab::OmniauthInitializer do
it 'configures defaults for gitlab' do
conf = {
'name' => 'gitlab',
- "args" => {}
+ "args" => { 'client_options' => { 'site' => generate(:url) } }
}
+ expect(devise_config).to receive(:omniauth).with(
+ :gitlab,
+ client_options: { site: conf.dig('args', 'client_options', 'site') },
+ authorize_params: { gl_auth_type: 'login' }
+ )
+
+ subject.execute([conf])
+ end
+
+ it 'configures defaults for gitlab, when arguments are not provided' do
+ conf = { 'name' => 'gitlab' }
+
+ expect(devise_config).to receive(:omniauth).with(
+ :gitlab,
+ authorize_params: { gl_auth_type: 'login' }
+ )
+
+ subject.execute([conf])
+ end
+
+ it 'configures defaults for gitlab, when array arguments are provided' do
+ conf = { 'name' => 'gitlab', 'args' => ['a'] }
+
+ expect(devise_config).to receive(:omniauth).with(
+ :gitlab,
+ 'a',
+ authorize_params: { gl_auth_type: 'login' }
+ )
+
+ subject.execute([conf])
+ end
+
+ it 'tracks a configuration error if the arguments are neither a hash nor an array' do
+ conf = { 'name' => 'gitlab', 'args' => 17 }
+
+ expect(::Gitlab::ErrorTracking)
+ .to receive(:track_and_raise_for_dev_exception)
+ .with(described_class::ConfigurationError, provider_name: 'gitlab', arguments_type: 'Integer')
+
expect(devise_config).to receive(:omniauth).with(
:gitlab,
authorize_params: { gl_auth_type: 'login' }
diff --git a/spec/models/analytics/cycle_analytics/aggregation_spec.rb b/spec/models/analytics/cycle_analytics/aggregation_spec.rb
index e4832cf3f27..cb439430dd4 100644
--- a/spec/models/analytics/cycle_analytics/aggregation_spec.rb
+++ b/spec/models/analytics/cycle_analytics/aggregation_spec.rb
@@ -25,18 +25,16 @@ RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model do
let_it_be(:subgroup) { create(:group, parent: group) }
it 'creates the aggregation record' do
- described_class.safe_create_for_group(group)
+ record = described_class.safe_create_for_group(group)
- record = described_class.find_by(group_id: group)
- expect(record).to be_present
+ expect(record).to be_persisted
end
context 'when non top-level group is given' do
it 'creates the aggregation record for the top-level group' do
- described_class.safe_create_for_group(subgroup)
+ record = described_class.safe_create_for_group(subgroup)
- record = described_class.find_by(group_id: group)
- expect(record).to be_present
+ expect(record).to be_persisted
end
end
@@ -75,4 +73,56 @@ RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model do
expect(last_two).to eq([aggregation5, aggregation2])
end
end
+
+ describe '#estimated_next_run_at' do
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ context 'when aggregation was not yet executed for the given group' do
+ let(:aggregation) { create(:cycle_analytics_aggregation, last_incremental_run_at: nil) }
+
+ it { expect(aggregation.estimated_next_run_at).to eq(nil) }
+ end
+
+ context 'when aggregation was already run' do
+ let!(:other_aggregation1) { create(:cycle_analytics_aggregation, last_incremental_run_at: 10.minutes.ago) }
+ let!(:other_aggregation2) { create(:cycle_analytics_aggregation, last_incremental_run_at: 15.minutes.ago) }
+ let!(:aggregation) { create(:cycle_analytics_aggregation, last_incremental_run_at: 5.minutes.ago) }
+
+ it 'returns the duration between the previous run timestamp and the earliest last_incremental_run_at' do
+ expect(aggregation.estimated_next_run_at).to eq(10.minutes.from_now)
+ end
+
+ context 'when the aggregation has persisted previous runtimes' do
+ before do
+ aggregation.update!(incremental_runtimes_in_seconds: [30, 60, 90])
+ end
+
+ it 'adds the runtime to the estimation' do
+ expect(aggregation.estimated_next_run_at).to eq((10.minutes.seconds + 60.seconds).from_now)
+ end
+ end
+ end
+
+ context 'when no records are present in the DB' do
+ it 'returns nil' do
+ expect(described_class.new.estimated_next_run_at).to eq(nil)
+ end
+ end
+
+ context 'when only one aggregation record present' do
+ let!(:aggregation) { create(:cycle_analytics_aggregation, last_incremental_run_at: 5.minutes.ago) }
+
+ it 'returns nil' do
+ expect(aggregation.estimated_next_run_at).to eq(nil)
+ end
+ end
+
+ context 'when the aggregation is disabled' do
+ it 'returns nil' do
+ expect(described_class.new(enabled: false).estimated_next_run_at).to eq(nil)
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/runners_token_prefixable_spec.rb b/spec/models/concerns/runners_token_prefixable_spec.rb
new file mode 100644
index 00000000000..29e7b8cf4f4
--- /dev/null
+++ b/spec/models/concerns/runners_token_prefixable_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe RunnersTokenPrefixable do
+ describe 'runners token prefix' do
+ subject { described_class::RUNNERS_TOKEN_PREFIX }
+
+ it 'has the correct value' do
+ expect(subject).to eq('GR1348941')
+ end
+ end
+end
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 5fec8ad1113..d7bfcc3f579 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -441,7 +441,7 @@ RSpec.shared_examples 'prefixed token rotation' do
context 'token is not set' do
it 'generates a new token' do
- expect(subject).to match(/^#{instance.class::RUNNERS_TOKEN_PREFIX}/)
+ expect(subject).to match(/^#{RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX}/)
expect(instance).not_to be_persisted
end
end
@@ -452,26 +452,14 @@ RSpec.shared_examples 'prefixed token rotation' do
end
it 'generates a new token' do
- expect(subject).to match(/^#{instance.class::RUNNERS_TOKEN_PREFIX}/)
+ expect(subject).to match(/^#{RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX}/)
expect(instance).not_to be_persisted
end
-
- context 'feature flag is disabled' do
- before do
- flag = "#{described_class.name.downcase.pluralize}_runners_token_prefix"
- stub_feature_flags(flag => false)
- end
-
- it 'leaves the token unchanged' do
- expect { subject }.not_to change(instance, :runners_token)
- expect(instance).not_to be_persisted
- end
- end
end
context 'token is set and matches prefix' do
before do
- instance.set_runners_token(instance.class::RUNNERS_TOKEN_PREFIX + '-abcdef')
+ instance.set_runners_token(RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX + '-abcdef')
end
it 'leaves the token unchanged' do
@@ -486,7 +474,7 @@ RSpec.shared_examples 'prefixed token rotation' do
context 'token is not set' do
it 'generates a new token' do
- expect(subject).to match(/^#{instance.class::RUNNERS_TOKEN_PREFIX}/)
+ expect(subject).to match(/^#{RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX}/)
expect(instance).to be_persisted
end
end
@@ -497,25 +485,14 @@ RSpec.shared_examples 'prefixed token rotation' do
end
it 'generates a new token' do
- expect(subject).to match(/^#{instance.class::RUNNERS_TOKEN_PREFIX}/)
+ expect(subject).to match(/^#{RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX}/)
expect(instance).to be_persisted
end
-
- context 'feature flag is disabled' do
- before do
- flag = "#{described_class.name.downcase.pluralize}_runners_token_prefix"
- stub_feature_flags(flag => false)
- end
-
- it 'leaves the token unchanged' do
- expect { subject }.not_to change(instance, :runners_token)
- end
- end
end
context 'token is set and matches prefix' do
before do
- instance.set_runners_token(instance.class::RUNNERS_TOKEN_PREFIX + '-abcdef')
+ instance.set_runners_token(RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX + '-abcdef')
instance.save!
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 98fad544abd..6a8f9826481 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -3239,12 +3239,4 @@ RSpec.describe Group do
it_behaves_like 'no effective expiration interval'
end
end
-
- describe '#runners_token' do
- let_it_be(:group) { create(:group) }
-
- subject { group }
-
- it_behaves_like 'it has a prefixable runners_token', :groups_runners_token_prefix
- end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d2d06b64cb3..b251e8dfe9e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -813,8 +813,8 @@ RSpec.describe Project, factory_default: :keep do
end
it 'does not set an random token if one provided' do
- project = FactoryBot.create(:project, runners_token: "#{Project::RUNNERS_TOKEN_PREFIX}my-token")
- expect(project.runners_token).to eq("#{Project::RUNNERS_TOKEN_PREFIX}my-token")
+ project = FactoryBot.create(:project, runners_token: "#{RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX}my-token")
+ expect(project.runners_token).to eq("#{RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX}my-token")
end
end
@@ -8077,14 +8077,6 @@ RSpec.describe Project, factory_default: :keep do
end
end
- describe '#runners_token' do
- let_it_be(:project) { create(:project) }
-
- subject { project }
-
- it_behaves_like 'it has a prefixable runners_token', :projects_runners_token_prefix
- end
-
private
def finish_job(export_job)
diff --git a/spec/rubocop/cop/database/establish_connection_spec.rb b/spec/rubocop/cop/database/establish_connection_spec.rb
index a3c27d33cb0..3919872b5e7 100644
--- a/spec/rubocop/cop/database/establish_connection_spec.rb
+++ b/spec/rubocop/cop/database/establish_connection_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/database/establish_connection'
RSpec.describe RuboCop::Cop::Database::EstablishConnection do
diff --git a/spec/support/shared_examples/models/runners_token_prefix_shared_examples.rb b/spec/support/shared_examples/models/runners_token_prefix_shared_examples.rb
deleted file mode 100644
index bbce67ae7b9..00000000000
--- a/spec/support/shared_examples/models/runners_token_prefix_shared_examples.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'it has a prefixable runners_token' do |feature_flag|
- context 'feature flag enabled' do
- before do
- stub_feature_flags(feature_flag => [subject])
- end
-
- describe '#runners_token' do
- it 'has a runners_token_prefix' do
- expect(subject.runners_token_prefix).not_to be_empty
- end
-
- it 'starts with the runners_token_prefix' do
- expect(subject.runners_token).to start_with(subject.runners_token_prefix)
- end
- end
- end
-
- context 'feature flag disabled' do
- before do
- stub_feature_flags(feature_flag => false)
- end
-
- describe '#runners_token' do
- it 'does not have a runners_token_prefix' do
- expect(subject.runners_token_prefix).to be_empty
- end
-
- it 'starts with the runners_token_prefix' do
- expect(subject.runners_token).to start_with(subject.runners_token_prefix)
- end
- end
- end
-end
diff --git a/spec/views/layouts/_header_search.html.haml_spec.rb b/spec/views/layouts/_header_search.html.haml_spec.rb
new file mode 100644
index 00000000000..3ab4ae6a483
--- /dev/null
+++ b/spec/views/layouts/_header_search.html.haml_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'layouts/_header_search' do
+ let(:project) { nil }
+ let(:group) { nil }
+ let(:scope) { nil }
+ let(:ref) { nil }
+ let(:code_search) { false }
+ let(:for_snippets) { false}
+
+ let(:header_search_context) do
+ {
+ project: project,
+ group: group,
+ scope: scope,
+ ref: ref,
+ code_search: code_search,
+ for_snippets: for_snippets
+ }
+ end
+
+ before do
+ allow(view).to receive(:header_search_context).and_return(header_search_context)
+ end
+
+ shared_examples 'hidden fields are properly set' do
+ context 'when search_context has a scope value' do
+ let(:scope) { 'issues' }
+
+ it 'sets scope input to issues' do
+ render
+
+ expect(rendered).to have_css("input[name='scope'][value='#{scope}']", count: 1, visible: false)
+ end
+ end
+
+ context 'when search_context has a code_search value' do
+ let(:code_search) { true }
+
+ it 'sets search_code input to true' do
+ render
+
+ expect(rendered).to have_css("input[name='search_code'][value='#{code_search}']", count: 1, visible: false)
+ end
+ end
+
+ context 'when search_context has a ref value' do
+ let(:ref) { 'test-branch' }
+
+ it 'sets repository_ref input to test-branch' do
+ render
+
+ expect(rendered).to have_css("input[name='repository_ref'][value='#{ref}']", count: 1, visible: false)
+ end
+ end
+
+ context 'when search_context has a for_snippets value' do
+ let(:for_snippets) { true }
+
+ it 'sets for_snippets input to true' do
+ render
+
+ expect(rendered).to have_css("input[name='snippets'][value='#{for_snippets}']", count: 1, visible: false)
+ end
+ end
+
+ context 'nav_source' do
+ it 'always set to navbar' do
+ render
+
+ expect(rendered).to have_css("input[name='nav_source'][value='navbar']", count: 1, visible: false)
+ end
+ end
+
+ context 'submit button' do
+ it 'always renders for specs' do
+ render
+
+ expect(rendered).to have_css('noscript button', text: 'Search')
+ end
+ end
+ end
+
+ context 'when doing a project level search' do
+ let(:project) do
+ { id: 123, name: 'foo' }
+ end
+
+ it 'sets project_id field' do
+ render
+
+ expect(rendered).to have_css("input[name='project_id'][value='#{project[:id]}']", count: 1, visible: false)
+ end
+
+ it_behaves_like 'hidden fields are properly set'
+ end
+
+ context 'when doing a group level search' do
+ let(:group) do
+ { id: 123, name: 'bar' }
+ end
+
+ it 'sets group_id field' do
+ render
+
+ expect(rendered).to have_css("input[name='group_id'][value='#{group[:id]}']", count: 1, visible: false)
+ end
+
+ it_behaves_like 'hidden fields are properly set'
+ end
+end
|