diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml
index dde08b15bc3..22fdce71243 100644
--- a/.gitlab/ci/review-apps/main.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml
@@ -77,7 +77,7 @@ review-build-cng:
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
- GITLAB_HELM_CHART_REF: "41d7632d9eba84f5816d808b98ccf3f5623e9fb5"
+ GITLAB_HELM_CHART_REF: "a6a609a19166f00b1a7774374041cd38a9f7e20d"
environment:
name: review/${CI_COMMIT_REF_SLUG}${FREQUENCY}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
diff --git a/Gemfile.lock b/Gemfile.lock
index d457aafc2ed..527ac45b101 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -963,7 +963,7 @@ GEM
pyu-ruby-sasl (0.0.3.3)
raabro (1.1.6)
racc (1.6.0)
- rack (2.2.3)
+ rack (2.2.3.1)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (6.3.0)
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js
index 34845e3d9e4..c97e191b630 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js
@@ -1,7 +1,5 @@
import { s__ } from '~/locale';
-export const PLATFORMS_WITHOUT_ARCHITECTURES = ['docker', 'kubernetes'];
-
export const REGISTRATION_TOKEN_PLACEHOLDER = '$REGISTRATION_TOKEN';
export const INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES = {
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
index 1236cd900d1..f65569b97b5 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
@@ -17,7 +17,6 @@ import { __, s__ } from '~/locale';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
import {
INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES,
- PLATFORMS_WITHOUT_ARCHITECTURES,
REGISTRATION_TOKEN_PLACEHOLDER,
} from './constants';
import getRunnerPlatformsQuery from './graphql/queries/get_runner_platforms.query.graphql';
@@ -60,20 +59,20 @@ export default {
platforms: {
query: getRunnerPlatformsQuery,
update(data) {
- return data?.runnerPlatforms?.nodes.map(({ name, humanReadableName, architectures }) => {
- return {
- name,
- humanReadableName,
- architectures: architectures?.nodes || [],
- };
- });
+ return (
+ data?.runnerPlatforms?.nodes.map(({ name, humanReadableName, architectures }) => {
+ return {
+ name,
+ humanReadableName,
+ architectures: architectures?.nodes || [],
+ };
+ }) ?? []
+ );
},
result() {
- if (this.platforms.length) {
- // If it is set and available, select the defaultSelectedPlatform.
- // Otherwise, select the first available platform
- this.selectPlatform(this.defaultPlatform() || this.platforms[0]);
- }
+ // If it is set and available, select the defaultSelectedPlatform.
+ // Otherwise, select the first available platform
+ this.selectPlatform(this.defaultPlatformName || this.platforms?.[0].name);
},
error() {
this.toggleAlert(true);
@@ -86,8 +85,8 @@ export default {
},
variables() {
return {
- platform: this.selectedPlatformName,
- architecture: this.selectedArchitectureName || '',
+ platform: this.selectedPlatform,
+ architecture: this.selectedArchitecture || '',
};
},
update(data) {
@@ -109,26 +108,21 @@ export default {
};
},
computed: {
- platformsEmpty() {
- return isEmpty(this.platforms);
- },
instructionsEmpty() {
return isEmpty(this.instructions);
},
- selectedPlatformName() {
- return this.selectedPlatform?.name;
+ architectures() {
+ return this.platforms.find(({ name }) => name === this.selectedPlatform)?.architectures || [];
},
- selectedArchitectureName() {
- return this.selectedArchitecture?.name;
- },
- hasArchitecureList() {
- return !PLATFORMS_WITHOUT_ARCHITECTURES.includes(this.selectedPlatformName);
+ binaryUrl() {
+ return this.architectures.find(({ name }) => name === this.selectedArchitecture)
+ ?.downloadLocation;
},
instructionsWithoutArchitecture() {
- return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatformName]?.instructions;
+ return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatform]?.instructions;
},
runnerInstallationLink() {
- return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatformName]?.link;
+ return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatform]?.link;
},
registerInstructionsWithToken() {
const { registerInstructions } = this.instructions || {};
@@ -139,7 +133,6 @@ export default {
this.registrationToken,
);
}
-
return registerInstructions;
},
},
@@ -150,21 +143,17 @@ export default {
focusSelected() {
// By default the first platform always gets the focus, but when the `defaultPlatformName`
// property is present, any other platform might actually be selected.
- this.$refs[this.selectedPlatformName]?.[0].$el.focus();
+ this.$refs[this.selectedPlatform]?.[0].$el.focus();
},
- defaultPlatform() {
- return this.platforms.find((platform) => platform.name === this.defaultPlatformName);
- },
- selectPlatform(platform) {
- this.selectedPlatform = platform;
+ selectPlatform(platformName) {
+ this.selectedPlatform = platformName;
// Update architecture when platform changes
- const architectures = platform.architectures || [];
- const arch = architectures.find(({ name }) => name === this.selectedArchitectureName);
+ const arch = this.architectures.find(({ name }) => name === this.selectedArchitecture);
if (arch) {
- this.selectArchitecture(arch);
+ this.selectArchitecture(arch.name);
} else {
- this.selectArchitecture(architectures[0]);
+ this.selectArchitecture(this.architectures[0]?.name);
}
},
selectArchitecture(architecture) {
@@ -210,9 +199,9 @@ export default {
{{ $options.i18n.fetchError }}
-
+
-
+
{{ __('Environment') }}
@@ -227,29 +216,29 @@ export default {
v-for="platform in platforms"
:key="platform.name"
:ref="platform.name"
- :selected="selectedPlatformName === platform.name"
- @click="selectPlatform(platform)"
+ :selected="selectedPlatform === platform.name"
+ @click="selectPlatform(platform.name)"
>
{{ platform.humanReadableName }}
-
+
{{ $options.i18n.architecture }}
-
+
{{ architecture.name }}
@@ -257,8 +246,9 @@ export default {
{{ $options.i18n.downloadInstallBinary }}
- [Introduced](issue-link) in GitLab X.X [with a flag](../../administration/feature_flags.md) named . Disabled by default.
+> - [Introduced](issue-link) in GitLab X.X [with a flag](../../administration/feature_flags.md) named `flag_name`. Disabled by default.
> - [Enabled on GitLab.com](issue-link) in GitLab X.X.
> - [Enabled on GitLab.com](issue-link) in GitLab X.X. Available to GitLab.com administrators only.
> - [Enabled on self-managed](issue-link) in GitLab X.X.
-> - [Generally available](issue-link) in GitLab X.Y. [Feature flag ](issue-link) removed.
+> - [Generally available](issue-link) in GitLab X.Y. [Feature flag `flag_name`](issue-link) removed.
```
You can combine entries if they happened in the same release:
@@ -60,15 +60,15 @@ FLAG:
| If the feature is... | Use this text |
|--------------------------|---------------|
-| Available | `On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](/administration/feature_flags.md) named .` |
-| Unavailable | `On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](/administration/feature_flags.md) named .` |
-| Available to some users | `On self-managed GitLab, by default this feature is available to a subset of users. To show or hide the feature for all, ask an administrator to [change the status of the feature flag](/administration/feature_flags.md) named .` |
-| Available, per-group | `On self-managed GitLab, by default this feature is available. To hide the feature per group, ask an administrator to [disable the feature flag](/administration/feature_flags.md) named .` |
-| Unavailable, per-group | `On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](/administration/feature_flags.md) named .` |
-| Available, per-project | `On self-managed GitLab, by default this feature is available. To hide the feature per project or for your entire instance, ask an administrator to [disable the feature flag](/administration/feature_flags.md) named .` |
-| Unavailable, per-project | `On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](/administration/feature_flags.md) named .` |
-| Available, per-user | `On self-managed GitLab, by default this feature is available. To hide the feature per user, ask an administrator to [disable the feature flag](/administration/feature_flags.md) named .` |
-| Unavailable, per-user | `On self-managed GitLab, by default this feature is not available. To make it available per user, ask an administrator to [enable the feature flag](/administration/feature_flags.md) named .` |
+| Available | ``On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](/administration/feature_flags.md) named `flag_name`.`` |
+| Unavailable | ``On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](/administration/feature_flags.md) named `flag_name`.`` |
+| Available to some users | ``On self-managed GitLab, by default this feature is available to a subset of users. To show or hide the feature for all, ask an administrator to [change the status of the feature flag](/administration/feature_flags.md) named `flag_name`.`` |
+| Available, per-group | ``On self-managed GitLab, by default this feature is available. To hide the feature per group, ask an administrator to [disable the feature flag](/administration/feature_flags.md) named `flag_name`.`` |
+| Unavailable, per-group | ``On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](/administration/feature_flags.md) named `flag_name`.`` |
+| Available, per-project | ``On self-managed GitLab, by default this feature is available. To hide the feature per project or for your entire instance, ask an administrator to [disable the feature flag](/administration/feature_flags.md) named `flag_name`.`` |
+| Unavailable, per-project | ``On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](/administration/feature_flags.md) named `flag_name`.`` |
+| Available, per-user | ``On self-managed GitLab, by default this feature is available. To hide the feature per user, ask an administrator to [disable the feature flag](/administration/feature_flags.md) named `flag_name`.`` |
+| Unavailable, per-user | ``On self-managed GitLab, by default this feature is not available. To make it available per user, ask an administrator to [enable the feature flag](/administration/feature_flags.md) named `flag_name`.`` |
### GitLab.com availability information
@@ -114,5 +114,5 @@ And, when the feature is done and fully available to all users:
> - Introduced in GitLab 13.7 [with a flag](../../administration/feature_flags.md) named `forti_token_cloud`. Disabled by default.
> - [Enabled on self-managed](https://gitlab.com/issue/etc) in GitLab 13.8.
> - [Enabled on GitLab.com](https://gitlab.com/issue/etc) in GitLab 13.9.
-> - [Generally available](issue-link) in GitLab 14.0. [Feature flag ](issue-link) removed.
+> - [Generally available](issue-link) in GitLab 14.0. [Feature flag `forti_token_cloud`](issue-link) removed.
```
diff --git a/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md b/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md
index 0163f2e648c..a3f9bbb59ab 100644
--- a/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md
+++ b/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md
@@ -22,6 +22,7 @@ This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec
| `:group_saml` | The test requires a GitLab instance that has SAML SSO enabled at the group level. Interacts with an external SAML identity provider. Paired with the `:orchestrated` tag. |
| `:instance_saml` | The test requires a GitLab instance that has SAML SSO enabled at the instance level. Interacts with an external SAML identity provider. Paired with the `:orchestrated` tag. |
| `:integrations` | This aims to test the available [integrations](../../../user/project/integrations/index.md#available-integrations). The test requires Docker to be installed in the run context. It will provision the containers and can be run against a local instance or using the `gitlab-qa` scenario `Test::Integration::Integrations` |
+| `:issue`, `:issue_${num}` | Optional links to issues which might be related to the spec. Helps keep track of related issues and can also be used by tools that create test reports. Currently added automatically to `Allure` test report. Multiple tags can be used by adding an optional numeric suffix like `issue_1`, `issue_2` etc. |
| `:service_ping_disabled` | The test interacts with the GitLab configuration service ping at the instance level to turn Admin Area setting service ping checkbox on or off. This tag will have the test run only in the `service_ping_disabled` job and must be paired with the `:orchestrated` and `:requires_admin` tags. |
| `:jira` | The test requires a Jira Server. [GitLab-QA](https://gitlab.com/gitlab-org/gitlab-qa) provisions the Jira Server in a Docker container when the `Test::Integration::Jira` test scenario is run. |
| `:kubernetes` | The test includes a GitLab instance that is configured to be run behind an SSH tunnel, allowing a TLS-accessible GitLab. This test also includes provisioning of at least one Kubernetes cluster to test against. _This tag is often be paired with `:orchestrated`._ |
@@ -42,6 +43,7 @@ This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec
| `:requires_git_protocol_v2` | The test requires that Git protocol version 2 is enabled on the server. It's assumed to be enabled by default but if not the test can be skipped by setting `QA_CAN_TEST_GIT_PROTOCOL_V2` to `false`. |
| `:requires_praefect` | The test requires that the GitLab instance uses [Gitaly Cluster](../../../administration/gitaly/praefect.md) (a.k.a. Praefect) as the repository storage . It's assumed to be used by default but if not the test can be skipped by setting `QA_CAN_TEST_PRAEFECT` to `false`. |
| `:runner` | The test depends on and sets up a GitLab Runner instance, typically to run a pipeline. |
+| `:sanity_feature_flags` | The test run in the `Test::Sanity::FeatureFlags` scenario to verify the functioning of the feature flag handling part of the test framework |
| `:skip_live_env` | The test is excluded when run against live deployed environments such as Staging, Canary, and Production. |
| `:skip_fips_env` | The test is excluded when run against an environment in FIPS mode. |
| `:skip_signup_disabled` | The test uses UI to sign up a new user and is skipped in any environment that does not allow new user registration via the UI. |
@@ -49,4 +51,3 @@ This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec
| `:smtp` | The test requires a GitLab instance to be configured to use an SMTP server. Tests SMTP notification email delivery from GitLab by using MailHog. |
| `:testcase` | The link to the test case issue in the [GitLab Project Test Cases](https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases). |
| `:transient` | The test tests transient bugs. It is excluded by default. |
-| `:issue`, `:issue_${num}` | Optional links to issues which might be related to the spec. Helps keep track of related issues and can also be used by tools that create test reports. Currently added automatically to `Allure` test report. Multiple tags can be used by adding an optional numeric suffix like `issue_1`, `issue_2` etc. |
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index b0159519fa7..5077b9c5ef0 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -160,6 +160,7 @@ the related documentation.
| Maximum test cases per [unit test report](../../ci/testing/unit_test_reports.md) | `500000` | Unlimited |
| Maximum registered runners | Free tier: `50` per-group / `50` per-project
All paid tiers: `1000` per-group / `1000` per-project | See [Number of registered runners per scope](../../administration/instance_limits.md#number-of-registered-runners-per-scope) |
| Limit of dotenv variables | Free tier: `50` / Premium tier: `100` / Ultimate tier: `150` | See [Limit dotenv variables](../../administration/instance_limits.md#limit-dotenv-variables) |
+| Authorization token duration (minutes) | `15` | To set a custom value, in the Rails console, run `ApplicationSetting.last.update(container_registry_token_expire_delay: )`, where `` is the desired number of minutes. |
## Package registry limits
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index c4809a17f66..675d163fe9d 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -251,7 +251,7 @@ GEM
pry (~> 0.10)
public_suffix (4.0.6)
racc (1.6.0)
- rack (2.2.3)
+ rack (2.2.3.1)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rainbow (3.0.0)
diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb
index 93e215e9635..db3a59b0549 100644
--- a/qa/qa/runtime/feature.rb
+++ b/qa/qa/runtime/feature.rb
@@ -9,6 +9,8 @@ module QA
AuthorizationError = Class.new(RuntimeError)
UnknownScopeError = Class.new(RuntimeError)
UnknownStateError = Class.new(RuntimeError)
+ UnknownFeatureFlagError = Class.new(RuntimeError)
+ DefinitionFileError = Class.new(RuntimeError)
class << self
# Documentation: https://docs.gitlab.com/ee/api/features.html
@@ -52,7 +54,21 @@ module QA
def enabled?(key, **scopes)
feature = JSON.parse(get_features).find { |flag| flag['name'] == key.to_s }
- feature && (feature['state'] == 'on' || feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], **scopes))
+ if feature
+ feature['state'] == 'on' ||
+ feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], **scopes)
+ else
+ # The feature wasn't found via the API so we check for a default value.
+ file = Pathname.new('../config/feature_flags')
+ .expand_path(Runtime::Path.qa_root)
+ .glob("**/#{key}.yml")
+ .first
+
+ raise UnknownFeatureFlagError, "No feature flag found named '#{key}'" unless file
+
+ definition = YAML.safe_load(File.read(file))
+ definition['default_enabled'].to_s.casecmp('true') == 0
+ end
end
private
diff --git a/qa/qa/specs/features/sanity/feature_flags_spec.rb b/qa/qa/specs/features/sanity/feature_flags_spec.rb
new file mode 100644
index 00000000000..83c0b045730
--- /dev/null
+++ b/qa/qa/specs/features/sanity/feature_flags_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Feature flag handler sanity checks', :sanity_feature_flags do
+ context 'with an existing feature flag definition file' do
+ let(:definition) do
+ path = Pathname.new('../config/feature_flags')
+ .expand_path(Runtime::Path.qa_root)
+ .glob('**/*.yml')
+ .first
+ YAML.safe_load(File.read(path))
+ end
+
+ it 'reads the correct default enabled state' do
+ # This test will fail if we ever remove all the feature flags, but that's very unlikely given how many there
+ # are and how much we rely on them.
+ expect(QA::Runtime::Feature.enabled?(definition['name'])).to be definition['default_enabled']
+ end
+ end
+
+ describe 'feature flag definition files' do
+ let(:file) do
+ path = Pathname.new('../config/feature_flags/development').expand_path(Runtime::Path.qa_root)
+ Tempfile.new(%w[ff-test .yml], path)
+ end
+
+ let(:flag) { Pathname.new(file.path).basename('.yml').to_s }
+
+ before do
+ definition = <<~YAML
+ name: #{flag}
+ type: development
+ default_enabled: #{flag_enabled}
+ YAML
+ File.write(file, definition)
+ end
+
+ after do
+ file.close!
+ end
+
+ context 'with a default disabled feature flag' do
+ let(:flag_enabled) { 'false' }
+
+ it 'reads the flag as disabled' do
+ expect(QA::Runtime::Feature.enabled?(flag)).to be false
+ end
+
+ it 'reads as enabled after the flag is enabled' do
+ QA::Runtime::Feature.enable(flag)
+
+ expect(QA::Runtime::Feature.enabled?(flag)).to be true
+ end
+ end
+
+ context 'with a default enabled feature flag' do
+ let(:flag_enabled) { 'true' }
+
+ it 'reads the flag as enabled' do
+ expect(QA::Runtime::Feature.enabled?(flag)).to be true
+ end
+
+ it 'reads as disabled after the flag is disabled' do
+ QA::Runtime::Feature.disable(flag)
+
+ expect(QA::Runtime::Feature.enabled?(flag)).to be false
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index 68b624b3f2e..801b9b222a4 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -12,6 +12,7 @@ module QA
DEFAULT_TEST_PATH_ARGS = ['--', File.expand_path('./features', __dir__)].freeze
DEFAULT_STD_ARGS = [$stderr, $stdout].freeze
+ DEFAULT_SKIPPED_TAGS = %w[orchestrated transient sanity_feature_flags].freeze
def initialize
@tty = false
@@ -25,7 +26,7 @@ module QA
if tags.any?
tags.each { |tag| tags_for_rspec.push(['--tag', tag.to_s]) }
else
- tags_for_rspec.push(%w[--tag ~orchestrated --tag ~transient]) unless (%w[-t --tag] & options).any?
+ tags_for_rspec.push(DEFAULT_SKIPPED_TAGS.map { |tag| %W[--tag ~#{tag}] }) unless (%w[-t --tag] & options).any?
end
tags_for_rspec.push(%w[--tag ~geo]) unless QA::Runtime::Env.geo_environment?
diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb
index 88f5cd5be93..72ba915d99b 100644
--- a/qa/spec/runtime/feature_spec.rb
+++ b/qa/spec/runtime/feature_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe QA::Runtime::Feature do
.to receive(:get)
.and_return(Struct.new(:code, :body).new(200, %Q([{ "name": "a_flag", "state": "conditional", "gates": #{gates} }])))
- expect(described_class.enabled?(feature_flag, scope => actor)).to be_truthy
+ expect(described_class.enabled?(feature_flag, scope => actor)).to be true
end
end
end
@@ -172,7 +172,7 @@ RSpec.describe QA::Runtime::Feature do
.to receive(:get)
.and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
- expect(described_class.enabled?(feature_flag)).to be_truthy
+ expect(described_class.enabled?(feature_flag)).to be true
end
it 'raises an error when the scope is unknown' do
@@ -224,6 +224,75 @@ RSpec.describe QA::Runtime::Feature do
let(:gates) { %q([{"key": "groups", "value": ["foo"]}]) }
end
end
+
+ context 'when a feature flag is not found via the API and there is no definition file' do
+ before do
+ allow(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ allow(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[]'))
+ allow(Dir).to receive(:glob).and_return([])
+ end
+
+ it 'raises an error' do
+ expect { described_class.enabled?(feature_flag) }
+ .to raise_error(QA::Runtime::Feature::UnknownFeatureFlagError)
+ end
+ end
+
+ context 'with definition files' do
+ context 'when no features are found via the API' do
+ before do
+ allow(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ allow(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[]'))
+ allow(Dir).to receive(:glob).and_return(['file_path'])
+ allow(File).to receive(:read).and_return(definition)
+ end
+
+ context 'with a default enabled defintion' do
+ let(:definition) { 'default_enabled: true' }
+
+ it 'returns a default enabled flag' do
+ expect(described_class.enabled?(feature_flag)).to be true
+ end
+ end
+
+ context 'with a default disabled defintion' do
+ let(:definition) { 'default_enabled: false' }
+
+ it 'returns a default disabled flag' do
+ expect(described_class.enabled?(feature_flag)).to be false
+ end
+ end
+ end
+
+ context 'when the feature is found via the API' do
+ before do
+ allow(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ allow(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
+ end
+
+ it 'returns the value from the API not the definition file' do
+ expect(Dir).not_to receive(:glob)
+ expect(File).not_to receive(:read)
+
+ expect(described_class.enabled?(feature_flag)).to be true
+ end
+ end
+ end
end
end
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index d5e442acfe7..dd013497367 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -1,14 +1,18 @@
# frozen_string_literal: true
RSpec.describe QA::Specs::Runner do
- shared_examples 'excludes orchestrated, transient, and geo' do
- it 'excludes the orchestrated, transient, and geo tags, and includes default args' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
+ shared_examples 'excludes default skipped, and geo' do
+ it 'excludes the default skipped and geo tags, and includes default args' do
+ expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
end
+ before do
+ stub_const('DEFAULT_SKIPPED_TAGS', %w[--tag ~orchestrated --tag ~transient --tag ~sanity_feature_flags].freeze)
+ end
+
describe '#perform' do
before do
allow(QA::Runtime::Browser).to receive(:configure!)
@@ -17,13 +21,15 @@ RSpec.describe QA::Specs::Runner do
QA::Runtime::Scenario.define(:klass, "QA::Scenario::Test::Instance::All")
end
- it_behaves_like 'excludes orchestrated, transient, and geo'
+ it_behaves_like 'excludes default skipped, and geo'
context 'when tty is set' do
subject { described_class.new.tap { |runner| runner.tty = true } }
it 'sets the `--tty` flag' do
- expect_rspec_runner_arguments(['--tty', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(
+ ['--tty'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS]
+ )
subject.perform
end
@@ -39,7 +45,10 @@ RSpec.describe QA::Specs::Runner do
end
it 'sets the `--dry-run` flag' do
- expect_rspec_runner_arguments(['--dry-run', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS], [$stderr, anything])
+ expect_rspec_runner_arguments(
+ ['--dry-run'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS],
+ [$stderr, anything]
+ )
subject.perform
end
@@ -73,7 +82,10 @@ RSpec.describe QA::Specs::Runner do
subject { described_class.new.tap { |runner| runner.options = %w[--tag actioncable] } }
it 'includes the option value in the file name' do
- expect_rspec_runner_arguments(['--dry-run', '--tag', '~geo', '--tag', 'actioncable', *described_class::DEFAULT_TEST_PATH_ARGS], [$stderr, anything])
+ expect_rspec_runner_arguments(
+ ['--dry-run', '--tag', '~geo', '--tag', 'actioncable', *described_class::DEFAULT_TEST_PATH_ARGS],
+ [$stderr, anything]
+ )
expect(File).to receive(:open).with('no_of_examples/test_instance_all_actioncable.txt', 'w') { '22' }
@@ -98,7 +110,10 @@ RSpec.describe QA::Specs::Runner do
end
it 'sets the `--dry-run` flag' do
- expect_rspec_runner_arguments(['--dry-run', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS], [$stderr, anything])
+ expect_rspec_runner_arguments(
+ ['--dry-run'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS],
+ [$stderr, anything]
+ )
subject.perform
end
@@ -125,7 +140,9 @@ RSpec.describe QA::Specs::Runner do
subject { described_class.new.tap { |runner| runner.tags = %i[orchestrated github] } }
it 'focuses on the given tags' do
- expect_rspec_runner_arguments(['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(
+ ['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS]
+ )
subject.perform
end
@@ -144,8 +161,8 @@ RSpec.describe QA::Specs::Runner do
context 'when "qa/specs/features/foo" is set as options' do
subject { described_class.new.tap { |runner| runner.options = %w[qa/specs/features/foo] } }
- it 'passes the given tests path and excludes the orchestrated, transient, and geo tags' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', 'qa/specs/features/foo'])
+ it 'passes the given tests path and excludes the default skipped, and geo tags' do
+ expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', 'qa/specs/features/foo'])
subject.perform
end
@@ -167,7 +184,7 @@ RSpec.describe QA::Specs::Runner do
end
it 'includes default args and excludes the skip_signup_disabled tag' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
@@ -179,7 +196,7 @@ RSpec.describe QA::Specs::Runner do
end
it 'includes default args and excludes the skip_live_env tag' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', '--tag', '~skip_live_env', *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_live_env', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
end
@@ -212,7 +229,10 @@ RSpec.describe QA::Specs::Runner do
end
it 'includes default args and excludes all unsupported tags' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *excluded_feature_tags_except(feature), *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(
+ DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *excluded_feature_tags_except(feature),
+ *described_class::DEFAULT_TEST_PATH_ARGS]
+ )
subject.perform
end
@@ -237,11 +257,11 @@ RSpec.describe QA::Specs::Runner do
end
end
- it_behaves_like 'excludes orchestrated, transient, and geo'
+ it_behaves_like 'excludes default skipped, and geo'
end
context 'when features are not specified' do
- it_behaves_like 'excludes orchestrated, transient, and geo'
+ it_behaves_like 'excludes default skipped, and geo'
end
end
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index b6ac7b4281b..8a868edce3a 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -248,7 +248,6 @@ function download_chart() {
helm repo add gitlab https://charts.gitlab.io
echoinfo "Building the gitlab chart's dependencies..."
- helm dependency build "gitlab-${GITLAB_HELM_CHART_REF}"
}
function base_config_changed() {
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 0d598fee07b..e6e0307d0ca 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -44,6 +44,14 @@ RSpec.describe Projects::CompareController do
expect(response).to be_successful
end
end
+
+ context 'with missing parameters' do
+ let(:params) { super().merge(from: '', to: '') }
+
+ it 'returns successfully' do
+ expect(response).to be_successful
+ end
+ end
end
describe 'GET show' do
diff --git a/spec/fixtures/project_services/campfire/rooms.json b/spec/fixtures/integrations/campfire/rooms.json
similarity index 100%
rename from spec/fixtures/project_services/campfire/rooms.json
rename to spec/fixtures/integrations/campfire/rooms.json
diff --git a/spec/fixtures/project_services/campfire/rooms2.json b/spec/fixtures/integrations/campfire/rooms2.json
similarity index 100%
rename from spec/fixtures/project_services/campfire/rooms2.json
rename to spec/fixtures/integrations/campfire/rooms2.json
diff --git a/spec/models/integrations/campfire_spec.rb b/spec/models/integrations/campfire_spec.rb
index 0044e6fae21..405a9ff4b3f 100644
--- a/spec/models/integrations/campfire_spec.rb
+++ b/spec/models/integrations/campfire_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe Integrations::Campfire do
it "calls Campfire API to get a list of rooms and speak in a room" do
# make sure a valid list of rooms is returned
- body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms.json')
+ body = File.read(Rails.root + 'spec/fixtures/integrations/campfire/rooms.json')
stub_full_request(@rooms_url).with(basic_auth: @auth).to_return(
body: body,
@@ -65,7 +65,7 @@ RSpec.describe Integrations::Campfire do
it "calls Campfire API to get a list of rooms but shouldn't speak in a room" do
# return a list of rooms that do not contain a room named 'test-room'
- body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms2.json')
+ body = File.read(Rails.root + 'spec/fixtures/integrations/campfire/rooms2.json')
stub_full_request(@rooms_url).with(basic_auth: @auth).to_return(
body: body,
status: 200,
diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
index 3ea6658c0c1..893eae19482 100644
--- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
@@ -44,7 +44,7 @@ Integration.available_integration_names.each do |integration|
let(:licensed_features) do
{
- 'github' => :github_project_service_integration
+ 'github' => :github_integration
}
end
diff --git a/tooling/overcommit/Gemfile.lock b/tooling/overcommit/Gemfile.lock
index 13c611439b6..a4b6f62a979 100644
--- a/tooling/overcommit/Gemfile.lock
+++ b/tooling/overcommit/Gemfile.lock
@@ -34,7 +34,7 @@ GEM
parallel (1.19.2)
parser (2.7.2.0)
ast (~> 2.4.1)
- rack (2.2.3)
+ rack (2.2.3.1)
rainbow (3.0.0)
regexp_parser (1.8.2)
rexml (3.2.4)