Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
cc626f1411
commit
4d528bfd73
|
|
@ -1,8 +1,10 @@
|
|||
<script>
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import { breakpoints } from '@gitlab/ui/dist/utils';
|
||||
import { sortBy, throttle } from 'lodash';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import { contentTop } from '~/lib/utils/common_utils';
|
||||
import { s__ } from '~/locale';
|
||||
import { formatBoardLists } from 'ee_else_ce/boards/boards_util';
|
||||
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
|
||||
|
|
@ -142,7 +144,11 @@ export default {
|
|||
el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' });
|
||||
},
|
||||
setBoardHeight() {
|
||||
this.boardHeight = `${window.innerHeight - this.$el.getBoundingClientRect().top}px`;
|
||||
if (window.innerWidth < breakpoints.md) {
|
||||
this.boardHeight = `${window.innerHeight - contentTop()}px`;
|
||||
} else {
|
||||
this.boardHeight = `${window.innerHeight - this.$el.getBoundingClientRect().top}px`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,7 +41,13 @@ module Environments
|
|||
|
||||
def by_search(environments)
|
||||
if params[:search].present?
|
||||
environments.for_name_like(params[:search], limit: nil)
|
||||
if Feature.enabled?(:enable_environments_search_within_folder, project)
|
||||
Environment.from_union(
|
||||
environments.for_name_like(params[:search], limit: nil),
|
||||
environments.for_name_like_within_folder(params[:search], limit: nil))
|
||||
else
|
||||
environments.for_name_like(params[:search], limit: nil)
|
||||
end
|
||||
else
|
||||
environments
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ class Environment < ApplicationRecord
|
|||
include FastDestroyAll::Helpers
|
||||
include Presentable
|
||||
include NullifyIfBlank
|
||||
include FromUnion
|
||||
|
||||
self.reactive_cache_refresh_interval = 1.minute
|
||||
self.reactive_cache_lifetime = 55.seconds
|
||||
|
|
@ -96,7 +97,16 @@ class Environment < ApplicationRecord
|
|||
# Search environments which have names like the given query.
|
||||
# Do not set a large limit unless you've confirmed that it works on gitlab.com scale.
|
||||
scope :for_name_like, -> (query, limit: 5) do
|
||||
where('LOWER(environments.name) LIKE LOWER(?) || \'%\'', sanitize_sql_like(query)).limit(limit)
|
||||
top_level = 'LOWER(environments.name) LIKE LOWER(?) || \'%\''
|
||||
|
||||
where(top_level, sanitize_sql_like(query)).limit(limit)
|
||||
end
|
||||
|
||||
scope :for_name_like_within_folder, -> (query, limit: 5) do
|
||||
within_folder = 'LOWER(ltrim(environments.name, environments.environment_type'\
|
||||
' || \'/\')) LIKE LOWER(?) || \'%\''
|
||||
|
||||
where(within_folder, sanitize_sql_like(query)).limit(limit)
|
||||
end
|
||||
|
||||
scope :for_project, -> (project) { where(project_id: project) }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: enable_environments_search_within_folder
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102227/diffs
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/382108
|
||||
milestone: '15.6'
|
||||
type: development
|
||||
group: group::release
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexEnvironmentsForNameSearchWithinFolder < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_environments_for_name_search_within_folder'
|
||||
|
||||
def up
|
||||
add_concurrent_index :environments,
|
||||
"project_id, lower(ltrim(name, environment_type || '/')) varchar_pattern_ops, state", name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :environments, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
600e0c6bd79850846c38de38f175889cee731b5619dfbd084e1bd4438d13d387
|
||||
|
|
@ -28967,6 +28967,8 @@ CREATE INDEX index_emails_on_user_id ON emails USING btree (user_id);
|
|||
|
||||
CREATE INDEX index_enabled_clusters_on_id ON clusters USING btree (id) WHERE (enabled = true);
|
||||
|
||||
CREATE INDEX index_environments_for_name_search_within_folder ON environments USING btree (project_id, lower(ltrim((name)::text, ((environment_type)::text || '/'::text))) varchar_pattern_ops, state);
|
||||
|
||||
CREATE INDEX index_environments_on_merge_request_id ON environments USING btree (merge_request_id);
|
||||
|
||||
CREATE INDEX index_environments_on_name_varchar_pattern_ops ON environments USING btree (name varchar_pattern_ops);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ NOTE:
|
|||
When configured to run on their own servers, Gitaly servers must be
|
||||
[upgraded](../../update/package/index.md) before Gitaly clients in your cluster.
|
||||
|
||||
NOTE:
|
||||
[Disk requirements](index.md#disk-requirements) apply to Gitaly nodes.
|
||||
|
||||
The process for setting up Gitaly on its own server is:
|
||||
|
||||
1. [Install Gitaly](#install-gitaly).
|
||||
|
|
|
|||
|
|
@ -90,6 +90,50 @@ If you are unable to use either method, contact customer support for restoration
|
|||
|
||||
Contact customer support for immediate help in restoration or recovery.
|
||||
|
||||
## Disk requirements
|
||||
|
||||
Gitaly and Gitaly Cluster require fast local storage to perform effectively because they are heavy I/O-based processes. Therefore,
|
||||
we strongly recommend that all Gitaly nodes use solid-state drives (SSDs).
|
||||
|
||||
These SSDs should have a throughput of at least:
|
||||
|
||||
- 8,000 input/output operations per second (IOPS) for read operations.
|
||||
- 2,000 IOPS for write operations.
|
||||
|
||||
These IOPS values are initial recommendations, and may be adjusted to greater or lesser values
|
||||
depending on the scale of your environment's workload. If you’re running the environment on a
|
||||
cloud provider, refer to their documentation about how to configure IOPS correctly.
|
||||
|
||||
For repository data, only local storage is supported for Gitaly and Gitaly Cluster for performance and consistency reasons. Alternatives such as
|
||||
[NFS](#moving-beyond-nfs) or [cloud-based systems](../nfs.md#avoid-using-cloud-based-file-systems) are not supported.
|
||||
|
||||
### Moving beyond NFS
|
||||
|
||||
Engineering support for NFS for Git repositories is deprecated. Technical support is planned to be unavailable starting
|
||||
November 22, 2022. See our [statement of support](https://about.gitlab.com/support/statement-of-support/#gitaly-and-nfs)
|
||||
for more details.
|
||||
|
||||
[Network File System (NFS)](https://en.wikipedia.org/wiki/Network_File_System)
|
||||
is not well suited to Git workloads which are CPU and IOPS sensitive.
|
||||
Specifically:
|
||||
|
||||
- Git is sensitive to file system latency. Some operations require many
|
||||
read operations. Operations that are fast on block storage can become an order of
|
||||
magnitude slower. This significantly impacts GitLab application performance.
|
||||
- NFS performance optimizations that prevent the performance gap between
|
||||
block storage and NFS being even wider are vulnerable to race conditions. We have observed
|
||||
[data inconsistencies](https://gitlab.com/gitlab-org/gitaly/-/issues/2589)
|
||||
in production environments caused by simultaneous writes to different NFS
|
||||
clients. Data corruption is not an acceptable risk.
|
||||
|
||||
Gitaly Cluster is purpose built to provide reliable, high performance, fault
|
||||
tolerant Git storage.
|
||||
|
||||
Further reading:
|
||||
|
||||
- Blog post: [The road to Gitaly v1.0 (aka, why GitLab doesn't require NFS for storing Git data anymore)](https://about.gitlab.com/blog/2018/09/12/the-road-to-gitaly-1-0/)
|
||||
- Blog post: [How we spent two weeks hunting an NFS bug in the Linux kernel](https://about.gitlab.com/blog/2018/11/14/how-we-spent-two-weeks-hunting-an-nfs-bug/)
|
||||
|
||||
## Directly accessing repositories
|
||||
|
||||
GitLab doesn't advise directly accessing Gitaly repositories stored on disk with a Git client or any other tool,
|
||||
|
|
@ -405,33 +449,6 @@ The leftover state is eventually cleaned up.
|
|||
Unlike Gitaly, Gitaly Cluster doesn't move the repositories in the storages but only virtually moves the repository by updating the
|
||||
relative path of the repository in the metadata store.
|
||||
|
||||
### Moving beyond NFS
|
||||
|
||||
Engineering support for NFS for Git repositories is deprecated. Technical support is planned to be unavailable starting
|
||||
November 22, 2022. See our [statement of support](https://about.gitlab.com/support/statement-of-support/#gitaly-and-nfs)
|
||||
for more details.
|
||||
|
||||
[Network File System (NFS)](https://en.wikipedia.org/wiki/Network_File_System)
|
||||
is not well suited to Git workloads which are CPU and IOPS sensitive.
|
||||
Specifically:
|
||||
|
||||
- Git is sensitive to file system latency. Some operations require many
|
||||
read operations. Operations that are fast on block storage can become an order of
|
||||
magnitude slower. This significantly impacts GitLab application performance.
|
||||
- NFS performance optimizations that prevent the performance gap between
|
||||
block storage and NFS being even wider are vulnerable to race conditions. We have observed
|
||||
[data inconsistencies](https://gitlab.com/gitlab-org/gitaly/-/issues/2589)
|
||||
in production environments caused by simultaneous writes to different NFS
|
||||
clients. Data corruption is not an acceptable risk.
|
||||
|
||||
Gitaly Cluster is purpose built to provide reliable, high performance, fault
|
||||
tolerant Git storage.
|
||||
|
||||
Further reading:
|
||||
|
||||
- Blog post: [The road to Gitaly v1.0 (aka, why GitLab doesn't require NFS for storing Git data anymore)](https://about.gitlab.com/blog/2018/09/12/the-road-to-gitaly-1-0/)
|
||||
- Blog post: [How we spent two weeks hunting an NFS bug in the Linux kernel](https://about.gitlab.com/blog/2018/11/14/how-we-spent-two-weeks-hunting-an-nfs-bug/)
|
||||
|
||||
### Components
|
||||
|
||||
Gitaly Cluster consists of multiple components:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ The minimum recommended configuration for a Gitaly Cluster requires:
|
|||
- 3 Praefect nodes
|
||||
- 3 Gitaly nodes (1 primary, 2 secondary)
|
||||
|
||||
NOTE:
|
||||
[Disk requirements](index.md#disk-requirements) apply to Gitaly nodes.
|
||||
|
||||
You should configure an odd number of Gitaly nodes so that transactions have a tie-breaker in case one of the
|
||||
Gitaly nodes fails in a mutating RPC call.
|
||||
|
||||
|
|
|
|||
|
|
@ -188,6 +188,31 @@ In this example:
|
|||
- `DEPLOY_ENVIRONMENT` is listed in the **Run pipeline** page, but with no value set.
|
||||
The user is expected to define the value each time the pipeline is run manually.
|
||||
|
||||
##### Configure a list of selectable values for a prefilled variable
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/363660) in GitLab 15.5 [with a flag](../../administration/feature_flags.md) named `run_pipeline_graphql`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
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 `run_pipeline_graphql`.
|
||||
The feature is not ready for production use.
|
||||
|
||||
You can define an array of CI/CD variable values the user can select from when running a pipeline manually.
|
||||
These values are in a dropdown list in the **Run pipeline** page. The first value
|
||||
in the array is the value selected by default.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
variables:
|
||||
DEPLOY_ENVIRONMENT:
|
||||
value:
|
||||
- "production"
|
||||
- "staging"
|
||||
- "canary"
|
||||
description: "The deployment target. Set to 'production' by default."
|
||||
```
|
||||
|
||||
### Run a pipeline by using a URL query string
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24146) in GitLab 12.5.
|
||||
|
|
|
|||
|
|
@ -4228,7 +4228,8 @@ deploy_review_job:
|
|||
|
||||
#### `variables:description`
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30101) in GitLab 13.7.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30101) in GitLab 13.7.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/363660) in GitLab 15.5, `variables:value` can contain an array of values.
|
||||
|
||||
Use the `description` keyword to define a [pipeline-level (global) variable that is prefilled](../pipelines/index.md#prefill-variables-in-manual-pipelines)
|
||||
when [running a pipeline manually](../pipelines/index.md#run-a-pipeline-manually).
|
||||
|
|
@ -4240,6 +4241,7 @@ If used with `value`, the variable value is also prefilled when running a pipeli
|
|||
**Possible inputs**:
|
||||
|
||||
- A string.
|
||||
- An array of strings.
|
||||
|
||||
**Example of `variables:description`**:
|
||||
|
||||
|
|
@ -4254,6 +4256,7 @@ variables:
|
|||
|
||||
- A global variable defined with `value` but no `description` behaves the same as
|
||||
[`variables`](#variables).
|
||||
- `variables:value` can [contain an array of selectable values](../pipelines/index.md#configure-a-list-of-selectable-values-for-a-prefilled-variable).
|
||||
|
||||
#### `variables:expand`
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ variables:
|
|||
COVFUZZ_VERSION: v3
|
||||
# This is for users who have an offline environment and will have to replicate gitlab-cov-fuzz release binaries
|
||||
# to their own servers
|
||||
COVFUZZ_URL_PREFIX: "https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw"
|
||||
COVFUZZ_URL_PREFIX: "https://gitlab.com/security-products/gitlab-cov-fuzz/-/raw"
|
||||
|
||||
|
||||
coverage_fuzzing_unlicensed:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ variables:
|
|||
COVFUZZ_VERSION: v3
|
||||
# This is for users who have an offline environment and will have to replicate gitlab-cov-fuzz release binaries
|
||||
# to their own servers
|
||||
COVFUZZ_URL_PREFIX: "https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw"
|
||||
COVFUZZ_URL_PREFIX: "https://gitlab.com/security-products/gitlab-cov-fuzz/-/raw"
|
||||
|
||||
|
||||
coverage_fuzzing_unlicensed:
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ module Gitlab
|
|||
def validate_url!
|
||||
return if Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
|
||||
|
||||
Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false)
|
||||
Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false, schemes: %w[http https])
|
||||
end
|
||||
|
||||
def service_account_exists?(resource)
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ module Gitlab
|
|||
|
||||
(delegator_class.instance_methods - allowlist).each do |method_name|
|
||||
target_classes.each do |target_class|
|
||||
next unless target_class.instance_methods.include?(method_name)
|
||||
next unless target_class.method_defined?(method_name)
|
||||
|
||||
errors << generate_error(method_name, target_class, delegator_class)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ module Gitlab
|
|||
private
|
||||
|
||||
def instance_method_defined?(klass, name)
|
||||
klass.instance_methods(false).include?(name) ||
|
||||
klass.private_instance_methods(false).include?(name)
|
||||
klass.method_defined?(name, false) ||
|
||||
klass.private_method_defined?(name, false)
|
||||
end
|
||||
|
||||
def find_direct_method(klass, name)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,34 @@ RSpec.describe Projects::EnvironmentsController do
|
|||
expect(json_response['stopped_count']).to eq 1
|
||||
end
|
||||
|
||||
it 'supports search within environment folder name' do
|
||||
create(:environment, project: project, name: 'review-app', state: :available)
|
||||
|
||||
get :index, params: environment_params(format: :json, search: 'review')
|
||||
|
||||
expect(environments.map { |env| env['name'] }).to contain_exactly('review-app',
|
||||
'staging/review-1',
|
||||
'staging/review-2')
|
||||
expect(json_response['available_count']).to eq 3
|
||||
expect(json_response['stopped_count']).to eq 1
|
||||
end
|
||||
|
||||
context 'when enable_environments_search_within_folder FF is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enable_environments_search_within_folder: false)
|
||||
end
|
||||
|
||||
it 'ignores name inside folder' do
|
||||
create(:environment, project: project, name: 'review-app', state: :available)
|
||||
|
||||
get :index, params: environment_params(format: :json, search: 'review')
|
||||
|
||||
expect(environments.map { |env| env['name'] }).to contain_exactly('review-app')
|
||||
expect(json_response['available_count']).to eq 1
|
||||
expect(json_response['stopped_count']).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets the polling interval header' do
|
||||
subject
|
||||
|
||||
|
|
|
|||
|
|
@ -51,15 +51,35 @@ RSpec.describe Environments::EnvironmentsFinder do
|
|||
end
|
||||
|
||||
context 'with search and states' do
|
||||
let_it_be(:environment_available_b) { create(:environment, :available, name: 'test/foldered-env', project: project) }
|
||||
|
||||
it 'searches environments by name and state' do
|
||||
result = described_class.new(project, user, search: 'test', states: :available).execute
|
||||
|
||||
expect(result).to contain_exactly(environment_available)
|
||||
expect(result).to contain_exactly(environment_available, environment_available_b)
|
||||
end
|
||||
|
||||
it 'searches environments by name inside folder and state' do
|
||||
result = described_class.new(project, user, search: 'folder', states: :available).execute
|
||||
|
||||
expect(result).to contain_exactly(environment_available_b)
|
||||
end
|
||||
|
||||
context 'when enable_environments_search_within_folder FF is disabled' do
|
||||
before do
|
||||
stub_feature_flags(enable_environments_search_within_folder: false)
|
||||
end
|
||||
|
||||
it 'ignores name inside folder' do
|
||||
result = described_class.new(project, user, search: 'folder', states: :available).execute
|
||||
|
||||
expect(result).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with id' do
|
||||
it 'searches environments by name and state' do
|
||||
it 'searches environments by name and id' do
|
||||
result = described_class.new(project, user, search: 'test', environment_ids: [environment_available.id]).execute
|
||||
|
||||
expect(result).to contain_exactly(environment_available)
|
||||
|
|
|
|||
|
|
@ -123,15 +123,32 @@ describe('BoardContent', () => {
|
|||
expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('resizes the list on resize', async () => {
|
||||
it('on small screens, sets board container height to full height', async () => {
|
||||
window.innerHeight = 1000;
|
||||
window.innerWidth = 767;
|
||||
jest.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({ top: 100 });
|
||||
|
||||
wrapper.vm.resizeObserver.trigger();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.findComponent({ ref: 'list' }).attributes('style')).toBe('height: 900px;');
|
||||
const style = wrapper.findComponent({ ref: 'list' }).attributes('style');
|
||||
|
||||
expect(style).toBe('height: 1000px;');
|
||||
});
|
||||
|
||||
it('on large screens, sets board container height fill area below filters', async () => {
|
||||
window.innerHeight = 1000;
|
||||
window.innerWidth = 768;
|
||||
jest.spyOn(Element.prototype, 'getBoundingClientRect').mockReturnValue({ top: 100 });
|
||||
|
||||
wrapper.vm.resizeObserver.trigger();
|
||||
|
||||
await nextTick();
|
||||
|
||||
const style = wrapper.findComponent({ ref: 'list' }).attributes('style');
|
||||
|
||||
expect(style).toBe('height: 900px;');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,14 @@ RSpec.describe Gitlab::Kubernetes::KubeClient do
|
|||
it_behaves_like 'local address'
|
||||
end
|
||||
|
||||
context 'when a non HTTP/HTTPS URL is provided' do
|
||||
let(:api_url) { 'ssh://192.168.1.2' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { client }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'falls back to default options, but allows overriding' do
|
||||
client = described_class.new(api_url)
|
||||
defaults = Gitlab::Kubernetes::KubeClient::DEFAULT_KUBECLIENT_OPTIONS
|
||||
|
|
|
|||
|
|
@ -282,6 +282,72 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.for_name_like_within_folder' do
|
||||
subject { project.environments.for_name_like_within_folder(query, limit: limit) }
|
||||
|
||||
let!(:environment) { create(:environment, name: 'review/test-app', project: project) }
|
||||
let!(:environment_a) { create(:environment, name: 'test-app', project: project) }
|
||||
let(:query) { 'test' }
|
||||
let(:limit) { 5 }
|
||||
|
||||
it 'returns a found name' do
|
||||
is_expected.to contain_exactly(environment)
|
||||
end
|
||||
|
||||
it 'does not return environment without folder' do
|
||||
is_expected.not_to include(environment_a)
|
||||
end
|
||||
|
||||
context 'when query is test-app' do
|
||||
let(:query) { 'test-app' }
|
||||
|
||||
it 'returns a found name' do
|
||||
is_expected.to include(environment)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when query is test-app-a' do
|
||||
let(:query) { 'test-app-a' }
|
||||
|
||||
it 'returns empty array' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when query is empty string' do
|
||||
let(:query) { '' }
|
||||
let!(:environment_b) { create(:environment, name: 'review/test-app-1', project: project) }
|
||||
|
||||
it 'returns only the foldered environments' do
|
||||
is_expected.to contain_exactly(environment, environment_b)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when query is nil' do
|
||||
let(:query) {}
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when query is partially matched in the middle of environment name' do
|
||||
let(:query) { 'app' }
|
||||
|
||||
it 'returns empty array' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when query contains a wildcard character' do
|
||||
let(:query) { 'test%' }
|
||||
|
||||
it 'prevents wildcard injection' do
|
||||
is_expected.to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.auto_stoppable' do
|
||||
subject { described_class.auto_stoppable(limit) }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue