Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-04 09:11:46 +00:00
parent 0250f48d9f
commit 9f0d276489
47 changed files with 622 additions and 317 deletions

View file

@ -10,11 +10,6 @@ export default () => {
const { url: initialUrl, urlVariables } = el.dataset; const { url: initialUrl, urlVariables } = el.dataset;
// Convert the array of 'key' strings to array of { key } objects
const initialUrlVariables = urlVariables
? JSON.parse(urlVariables)?.map((key) => ({ key }))
: undefined;
return new Vue({ return new Vue({
el, el,
name: 'WebhookFormRoot', name: 'WebhookFormRoot',
@ -22,7 +17,7 @@ export default () => {
return createElement(FormUrlApp, { return createElement(FormUrlApp, {
props: { props: {
initialUrl, initialUrl,
initialUrlVariables, initialUrlVariables: JSON.parse(urlVariables),
}, },
}); });
}, },

View file

@ -4,7 +4,7 @@ module HooksHelper
def webhook_form_data(hook) def webhook_form_data(hook)
{ {
url: hook.url, url: hook.url,
url_variables: Gitlab::Json.dump(hook.url_variables.keys) url_variables: Gitlab::Json.dump(hook.url_variables.keys.map { { key: _1 } })
} }
end end

View file

@ -261,6 +261,8 @@ class User < ApplicationRecord
has_many :resource_state_events, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent has_many :resource_state_events, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent
has_many :authored_events, class_name: 'Event', dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent has_many :authored_events, class_name: 'Event', dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :namespace_commit_emails
# #
# Validations # Validations
# #

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Users
class NamespaceCommitEmail < ApplicationRecord
belongs_to :user
belongs_to :namespace
belongs_to :email
validates :user, presence: true
validates :namespace, presence: true
validates :email, presence: true
validates :user_id, uniqueness: { scope: [:namespace_id] }
end
end

View file

@ -1,38 +0,0 @@
# frozen_string_literal: true
module Clusters
module Applications
# Deprecated, to be removed in %14.0 as part of https://gitlab.com/groups/gitlab-org/-/epics/4280
class PrometheusUpdateService < BaseHelmService
attr_accessor :project
def initialize(app, project)
super(app)
@project = project
end
def execute
raise NotImplementedError, 'Externally installed prometheus should not be modified!' unless app.managed_prometheus?
app.make_updating!
helm_api.update(patch_command(values))
::ClusterWaitForAppUpdateWorker.perform_in(::ClusterWaitForAppUpdateWorker::INTERVAL, app.name, app.id)
rescue ::Kubeclient::HttpError => ke
app.make_update_errored!("Kubernetes error: #{ke.message}")
rescue StandardError => e
app.make_update_errored!(e.message)
end
private
def values
PrometheusConfigService
.new(project, cluster, app)
.execute
.to_yaml
end
end
end
end

View file

@ -12,11 +12,15 @@ module ResourceEvents
def execute def execute
create_event create_event
track_event
resource.expire_note_etag_cache resource.expire_note_etag_cache
end end
private private
def track_event; end
def create_event def create_event
raise NotImplementedError raise NotImplementedError
end end

View file

@ -13,6 +13,12 @@ module ResourceEvents
private private
def track_event
return unless resource.is_a?(WorkItem)
Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter.track_work_item_milestone_changed_action(author: user)
end
def create_event def create_event
ResourceMilestoneEvent.create(build_resource_args) ResourceMilestoneEvent.create(build_resource_args)
end end

View file

@ -46,4 +46,5 @@
= render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name], class: 'gl-vertical-align-top' = render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name], class: 'gl-vertical-align-top'
= render 'projects/branches/delete_branch_modal_button', project: @project, branch: branch, merged: merged - if can?(current_user, :push_code, @project)
= render 'projects/branches/delete_branch_modal_button', project: @project, branch: branch, merged: merged

View file

@ -16,12 +16,12 @@
= _('Check out branch') = _('Check out branch')
- if current_user - if current_user
%li.gl-new-dropdown-item %li.gl-new-dropdown-item
= link_to ide_merge_request_path(@merge_request), class: 'dropdown-item', data: { qa_selector: 'open_in_web_ide_button' } do = link_to ide_merge_request_path(@merge_request), class: 'dropdown-item', target: '_blank', data: { qa_selector: 'open_in_web_ide_button' } do
.gl-new-dropdown-item-text-wrapper .gl-new-dropdown-item-text-wrapper
= _('Open in Web IDE') = _('Open in Web IDE')
- if Gitlab::CurrentSettings.gitpod_enabled && current_user&.gitpod_enabled - if Gitlab::CurrentSettings.gitpod_enabled && current_user&.gitpod_enabled
%li.gl-new-dropdown-item %li.gl-new-dropdown-item
= link_to "#{Gitlab::CurrentSettings.gitpod_url}##{merge_request_url(@merge_request)}", class: 'dropdown-item' do = link_to "#{Gitlab::CurrentSettings.gitpod_url}##{merge_request_url(@merge_request)}", target: '_blank', class: 'dropdown-item' do
.gl-new-dropdown-item-text-wrapper .gl-new-dropdown-item-text-wrapper
= _('Open in Gitpod') = _('Open in Gitpod')
%li.gl-new-dropdown-divider %li.gl-new-dropdown-divider

View file

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
# Deprecated, to be removed in %14.0 as part of https://gitlab.com/groups/gitlab-org/-/epics/4280 # Deprecated, to be removed in %14.0 as part of https://gitlab.com/groups/gitlab-org/-/epics/4280
# Also see https://gitlab.com/gitlab-org/gitlab/-/issues/366573
class ClusterUpdateAppWorker # rubocop:disable Scalability/IdempotentWorker class ClusterUpdateAppWorker # rubocop:disable Scalability/IdempotentWorker
UpdateAlreadyInProgressError = Class.new(StandardError) UpdateAlreadyInProgressError = Class.new(StandardError)
@ -16,38 +17,5 @@ class ClusterUpdateAppWorker # rubocop:disable Scalability/IdempotentWorker
LEASE_TIMEOUT = 10.minutes.to_i LEASE_TIMEOUT = 10.minutes.to_i
def perform(app_name, app_id, project_id, scheduled_time) def perform(app_name, app_id, project_id, scheduled_time); end
@app_id = app_id
try_obtain_lease do
execute(app_name, app_id, project_id, scheduled_time)
end
end
private
def execute(app_name, app_id, project_id, scheduled_time)
project = Project.find_by_id(project_id)
return unless project
find_application(app_name, app_id) do |app|
update_prometheus(app, scheduled_time, project)
end
end
def update_prometheus(app, scheduled_time, project)
return unless app.managed_prometheus?
return if app.updated_since?(scheduled_time)
return if app.update_in_progress?
Clusters::Applications::PrometheusUpdateService.new(app, project).execute
end
def lease_key
@lease_key ||= "#{self.class.name.underscore}-#{@app_id}"
end
def lease_timeout
LEASE_TIMEOUT
end
end end

View file

@ -23,6 +23,7 @@ options:
- users_updating_work_item_labels - users_updating_work_item_labels
- users_updating_work_item_iteration - users_updating_work_item_iteration
- users_updating_weight_estimate - users_updating_weight_estimate
- users_updating_work_item_milestone
data_category: optional data_category: optional
distribution: distribution:
- ce - ce

View file

@ -23,6 +23,7 @@ options:
- users_updating_work_item_labels - users_updating_work_item_labels
- users_updating_work_item_iteration - users_updating_work_item_iteration
- users_updating_weight_estimate - users_updating_weight_estimate
- users_updating_work_item_milestone
data_category: optional data_category: optional
distribution: distribution:
- ce - ce

View file

@ -23,6 +23,7 @@ options:
- users_updating_work_item_labels - users_updating_work_item_labels
- users_updating_work_item_iteration - users_updating_work_item_iteration
- users_updating_weight_estimate - users_updating_weight_estimate
- users_updating_work_item_milestone
data_category: optional data_category: optional
distribution: distribution:
- ce - ce

View file

@ -0,0 +1,25 @@
---
key_path: redis_hll_counters.work_items.users_updating_work_item_milestone_monthly
description: Unique users updating a work item's milestone
product_section: dev
product_stage: plan
product_group: project_management
product_category: team_planning
value_type: number
status: active
milestone: "15.6"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102495
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- users_updating_work_item_milestone
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View file

@ -23,6 +23,7 @@ options:
- users_updating_work_item_labels - users_updating_work_item_labels
- users_updating_work_item_iteration - users_updating_work_item_iteration
- users_updating_weight_estimate - users_updating_weight_estimate
- users_updating_work_item_milestone
data_category: optional data_category: optional
distribution: distribution:
- ce - ce

View file

@ -23,6 +23,7 @@ options:
- users_updating_work_item_labels - users_updating_work_item_labels
- users_updating_work_item_iteration - users_updating_work_item_iteration
- users_updating_weight_estimate - users_updating_weight_estimate
- users_updating_work_item_milestone
data_category: optional data_category: optional
distribution: distribution:
- ce - ce

View file

@ -23,6 +23,7 @@ options:
- users_updating_work_item_labels - users_updating_work_item_labels
- users_updating_work_item_iteration - users_updating_work_item_iteration
- users_updating_weight_estimate - users_updating_weight_estimate
- users_updating_work_item_milestone
data_category: optional data_category: optional
distribution: distribution:
- ce - ce

View file

@ -0,0 +1,25 @@
---
key_path: redis_hll_counters.work_items.users_updating_work_item_milestone_weekly
description: Unique users updating a work item's milestone
product_section: dev
product_stage: plan
product_group: project_management
product_category: team_planning
value_type: number
status: active
milestone: "15.6"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102495
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- users_updating_work_item_milestone
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View file

@ -0,0 +1,9 @@
---
table_name: namespace_commit_emails
classes:
- Users::NamespaceCommitEmail
feature_categories:
- source_code_management
description: User default email for commits from the GitLab UI
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101832
milestone: '15.6'

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
class CreateNamespaceCommitEmails < Gitlab::Database::Migration[2.0]
def change
create_table :namespace_commit_emails do |t|
t.references :user, index: false, null: false, foreign_key: { on_delete: :cascade }
t.references :namespace, null: false
t.references :email, null: false
t.timestamps_with_timezone null: false
t.index [:user_id, :namespace_id], unique: true
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddNamespaceCommitEmailsNamespaceFk < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :namespace_commit_emails, :namespaces, column: :namespace_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :namespace_commit_emails, column: :namespace_id
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddNamespaceCommitEmailsEmailFk < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :namespace_commit_emails, :emails, column: :email_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :namespace_commit_emails, column: :email_id
end
end
end

View file

@ -0,0 +1 @@
defe6e66c98648ea7fb77d8001392bc707ec022f639d346c42d23fad10958856

View file

@ -0,0 +1 @@
c48015b2ff6ad4b58bffaf5342247d890f6bd2388c467751654bc705f5eb53ed

View file

@ -0,0 +1 @@
739952c72f82b804b84d73107264804202ad102b425008d4dcb029c1f02e2118

View file

@ -17951,6 +17951,24 @@ CREATE TABLE namespace_ci_cd_settings (
allow_stale_runner_pruning boolean DEFAULT false NOT NULL allow_stale_runner_pruning boolean DEFAULT false NOT NULL
); );
CREATE TABLE namespace_commit_emails (
id bigint NOT NULL,
user_id bigint NOT NULL,
namespace_id bigint NOT NULL,
email_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE namespace_commit_emails_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE namespace_commit_emails_id_seq OWNED BY namespace_commit_emails.id;
CREATE TABLE namespace_details ( CREATE TABLE namespace_details (
namespace_id bigint NOT NULL, namespace_id bigint NOT NULL,
created_at timestamp with time zone, created_at timestamp with time zone,
@ -23974,6 +23992,8 @@ ALTER TABLE ONLY namespace_admin_notes ALTER COLUMN id SET DEFAULT nextval('name
ALTER TABLE ONLY namespace_bans ALTER COLUMN id SET DEFAULT nextval('namespace_bans_id_seq'::regclass); ALTER TABLE ONLY namespace_bans ALTER COLUMN id SET DEFAULT nextval('namespace_bans_id_seq'::regclass);
ALTER TABLE ONLY namespace_commit_emails ALTER COLUMN id SET DEFAULT nextval('namespace_commit_emails_id_seq'::regclass);
ALTER TABLE ONLY namespace_statistics ALTER COLUMN id SET DEFAULT nextval('namespace_statistics_id_seq'::regclass); ALTER TABLE ONLY namespace_statistics ALTER COLUMN id SET DEFAULT nextval('namespace_statistics_id_seq'::regclass);
ALTER TABLE ONLY namespaces ALTER COLUMN id SET DEFAULT nextval('namespaces_id_seq'::regclass); ALTER TABLE ONLY namespaces ALTER COLUMN id SET DEFAULT nextval('namespaces_id_seq'::regclass);
@ -26037,6 +26057,9 @@ ALTER TABLE ONLY namespace_bans
ALTER TABLE ONLY namespace_ci_cd_settings ALTER TABLE ONLY namespace_ci_cd_settings
ADD CONSTRAINT namespace_ci_cd_settings_pkey PRIMARY KEY (namespace_id); ADD CONSTRAINT namespace_ci_cd_settings_pkey PRIMARY KEY (namespace_id);
ALTER TABLE ONLY namespace_commit_emails
ADD CONSTRAINT namespace_commit_emails_pkey PRIMARY KEY (id);
ALTER TABLE ONLY namespace_details ALTER TABLE ONLY namespace_details
ADD CONSTRAINT namespace_details_pkey PRIMARY KEY (namespace_id); ADD CONSTRAINT namespace_details_pkey PRIMARY KEY (namespace_id);
@ -29679,6 +29702,12 @@ CREATE UNIQUE INDEX index_namespace_bans_on_namespace_id_and_user_id ON namespac
CREATE INDEX index_namespace_bans_on_user_id ON namespace_bans USING btree (user_id); CREATE INDEX index_namespace_bans_on_user_id ON namespace_bans USING btree (user_id);
CREATE INDEX index_namespace_commit_emails_on_email_id ON namespace_commit_emails USING btree (email_id);
CREATE INDEX index_namespace_commit_emails_on_namespace_id ON namespace_commit_emails USING btree (namespace_id);
CREATE UNIQUE INDEX index_namespace_commit_emails_on_user_id_and_namespace_id ON namespace_commit_emails USING btree (user_id, namespace_id);
CREATE UNIQUE INDEX index_namespace_root_storage_statistics_on_namespace_id ON namespace_root_storage_statistics USING btree (namespace_id); CREATE UNIQUE INDEX index_namespace_root_storage_statistics_on_namespace_id ON namespace_root_storage_statistics USING btree (namespace_id);
CREATE UNIQUE INDEX index_namespace_statistics_on_namespace_id ON namespace_statistics USING btree (namespace_id); CREATE UNIQUE INDEX index_namespace_statistics_on_namespace_id ON namespace_statistics USING btree (namespace_id);
@ -32849,6 +32878,9 @@ ALTER TABLE ONLY user_namespace_callouts
ALTER TABLE ONLY sbom_occurrences ALTER TABLE ONLY sbom_occurrences
ADD CONSTRAINT fk_4b88e5b255 FOREIGN KEY (component_version_id) REFERENCES sbom_component_versions(id) ON DELETE CASCADE; ADD CONSTRAINT fk_4b88e5b255 FOREIGN KEY (component_version_id) REFERENCES sbom_component_versions(id) ON DELETE CASCADE;
ALTER TABLE ONLY namespace_commit_emails
ADD CONSTRAINT fk_4d6ba63ba5 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_reads ALTER TABLE ONLY vulnerability_reads
ADD CONSTRAINT fk_4f593f6c62 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; ADD CONSTRAINT fk_4f593f6c62 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@ -33233,6 +33265,9 @@ ALTER TABLE ONLY issue_assignees
ALTER TABLE ONLY agent_project_authorizations ALTER TABLE ONLY agent_project_authorizations
ADD CONSTRAINT fk_b7fe9b4777 FOREIGN KEY (agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE; ADD CONSTRAINT fk_b7fe9b4777 FOREIGN KEY (agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
ALTER TABLE ONLY namespace_commit_emails
ADD CONSTRAINT fk_b8d89d555e FOREIGN KEY (email_id) REFERENCES emails(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_trigger_requests ALTER TABLE ONLY ci_trigger_requests
ADD CONSTRAINT fk_b8ec8b7245 FOREIGN KEY (trigger_id) REFERENCES ci_triggers(id) ON DELETE CASCADE; ADD CONSTRAINT fk_b8ec8b7245 FOREIGN KEY (trigger_id) REFERENCES ci_triggers(id) ON DELETE CASCADE;
@ -34937,6 +34972,9 @@ ALTER TABLE ONLY packages_debian_project_distributions
ALTER TABLE ONLY incident_management_oncall_shifts ALTER TABLE ONLY incident_management_oncall_shifts
ADD CONSTRAINT fk_rails_df4feb286a FOREIGN KEY (rotation_id) REFERENCES incident_management_oncall_rotations(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_df4feb286a FOREIGN KEY (rotation_id) REFERENCES incident_management_oncall_rotations(id) ON DELETE CASCADE;
ALTER TABLE ONLY namespace_commit_emails
ADD CONSTRAINT fk_rails_dfa4c104f5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY analytics_cycle_analytics_group_stages ALTER TABLE ONLY analytics_cycle_analytics_group_stages
ADD CONSTRAINT fk_rails_dfb37c880d FOREIGN KEY (end_event_label_id) REFERENCES labels(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_dfb37c880d FOREIGN KEY (end_event_label_id) REFERENCES labels(id) ON DELETE CASCADE;

View file

@ -171,7 +171,6 @@ deploy:
include: deploy.gitlab-ci.yml include: deploy.gitlab-ci.yml
strategy: depend strategy: depend
resource_group: AWS-production resource_group: AWS-production
environment: production
``` ```
```yaml ```yaml

View file

@ -11,7 +11,7 @@ at GitLab. We use Jest for JavaScript unit and integration testing,
and RSpec feature tests with Capybara for e2e (end-to-end) integration testing. and RSpec feature tests with Capybara for e2e (end-to-end) integration testing.
Unit and feature tests need to be written for all new features. Unit and feature tests need to be written for all new features.
Most of the time, you should use [RSpec](https://github.com/rspec/rspec-rails#feature-specs) for your feature tests. Most of the time, you should use [RSpec](https://github.com/rspec/rspec-rails#feature-specs) for your feature tests. For more information on how to get started with feature tests, see [get started with feature tests](#get-started-with-feature-tests)
Regression tests should be written for bug fixes to prevent them from recurring Regression tests should be written for bug fixes to prevent them from recurring
in the future. in the future.
@ -962,7 +962,7 @@ If an integration test depends on JavaScript to run correctly, you need to make
sure the spec is configured to enable JavaScript when the tests are run. If you sure the spec is configured to enable JavaScript when the tests are run. If you
don't do this, the spec runner displays vague error messages. don't do this, the spec runner displays vague error messages.
To enable a JavaScript driver in an `rspec` test, add `:js` to the To enable a JavaScript driver in an `RSpec` test, add `:js` to the
individual spec or the context block containing multiple specs that need individual spec or the context block containing multiple specs that need
JavaScript enabled: JavaScript enabled:
@ -1242,6 +1242,332 @@ A good guideline to follow: the more complex the component you may want to steer
- To just have some kind of test written - To just have some kind of test written
- To capture highly volatile UI elements without stubbing them (Think of GitLab UI version updates) - To capture highly volatile UI elements without stubbing them (Think of GitLab UI version updates)
## Get started with feature tests
### What is a feature test
A [feature test](testing_levels.md#white-box-tests-at-the-system-level-formerly-known-as-system--feature-tests), also known as `white-box testing`, is a test that spawns a browser and has Capybara helpers. This means the test can:
- Locate an element in the browser.
- Click that element.
- Call the API.
Feature tests are expensive to run. You should make sure that you **really want** this type of test before running one.
All of our feature tests are written in `Ruby` but often end up being written by `JavaScript` engineers, as they implement the user-facing feature. So, the following section assumes no prior knowledge of `Ruby` or `Capybara`, and provide a clear guideline on when and how to use these tests.
### When to use feature tests
You should use a feature test when the test:
- Is across multiple components.
- Requires that a user navigate across pages.
- Is submitting a form and observing results elsewhere.
- Would result in a huge number of mocking and stubbing with fake data and components if done as a unit test.
Feature tests are especially useful when you want to test:
- That multiple components are working together successfully.
- Complex API interactions. Feature tests interact with the API, so they are slower but do not need any level of mocking or fixtures.
### When not to use feature tests
You should use `jest` and `vue-test-utils` unit tests instead of a feature test if you can get the same test results from these methods. Feature tests are quite expensive to run.
You should use a unit test if:
- The behavior you are implementing is all in one component.
- You can simulate other components' behavior to trigger the desired effect.
- You can already select UI elements in the virtual DOM to trigger the desired effects.
Also, if a behavior in your new code needs multiple components to work together, you should consider testing your behavior higher in the component tree. For example, let's say that we have a component called `ParentComponent` with the code:
```vue
<script>
export default{
name: ParentComponent,
data(){
return {
internalData: 'oldValue'
}
},
methods:{
changeSomeInternalData(newVal){
this.internalData = newVal
}
}
}
</script>
<template>
<div>
<child-component-1 @child-event="changeSomeInternalData" />
<child-component-2 :parent-data="internalData" />
</div>
</template>
```
In this example:
- `ChildComponent1` emits an event.
- `ParentComponent` changes its `internalData` value.
- `ParentComponent` passes the props down to `ChildComponent2`.
You can use a unit test instead by:
- From inside the `ParentComponent` unit test file, emitting the expected event from `childComponent1`
- Making sure the prop is passed down to `childComponent2`.
Then each child component unit tests what happens when the event is emitted and when the prop changes.
This example also applies at larger scale and with deeper component trees. It is definitely worth using unit tests and avoiding the extra cost of feature tests if you can:
- Confidently mount child components.
- Emit events or select elements in the virtual DOM.
- Get the test behavior that you want.
### Where to create your test
Feature tests live in `spec/features` folder. You should look for existing files that can test the page you are adding a feature to. Within that folder, you can locate your section. For example, if you wanted to add a new feature test for the pipeline page, you would look in `spec/features/projects/pipelines` and see if the test you want to write exists here.
### How to run a feature test
1. Make sure that you have a working GDK environment.
1. Start your `gdk` environment with `gdk start` command.
1. In your terminal, run:
```shell
bundle exec rspec path/to/file:line_of_my_test
```
You can also prefix this command with `WEBDRIVER_HEADLESS=0` which will run the test by opening an actual browser on your computer that you can see, which is very useful for debugging.
### How to write a test
#### Basic file structure
1. Make all string literals unchangeable
In all feature tests, the very first line should be:
```ruby
# frozen_string_literal: true
```
This is in every `Ruby` file and makes all string literals unchangeable. There are also some performance benefits, but this is beyond the scope of this section.
1. Import dependencies.
You should import the modules you need. You will most likely always need to require `spec_helper`:
```ruby
require 'spec_helper'
```
Import any other relevant module.
1. Create a global scope for RSpec to define our tests, just like what we do in jest with the initial describe block.
Then, you need to create the very first `RSpec` scope.
```ruby
RSpec.describe 'Pipeline', :js do
...
end
```
What is different though, is that just like everything in Ruby, this is actually a `class`. Which means that right at the top, you can `include` modules that you'd need for your test. For example, you could include the `RoutesHelpers` to navigate more easily.
```ruby
RSpec.describe 'Pipeline', :js do
include RoutesHelpers
...
end
```
After all of this implementation, we have a file that looks something like this:
```ruby
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Pipeline', :js do
include RoutesHelpers
end
```
#### Seeding data
Each test is in its own environment and so you must use a factory to seed the required data. To continue on with the pipeline example, let's say that you want a test that takes you to the main pipeline page, which is at the route `/namespace/project/-/pipelines/:id/`.
Most feature tests at least require you to create a user, because you want to be signed in. You can skip this step if you don't have to be signed in, but as a general rule, you should **always create a user unless you are specifically testing a feature looked at by an anonymous user**. This makes sure that you explicitly set a level of permission that you can edit in the test as needed to change or test a new level of permission as the section changes. To create a user:
```ruby
let(:user) { create(:user) }
```
This creates a variable that holds the newly created user and we can use `create` because we imported the `spec_helper`.
However, we have not done anything with this user yet because it's just a variable. So, in the `before do` block of the spec, we could sign in with the user so that every spec starts with a signed in user.
```ruby
let(:user) { create(:user) }
before do
sign_in(user)
end
```
Now that we have a user, we should look at what else we'd need before asserting anything on a pipeline page. If you look at the route `/namespace/project/-/pipelines/:id/` we can determine we need a project and a pipeline.
So we'd create a project and pipeline, and link them together. Usually in factories, the child element requires its parent as an argument. In this case, a pipeline is a child of a project. So we can create the project first, and then when we create the pipeline, we are pass the project as an argument which "binds" the pipeline to the project. A pipeline is also owned by a user, so we need the user as well. For example, this creates a project and a pipeline:
```ruby
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
```
In the same spirit, you could then create a job (build) by using the build factory and passing the parent pipeline:
```ruby
create(:ci_build, pipeline: pipeline, stage_idx: 10, stage: 'publish', name: 'CentOS')
```
There are many factories that already exists, so make sure to look at other existing files to see if what you need is available.
#### Navigation
You can navigate to a page by using the `visit` method and passing the path as an argument. Rails automatically generates helper paths, so make sure to use these instead of a hardcoded string. They are generated using the route model, so if we want to go to a pipeline, we'd use:
```ruby
visit project_pipeline_path(project, pipeline)
```
Before executing any page interaction when navigating or making asynchronous call through the UI, make sure to use `wait_for_requests` before proceeding with further instructions.
#### Elements interaction
There are a lot of different ways to find and interact with elements. For example, you could use the basic `find` method with the `selector` and `text` parameter and then use the `.click` method
```ruby
find('.gl-tab-nav-item', text: 'Tests').click
```
Alternatively, you could use `click_button` with a string of text that is found within the button, which is a more semantically meaningful way of clicking the element.
```ruby
click_button 'Text inside the button element'
```
If you want to follow a link, then there is `click_link`:
```ruby
click_link 'Text inside the link tag'
```
You can use `fill_in` to fill input / form elements. The first argument is the selector, the second is `with:` which is the value to pass in.
```ruby
fill_in 'current_password', with: '123devops'
```
Alternatively, you can use the `find` selector paired with `send_keys` to add keys in a field without removing previous text, or `set` which completely replaces the value of the input element.
All of these are valid selectors and methods. Pick whichever suits your needs and look around as there are many more useful ones!
#### Assertions
To assert anything in a page, you can always access `page` variable, which is automatically defines and actually means the page document. This means you can expect the `page` to have certain components like selectors or content. Here are a few examples:
```ruby
# Finding an element by ID
expect(page).to have_selector('#js-pipeline-graph')
```
```ruby
# Finding by text
expect(page).to have_content('build')
```
```ruby
# Finding by `href` value
expect(page).to have_link(pipeline.ref)
```
```ruby
# Finding by CSS selector. This is a last resort.
# For example, when you cannot add attributes on the desired element.
expect(page).to have_css('.js-icon-retry')
```
```ruby
# Find by data-testid
# Like CSS selector, this is acceptable when there isn't a specific matcher available.
expect(page).to have_selector('[data-testid="pipeline-multi-actions-dropdown"]')
```
```ruby
# You can combine any of these selectors with `not_to` instead
expect(page).not_to have_selector('#js-pipeline-graph')
```
```ruby
# When a test case has back to back expectations,
# it is recommended to group them using `:aggregate_failures`
it 'shows the issue description and design references', :aggregate_failures do
expect(page).to have_text('The designs I mentioned')
expect(page).to have_link(design_tab_ref)
expect(page).to have_link(design_ref_a)
expect(page).to have_link(design_ref_b)
end
```
You can also create a sub-block to look into, to:
- Scope down where you are making your assertions and reduce the risk of finding another element that was not intended.
- Make sure an element is found within the right boundaries.
```ruby
page.within('#js-pipeline-graph') do
...
end
```
#### Feature flags
By default, every feature flag is enabled **regardless of the YAML definition or the flags you've set manually in your GDK**. To test when a feature flag is disabled, you must manually stub the flag, ideally in a `before do` block.
```ruby
stub_feature_flags(my_feature_flag: false)
```
If you are stubbing an `ee` feature flag, then use:
```ruby
stub_licensed_features(my_feature_flag: false)
```
### Debugging
You can run your spec with the prefix `WEBDRIVER_HEADLESS=0` to open an actual browser. However, the specs goes though the commands quickly and leaves you no time to look around.
To avoid this problem, you can write `binding.pry` on the line where you want Capybara to stop execution. You are then inside the browser with normal usage. To understand why you cannot find certain elements, you can:
- Select elements.
- Use the console and network tab.
- Execute selectors inside the browser console.
Inside the terminal, where capybara is running, you can also execute `next` which goes line by line through the test. This way you can check every single interaction one by one to see what might be causing an issue.
### Updating ChromeDriver
On MacOS, if you get a ChromeDriver error, make sure to update it by running
```shell
brew upgrade chromedriver
```
--- ---
[Return to Testing documentation](index.md) [Return to Testing documentation](index.md)

View file

@ -43,6 +43,11 @@ system tests, parameterized tests etc.
Everything you should know about how to write good Frontend tests: Jest, Everything you should know about how to write good Frontend tests: Jest,
testing promises, stubbing etc. testing promises, stubbing etc.
## [Getting started with Feature tests](frontend_testing.md#get-started-with-feature-tests)
Need to get started with feature tests? Here are some general guidelines,
tips and tricks to get the most out of white-box testing.
## [Flaky tests](flaky_tests.md) ## [Flaky tests](flaky_tests.md)
What are flaky tests, the different kind of flaky tests we encountered, and what What are flaky tests, the different kind of flaky tests we encountered, and what

View file

@ -343,6 +343,7 @@ namespace_limits: :gitlab_main
namespace_package_settings: :gitlab_main namespace_package_settings: :gitlab_main
namespace_root_storage_statistics: :gitlab_main namespace_root_storage_statistics: :gitlab_main
namespace_ci_cd_settings: :gitlab_main namespace_ci_cd_settings: :gitlab_main
namespace_commit_emails: :gitlab_main
namespace_settings: :gitlab_main namespace_settings: :gitlab_main
namespace_details: :gitlab_main namespace_details: :gitlab_main
namespaces: :gitlab_main namespaces: :gitlab_main

View file

@ -19,6 +19,11 @@
redis_slot: users redis_slot: users
aggregation: weekly aggregation: weekly
feature_flag: track_work_items_activity feature_flag: track_work_items_activity
- name: users_updating_work_item_milestone
category: work_items
redis_slot: users
aggregation: weekly
feature_flag: track_work_items_activity
- name: users_updating_work_item_iteration - name: users_updating_work_item_iteration
# The event tracks an EE feature. # The event tracks an EE feature.
# It's added here so it can be aggregated into the CE/EE 'OR' aggregate metrics. # It's added here so it can be aggregated into the CE/EE 'OR' aggregate metrics.

View file

@ -7,6 +7,7 @@ module Gitlab
WORK_ITEM_TITLE_CHANGED = 'users_updating_work_item_title' WORK_ITEM_TITLE_CHANGED = 'users_updating_work_item_title'
WORK_ITEM_DATE_CHANGED = 'users_updating_work_item_dates' WORK_ITEM_DATE_CHANGED = 'users_updating_work_item_dates'
WORK_ITEM_LABELS_CHANGED = 'users_updating_work_item_labels' WORK_ITEM_LABELS_CHANGED = 'users_updating_work_item_labels'
WORK_ITEM_MILESTONE_CHANGED = 'users_updating_work_item_milestone'
class << self class << self
def track_work_item_created_action(author:) def track_work_item_created_action(author:)
@ -25,6 +26,10 @@ module Gitlab
track_unique_action(WORK_ITEM_LABELS_CHANGED, author) track_unique_action(WORK_ITEM_LABELS_CHANGED, author)
end end
def track_work_item_milestone_changed_action(author:)
track_unique_action(WORK_ITEM_MILESTONE_CHANGED, author)
end
private private
def track_unique_action(action, author) def track_unique_action(action, author)

View file

@ -5,7 +5,7 @@ source 'https://rubygems.org'
gem 'gitlab-qa', '~> 8', '>= 8.10.1', require: 'gitlab/qa' gem 'gitlab-qa', '~> 8', '>= 8.10.1', require: 'gitlab/qa'
gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.18.0' gem 'allure-rspec', '~> 2.18.0'
gem 'capybara', '~> 3.37.1' gem 'capybara', '~> 3.38.0'
gem 'capybara-screenshot', '~> 1.0.26' gem 'capybara-screenshot', '~> 1.0.26'
gem 'rake', '~> 13', '>= 13.0.6' gem 'rake', '~> 13', '>= 13.0.6'
gem 'rspec', '~> 3.12' gem 'rspec', '~> 3.12'

View file

@ -27,7 +27,7 @@ GEM
binding_ninja (0.2.3) binding_ninja (0.2.3)
builder (3.2.4) builder (3.2.4)
byebug (11.1.3) byebug (11.1.3)
capybara (3.37.1) capybara (3.38.0)
addressable addressable
matrix matrix
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
@ -302,7 +302,7 @@ DEPENDENCIES
activesupport (~> 6.1.4.7) activesupport (~> 6.1.4.7)
airborne (~> 0.3.7) airborne (~> 0.3.7)
allure-rspec (~> 2.18.0) allure-rspec (~> 2.18.0)
capybara (~> 3.37.1) capybara (~> 3.38.0)
capybara-screenshot (~> 1.0.26) capybara-screenshot (~> 1.0.26)
chemlab (~> 0.10) chemlab (~> 0.10)
chemlab-library-www-gitlab-com (~> 0.1, >= 0.1.1) chemlab-library-www-gitlab-com (~> 0.1, >= 0.1.1)

View file

@ -27,7 +27,7 @@ module QA
errors = ["Correlation Id: #{correlation_id}"] errors = ["Correlation Id: #{correlation_id}"]
errors << "Sentry Url: #{sentry_uri}&query=correlation_id%3A%22#{correlation_id}%22" if sentry_uri errors << "Sentry Url: #{sentry_uri}&query=correlation_id%3A%22#{correlation_id}%22" if sentry_uri
errors << "Kibana Url: #{kibana_uri}app/discover#/?_a=(query:(language:kuery,query:'json.correlation_id%20:%20#{correlation_id}'))&_g=(time:(from:now-24h,to:now))" if kibana_uri errors << "Kibana Url: #{kibana_uri}app/discover#/?_a=%28query%3A%28language%3Akuery%2Cquery%3A%27json.correlation_id%20%3A%20#{correlation_id}%27%29%29&_g=%28time%3A%28from%3Anow-24h%2Cto%3Anow%29%29" if kibana_uri
errors.join("\n") errors.join("\n")
end end

View file

@ -156,7 +156,7 @@ RSpec.describe QA::Resource::ApiFabricator do
Fabrication of FooBarResource using the API failed (400) with `#{raw_post}`. Fabrication of FooBarResource using the API failed (400) with `#{raw_post}`.
Correlation Id: foobar Correlation Id: foobar
Sentry Url: https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg&query=correlation_id%3A%22foobar%22 Sentry Url: https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg&query=correlation_id%3A%22foobar%22
Kibana Url: https://nonprod-log.gitlab.net/app/discover#/?_a=(query:(language:kuery,query:'json.correlation_id%20:%20foobar'))&_g=(time:(from:now-24h,to:now)) Kibana Url: https://nonprod-log.gitlab.net/app/discover#/?_a=%28query%3A%28language%3Akuery%2Cquery%3A%27json.correlation_id%20%3A%20foobar%27%29%29&_g=%28time%3A%28from%3Anow-24h%2Cto%3Anow%29%29
ERROR ERROR
end end
end end

View file

@ -29,7 +29,7 @@ RSpec.describe QA::Support::Loglinking do
expect(QA::Support::Loglinking.failure_metadata('foo123')).to eql(<<~ERROR.chomp) expect(QA::Support::Loglinking.failure_metadata('foo123')).to eql(<<~ERROR.chomp)
Correlation Id: foo123 Correlation Id: foo123
Kibana Url: https://kibana.address/app/discover#/?_a=(query:(language:kuery,query:'json.correlation_id%20:%20foo123'))&_g=(time:(from:now-24h,to:now)) Kibana Url: https://kibana.address/app/discover#/?_a=%28query%3A%28language%3Akuery%2Cquery%3A%27json.correlation_id%20%3A%20foo123%27%29%29&_g=%28time%3A%28from%3Anow-24h%2Cto%3Anow%29%29
ERROR ERROR
end end
end end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
FactoryBot.define do
factory :namespace_commit_email, class: 'Users::NamespaceCommitEmail' do
email
user { email.user }
namespace
end
end

View file

@ -21,6 +21,6 @@ RSpec.describe 'IDE merge request', :js do
wait_for_requests wait_for_requests
expect(page).to have_selector('.monaco-diff-editor') expect(page).not_to have_selector('.monaco-diff-editor')
end end
end end

View file

@ -7,6 +7,19 @@ RSpec.describe 'Branches' do
let_it_be(:project) { create(:project, :public, :repository) } let_it_be(:project) { create(:project, :public, :repository) }
let(:repository) { project.repository } let(:repository) { project.repository }
context 'when logged in as reporter' do
before do
sign_in(user)
project.add_reporter(user)
end
it 'does not show delete button' do
visit project_branches_path(project)
expect(page).not_to have_css '.js-delete-branch-button'
end
end
context 'when logged in as developer' do context 'when logged in as developer' do
before do before do
sign_in(user) sign_in(user)

View file

@ -15,7 +15,7 @@ RSpec.describe HooksHelper do
it 'returns proper data' do it 'returns proper data' do
expect(subject).to match( expect(subject).to match(
url: project_hook.url, url: project_hook.url,
url_variables: Gitlab::Json.dump([]) url_variables: "[]"
) )
end end
end end
@ -26,7 +26,7 @@ RSpec.describe HooksHelper do
it 'returns proper data' do it 'returns proper data' do
expect(subject).to match( expect(subject).to match(
url: project_hook.url, url: project_hook.url,
url_variables: Gitlab::Json.dump(['abc']) url_variables: Gitlab::Json.dump([{ key: 'abc' }])
) )
end end
end end

View file

@ -15,6 +15,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::WorkItemsActivityAggreg
users_creating_work_items users_creating_work_items
users_updating_work_item_title users_updating_work_item_title
users_updating_work_item_dates users_updating_work_item_dates
users_updating_work_item_labels
users_updating_work_item_milestone
users_updating_work_item_iteration users_updating_work_item_iteration
] ]
} }
@ -57,6 +59,7 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::WorkItemsActivityAggreg
counter.track_event(:users_updating_work_item_title, values: author1_id, time: event_time) counter.track_event(:users_updating_work_item_title, values: author1_id, time: event_time)
counter.track_event(:users_updating_work_item_dates, values: author1_id, time: event_time) counter.track_event(:users_updating_work_item_dates, values: author1_id, time: event_time)
counter.track_event(:users_updating_work_item_labels, values: author1_id, time: event_time) counter.track_event(:users_updating_work_item_labels, values: author1_id, time: event_time)
counter.track_event(:users_updating_work_item_milestone, values: author1_id, time: event_time)
end.to not_change { described_class.new(metric_definition).value } end.to not_change { described_class.new(metric_definition).value }
expect do expect do

View file

@ -36,4 +36,12 @@ RSpec.describe Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter, :clean_
it_behaves_like 'work item unique counter' it_behaves_like 'work item unique counter'
end end
describe '.track_work_item_milestone_changed_action' do
subject(:track_event) { described_class.track_work_item_milestone_changed_action(author: user) }
let(:event_name) { described_class::WORK_ITEM_MILESTONE_CHANGED }
it_behaves_like 'work item unique counter'
end
end end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Users::NamespaceCommitEmail, type: :model do
subject { build(:namespace_commit_email) }
describe 'associations' do
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:namespace) }
it { is_expected.to belong_to(:email) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:namespace) }
it { is_expected.to validate_presence_of(:email) }
end
it { is_expected.to be_valid }
end

View file

@ -1,111 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::Applications::PrometheusUpdateService do
describe '#execute' do
let(:project) { create(:project) }
let(:environment) { create(:environment, project: project) }
let(:cluster) { create(:cluster, :provided_by_user, :with_installed_helm, projects: [project]) }
let(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
let(:empty_alerts_values_update_yaml) { "---\nalertmanager:\n enabled: false\nserverFiles:\n alerts: {}\n" }
let(:helm_client) { instance_double(::Gitlab::Kubernetes::Helm::API) }
subject(:service) { described_class.new(application, project) }
context 'when prometheus is a Clusters::Integrations::Prometheus' do
let(:application) { create(:clusters_integrations_prometheus, cluster: cluster) }
it 'raises NotImplementedError' do
expect { service.execute }.to raise_error(NotImplementedError)
end
end
context 'when prometheus is externally installed' do
let(:application) { create(:clusters_applications_prometheus, :externally_installed, cluster: cluster) }
it 'raises NotImplementedError' do
expect { service.execute }.to raise_error(NotImplementedError)
end
end
context 'when prometheus is a Clusters::Applications::Prometheus' do
let!(:patch_command) { application.patch_command(empty_alerts_values_update_yaml) }
before do
allow(service).to receive(:patch_command).with(empty_alerts_values_update_yaml).and_return(patch_command)
allow(service).to receive(:helm_api).and_return(helm_client)
end
context 'when there are no errors' do
before do
expect(helm_client).to receive(:update).with(patch_command)
allow(::ClusterWaitForAppUpdateWorker)
.to receive(:perform_in)
.and_return(nil)
end
it 'make the application updating' do
expect(application.cluster).not_to be_nil
service.execute
expect(application).to be_updating
end
it 'updates current config' do
prometheus_config_service = spy(:prometheus_config_service)
expect(Clusters::Applications::PrometheusConfigService)
.to receive(:new)
.with(project, cluster, application)
.and_return(prometheus_config_service)
expect(prometheus_config_service)
.to receive(:execute)
.and_return(YAML.safe_load(empty_alerts_values_update_yaml))
service.execute
end
it 'schedules async update status check' do
expect(::ClusterWaitForAppUpdateWorker).to receive(:perform_in).once
service.execute
end
end
context 'when k8s cluster communication fails' do
before do
error = ::Kubeclient::HttpError.new(500, 'system failure', nil)
allow(helm_client).to receive(:update).and_raise(error)
end
it 'make the application update errored' do
service.execute
expect(application).to be_update_errored
expect(application.status_reason).to match(/kubernetes error:/i)
end
end
context 'when application cannot be persisted' do
let(:application) { build(:clusters_applications_prometheus, :installed) }
before do
allow(application).to receive(:make_updating!).once
.and_raise(ActiveRecord::RecordInvalid.new(application))
end
it 'make the application update errored' do
expect(helm_client).not_to receive(:update)
service.execute
expect(application).to be_update_errored
end
end
end
end
end

View file

@ -14,4 +14,35 @@ RSpec.describe ResourceEvents::ChangeMilestoneService do
let_it_be(:resource) { create(issuable) } # rubocop:disable Rails/SaveBang let_it_be(:resource) { create(issuable) } # rubocop:disable Rails/SaveBang
end end
end end
describe 'events tracking' do
let_it_be(:user) { create(:user) }
let(:resource) { create(resource_type, milestone: timebox, project: timebox.project) }
subject(:service_instance) { described_class.new(resource, user, old_milestone: nil) }
context 'when the resource is a work item' do
let(:resource_type) { :work_item }
it 'tracks work item usage data counters' do
expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter)
.to receive(:track_work_item_milestone_changed_action)
.with(author: user)
service_instance.execute
end
end
context 'when the resource is not a work item' do
let(:resource_type) { :issue }
it 'does not track work item usage data counters' do
expect(Gitlab::UsageDataCounters::WorkItemActivityUniqueCounter)
.not_to receive(:track_work_item_milestone_changed_action)
service_instance.execute
end
end
end
end end

View file

@ -1,112 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ClusterUpdateAppWorker do
include ExclusiveLeaseHelpers
let_it_be(:project) { create(:project) }
let(:prometheus_update_service) { spy }
subject { described_class.new }
around do |example|
freeze_time { example.run }
end
before do
allow(::Clusters::Applications::PrometheusUpdateService).to receive(:new).and_return(prometheus_update_service)
end
describe '#perform' do
context 'when the application last_update_started_at is higher than the time the job was scheduled in' do
it 'does nothing' do
application = create(:clusters_applications_prometheus, :updated, last_update_started_at: Time.current)
expect(prometheus_update_service).not_to receive(:execute)
expect(subject.perform(application.name, application.id, project.id, Time.current - 5.minutes)).to be_nil
end
end
context 'when another worker is already running' do
it 'returns nil' do
application = create(:clusters_applications_prometheus, :updating)
expect(subject.perform(application.name, application.id, project.id, Time.current)).to be_nil
end
end
it 'executes PrometheusUpdateService' do
application = create(:clusters_applications_prometheus, :installed)
expect(prometheus_update_service).to receive(:execute)
subject.perform(application.name, application.id, project.id, Time.current)
end
context 'application is externally installed' do
it 'does not execute PrometheusUpdateService' do
application = create(:clusters_applications_prometheus, :externally_installed)
expect(prometheus_update_service).not_to receive(:execute)
subject.perform(application.name, application.id, project.id, Time.current)
end
end
context 'with exclusive lease' do
let_it_be(:user) { create(:user) }
let(:application) { create(:clusters_applications_prometheus, :installed) }
let(:lease_key) { "#{described_class.name.underscore}-#{application.id}" }
before do
# update_highest_role uses exclusive key too:
allow(Gitlab::ExclusiveLease).to receive(:new).and_call_original
stub_exclusive_lease_taken(lease_key)
end
it 'does not allow same app to be updated concurrently by same project' do
expect(Clusters::Applications::PrometheusUpdateService).not_to receive(:new)
subject.perform(application.name, application.id, project.id, Time.current)
end
it 'does not allow same app to be updated concurrently by different project', :aggregate_failures do
project1 = create(:project, namespace: create(:namespace, owner: user))
expect(Clusters::Applications::PrometheusUpdateService).not_to receive(:new)
subject.perform(application.name, application.id, project1.id, Time.current)
end
it 'allows different app to be updated concurrently by same project' do
application2 = create(:clusters_applications_prometheus, :installed)
lease_key2 = "#{described_class.name.underscore}-#{application2.id}"
stub_exclusive_lease(lease_key2)
expect(Clusters::Applications::PrometheusUpdateService).to receive(:new)
.with(application2, project)
subject.perform(application2.name, application2.id, project.id, Time.current)
end
it 'allows different app to be updated by different project', :aggregate_failures do
application2 = create(:clusters_applications_prometheus, :installed)
lease_key2 = "#{described_class.name.underscore}-#{application2.id}"
project2 = create(:project, namespace: create(:namespace, owner: user))
stub_exclusive_lease(lease_key2)
expect(Clusters::Applications::PrometheusUpdateService).to receive(:new)
.with(application2, project2)
subject.perform(application2.name, application2.id, project2.id, Time.current)
end
end
end
end