Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-27 18:07:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-27 18:07:02 +0300
commitd9953dadb4f0170a32fbb3204564f2f96396656e (patch)
tree5106d7c970874f3afcf39cca0d8adfc76abc09c3 /spec
parentf8740a1ade9d4614dde927d8983eeb288e783ccf (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/graphql/types/ml/model_type_spec.rb2
-rw-r--r--spec/helpers/ci/status_helper_spec.rb13
-rw-r--r--spec/models/member_spec.rb26
-rw-r--r--spec/models/members/group_member_spec.rb12
-rw-r--r--spec/requests/api/graphql/mutations/ml/models/create_spec.rb48
-rw-r--r--spec/services/click_house/sync_strategies/base_sync_strategy_spec.rb169
-rw-r--r--spec/services/click_house/sync_strategies/event_sync_strategy_spec.rb128
-rw-r--r--spec/services/members/update_service_spec.rb2
-rw-r--r--spec/services/ml/create_model_service_spec.rb5
-rw-r--r--spec/services/notification_service_spec.rb48
-rw-r--r--spec/support/rspec_order_todo.yml6
-rw-r--r--spec/support/shared_examples/models/members_notifications_shared_example.rb16
-rw-r--r--spec/workers/click_house/events_sync_worker_spec.rb174
13 files changed, 424 insertions, 225 deletions
diff --git a/spec/graphql/types/ml/model_type_spec.rb b/spec/graphql/types/ml/model_type_spec.rb
index 79edea29eb8..078391f135a 100644
--- a/spec/graphql/types/ml/model_type_spec.rb
+++ b/spec/graphql/types/ml/model_type_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['MlModel'], feature_category: :mlops do
specify { expect(described_class.description).to eq('Machine learning model in the model registry') }
it 'includes all the package fields' do
- expected_fields = %w[id name versions candidates version_count _links created_at latest_version]
+ expected_fields = %w[id name versions candidates version_count _links created_at latest_version description]
expect(described_class).to include_graphql_fields(*expected_fields)
end
diff --git a/spec/helpers/ci/status_helper_spec.rb b/spec/helpers/ci/status_helper_spec.rb
index 502a535e102..a00a80ac06b 100644
--- a/spec/helpers/ci/status_helper_spec.rb
+++ b/spec/helpers/ci/status_helper_spec.rb
@@ -8,19 +8,6 @@ RSpec.describe Ci::StatusHelper do
let(:success_commit) { double("Ci::Pipeline", status: 'success') }
let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
- describe "#pipeline_status_cache_key" do
- it "builds a cache key for pipeline status" do
- pipeline_status = Gitlab::Cache::Ci::ProjectPipelineStatus.new(
- build_stubbed(:project),
- pipeline_info: {
- sha: "123abc",
- status: "success"
- }
- )
- expect(helper.pipeline_status_cache_key(pipeline_status)).to eq("pipeline-status/123abc-success")
- end
- end
-
describe "#render_ci_icon" do
subject { helper.render_ci_icon("success") }
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 32303f76684..38b1cf35bbf 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -1095,6 +1095,32 @@ RSpec.describe Member, feature_category: :groups_and_projects do
end
end
+ context 'when after_update :post_update_hook' do
+ let_it_be(:member) { create(:group_member, :developer) }
+
+ context 'when access_level is changed' do
+ it 'calls NotificationService.update_member' do
+ expect(NotificationService).to receive_message_chain(:new, :updated_member_access_level).with(member)
+
+ member.update_attribute(:access_level, Member::MAINTAINER)
+ end
+
+ it 'does not send an email when the access level has not changed' do
+ expect(NotificationService).not_to receive(:new)
+
+ member.touch
+ end
+ end
+
+ context 'when expiration is changed' do
+ it 'calls the notification service when membership expiry has changed' do
+ expect(NotificationService).to receive_message_chain(:new, :updated_member_expiration).with(member)
+
+ member.update!(expires_at: 5.days.from_now)
+ end
+ end
+ end
+
describe 'log_invitation_token_cleanup' do
let_it_be(:project) { create :project }
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 7307e76272d..a7508f0e616 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -202,18 +202,6 @@ RSpec.describe GroupMember, feature_category: :cell do
end
end
- context 'when group member expiration date is updated' do
- let_it_be(:group_member) { create(:group_member) }
-
- it 'emails the user that their group membership expiry has changed' do
- expect_next_instance_of(NotificationService) do |notification|
- allow(notification).to receive(:updated_group_member_expiration).with(group_member)
- end
-
- group_member.update!(expires_at: 5.days.from_now)
- end
- end
-
describe 'refresh_member_authorized_projects' do
context 'when importing' do
it 'does not refresh' do
diff --git a/spec/requests/api/graphql/mutations/ml/models/create_spec.rb b/spec/requests/api/graphql/mutations/ml/models/create_spec.rb
new file mode 100644
index 00000000000..0daabeab0d1
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ml/models/create_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Creation of a machine learning model', feature_category: :mlops do
+ include GraphqlHelpers
+
+ let_it_be(:model) { create(:ml_models) }
+ let_it_be(:project) { model.project }
+ let_it_be(:current_user) { project.owner }
+
+ let(:input) { { project_path: project.full_path, name: name, description: description } }
+ let(:name) { 'some_name' }
+ let(:description) { 'A description' }
+
+ let(:mutation) { graphql_mutation(:ml_model_create, input) }
+ let(:mutation_response) { graphql_mutation_response(:ml_model_create) }
+
+ context 'when user is not allowed write changes' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(current_user, :write_model_registry, project)
+ .and_return(false)
+ end
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user is allowed write changes' do
+ it 'creates a models' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['model']).to include(
+ 'name' => name,
+ 'description' => description
+ )
+ end
+
+ context 'when name already exists' do
+ err_msg = "Name has already been taken"
+ let(:name) { model.name }
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: [err_msg]
+ end
+ end
+end
diff --git a/spec/services/click_house/sync_strategies/base_sync_strategy_spec.rb b/spec/services/click_house/sync_strategies/base_sync_strategy_spec.rb
new file mode 100644
index 00000000000..eb9324fd24b
--- /dev/null
+++ b/spec/services/click_house/sync_strategies/base_sync_strategy_spec.rb
@@ -0,0 +1,169 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ClickHouse::SyncStrategies::BaseSyncStrategy, feature_category: :value_stream_management do
+ let(:strategy) { described_class.new }
+
+ describe '#execute' do
+ subject(:execute) { strategy.execute }
+
+ context 'when clickhouse configuration database is available', :click_house do
+ before do
+ allow(strategy).to receive(:model_class).and_return(::Event)
+ allow(strategy).to receive(:projections).and_return([:id])
+ allow(strategy).to receive(:csv_mapping).and_return({ id: :id })
+ allow(strategy).to receive(:insert_query).and_return("INSERT INTO events (id) SETTINGS async_insert=1,
+ wait_for_async_insert=1 FORMAT CSV")
+ end
+
+ context 'when there is nothing to sync' do
+ it 'adds metadata for the worker' do
+ expect(execute).to eq({ status: :processed, records_inserted: 0, reached_end_of_table: true })
+
+ events = ClickHouse::Client.select('SELECT * FROM events', :main)
+ expect(events).to be_empty
+ end
+ end
+
+ context 'when syncing records' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:project_event2) { create(:event, :closed, project: project, target: issue) }
+ let_it_be(:event_without_parent) { create(:event, :joined, project: nil, group: nil) }
+ let_it_be(:group_event) { create(:event, :created, group: group, project: nil) }
+ let_it_be(:project_event1) { create(:event, :created, project: project, target: issue) }
+
+ it 'inserts all records' do
+ expect(execute).to eq({ status: :processed, records_inserted: 4, reached_end_of_table: true })
+
+ expected_records = [
+ hash_including('id' => project_event2.id),
+ hash_including('id' => event_without_parent.id),
+ hash_including('id' => group_event.id),
+ hash_including('id' => project_event1.id)
+ ]
+
+ events = ClickHouse::Client.select('SELECT * FROM events ORDER BY id', :main)
+
+ expect(events).to match(expected_records)
+
+ last_processed_id = ClickHouse::SyncCursor.cursor_for(:events)
+ expect(last_processed_id).to eq(project_event1.id)
+ end
+
+ context 'when multiple batches are needed' do
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 1)
+ stub_const("#{described_class}::INSERT_BATCH_SIZE", 1)
+ end
+
+ it 'inserts all records' do
+ expect(execute).to eq({ status: :processed, records_inserted: 4, reached_end_of_table: true })
+
+ events = ClickHouse::Client.select('SELECT * FROM events', :main)
+ expect(events.size).to eq(4)
+ end
+
+ context 'when new records are inserted while processing' do
+ it 'does not process new records created during the iteration' do
+ # Simulating the case when there is an insert during the iteration
+ call_count = 0
+ allow(strategy).to receive(:next_batch).and_wrap_original do |method|
+ call_count += 1
+ create(:event) if call_count == 3
+ method.call
+ end
+
+ expect(execute).to eq({ status: :processed, records_inserted: 4, reached_end_of_table: true })
+ end
+ end
+ end
+
+ context 'when time limit is reached' do
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 1)
+ end
+
+ it 'stops the processing' do
+ allow_next_instance_of(Analytics::CycleAnalytics::RuntimeLimiter) do |runtime_limiter|
+ allow(runtime_limiter).to receive(:over_time?).and_return(false, true)
+ end
+
+ expect(execute).to eq({ status: :processed, records_inserted: 2, reached_end_of_table: false })
+
+ last_processed_id = ClickHouse::SyncCursor.cursor_for(:events)
+ expect(last_processed_id).to eq(event_without_parent.id)
+ end
+ end
+
+ context 'when syncing from a certain point' do
+ before do
+ ClickHouse::SyncCursor.update_cursor_for(:events, project_event2.id)
+ end
+
+ it 'syncs records after the cursor' do
+ expect(execute).to eq({ status: :processed, records_inserted: 3, reached_end_of_table: true })
+
+ events = ClickHouse::Client.select('SELECT id FROM events ORDER BY id', :main)
+
+ expect(events).to eq([{ 'id' => event_without_parent.id }, { 'id' => group_event.id },
+ { 'id' => project_event1.id }])
+ end
+
+ context 'when there is nothing to sync' do
+ it 'does nothing' do
+ ClickHouse::SyncCursor.update_cursor_for(:events, project_event1.id)
+
+ expect(execute).to eq({ status: :processed, records_inserted: 0, reached_end_of_table: true })
+
+ events = ClickHouse::Client.select('SELECT id FROM events ORDER BY id', :main)
+ expect(events).to be_empty
+ end
+ end
+ end
+ end
+ end
+
+ context 'when clickhouse is not configured' do
+ before do
+ allow(ClickHouse::Client.configuration).to receive(:databases).and_return({})
+ end
+
+ it 'skips execution' do
+ expect(execute).to eq({ status: :disabled })
+ end
+ end
+
+ context 'when exclusive lease error happens' do
+ it 'skips execution' do
+ allow(ClickHouse::Client.configuration).to receive(:databases).and_return({ main: :some_db })
+
+ expect(strategy).to receive(:in_lock).and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
+ expect(execute).to eq({ status: :skipped })
+ end
+ end
+ end
+
+ describe '#projections' do
+ it 'raises a NotImplementedError' do
+ expect { strategy.send(:projections) }.to raise_error(NotImplementedError,
+ "Subclasses must implement `projections`")
+ end
+ end
+
+ describe '#csv_mapping' do
+ it 'raises a NotImplementedError' do
+ expect { strategy.send(:csv_mapping) }.to raise_error(NotImplementedError,
+ "Subclasses must implement `csv_mapping`")
+ end
+ end
+
+ describe '#insert_query' do
+ it 'raises a NotImplementedError' do
+ expect { strategy.send(:insert_query) }.to raise_error(NotImplementedError,
+ "Subclasses must implement `insert_query`")
+ end
+ end
+end
diff --git a/spec/services/click_house/sync_strategies/event_sync_strategy_spec.rb b/spec/services/click_house/sync_strategies/event_sync_strategy_spec.rb
new file mode 100644
index 00000000000..05fcf6ddeb3
--- /dev/null
+++ b/spec/services/click_house/sync_strategies/event_sync_strategy_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ClickHouse::SyncStrategies::EventSyncStrategy, feature_category: :value_stream_management do
+ let(:strategy) { described_class.new }
+
+ describe '#execute' do
+ subject(:execute) { strategy.execute }
+
+ context 'when syncing records', :click_house do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:project_event2) { create(:event, :closed, project: project, target: issue) }
+ let_it_be(:event_without_parent) { create(:event, :joined, project: nil, group: nil) }
+ let_it_be(:group_event) { create(:event, :created, group: group, project: nil) }
+ let_it_be(:project_event1) { create(:event, :created, project: project, target: issue) }
+ # looks invalid but we have some records like this on PRD
+
+ it 'correctly inserts all records' do
+ expect(execute).to eq({ status: :processed, records_inserted: 4, reached_end_of_table: true })
+
+ expected_records = [
+ hash_including('id' => project_event2.id, 'path' => "#{group.id}/#{project.project_namespace.id}/",
+ 'target_type' => 'Issue'),
+ hash_including('id' => event_without_parent.id, 'path' => '', 'target_type' => ''),
+ hash_including('id' => group_event.id, 'path' => "#{group.id}/", 'target_type' => ''),
+ hash_including('id' => project_event1.id, 'path' => "#{group.id}/#{project.project_namespace.id}/",
+ 'target_type' => 'Issue')
+ ]
+
+ events = ClickHouse::Client.select('SELECT * FROM events ORDER BY id', :main)
+
+ expect(events).to match(expected_records)
+
+ last_processed_id = ClickHouse::SyncCursor.cursor_for(:events)
+ expect(last_processed_id).to eq(project_event1.id)
+ end
+ end
+ end
+
+ describe '#projections' do
+ it 'returns correct projections' do
+ expect(strategy.send(:projections)).to match_array([
+ :id,
+ described_class::PATH_COLUMN,
+ :author_id,
+ :target_id,
+ :target_type,
+ 'action AS raw_action',
+ 'EXTRACT(epoch FROM created_at) AS casted_created_at',
+ 'EXTRACT(epoch FROM updated_at) AS casted_updated_at'
+ ])
+ end
+ end
+
+ describe '#csv_mapping' do
+ it 'returns correct csv mapping' do
+ expect(strategy.send(:csv_mapping)).to eq({
+ id: :id,
+ path: :path,
+ author_id: :author_id,
+ target_id: :target_id,
+ target_type: :target_type,
+ action: :raw_action,
+ created_at: :casted_created_at,
+ updated_at: :casted_updated_at
+ })
+ end
+ end
+
+ describe '#insert_query' do
+ let(:expected_query) do
+ <<~SQL.squish
+ INSERT INTO events (id, path, author_id,
+ target_id, target_type,
+ action, created_at, updated_at)
+ SETTINGS async_insert=1,
+ wait_for_async_insert=1 FORMAT CSV
+ SQL
+ end
+
+ it 'returns correct insert query' do
+ expect(strategy.send(:insert_query)).to eq(expected_query)
+ end
+ end
+
+ describe '#model_class' do
+ it 'returns the correct model class' do
+ expect(strategy.send(:model_class)).to eq(::Event)
+ end
+ end
+
+ describe '#enabled?' do
+ context 'when the clickhouse database is configured the feature flag is enabled' do
+ before do
+ allow(ClickHouse::Client.configuration).to receive(:databases).and_return({ main: :some_db })
+ stub_feature_flags(event_sync_worker_for_click_house: true)
+ end
+
+ it 'returns true' do
+ expect(strategy.send(:enabled?)).to be_truthy
+ end
+ end
+
+ context 'when the clickhouse database is not configured' do
+ before do
+ allow(ClickHouse::Client.configuration).to receive(:databases).and_return({})
+ end
+
+ it 'returns false' do
+ expect(strategy.send(:enabled?)).to be_falsey
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ allow(ClickHouse::Client.configuration).to receive(:databases).and_return({ main: :some_db })
+ stub_feature_flags(event_sync_worker_for_click_house: false)
+ end
+
+ it 'returns false' do
+ expect(strategy.send(:enabled?)).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb
index 3860543a85e..b23f5856575 100644
--- a/spec/services/members/update_service_spec.rb
+++ b/spec/services/members/update_service_spec.rb
@@ -263,7 +263,7 @@ RSpec.describe Members::UpdateService, feature_category: :groups_and_projects do
it 'emails the users that their group membership expiry has changed' do
members.each do |member|
- expect(notification_service).to receive(:updated_group_member_expiration).with(member)
+ expect(notification_service).to receive(:updated_member_expiration).with(member)
end
subject
diff --git a/spec/services/ml/create_model_service_spec.rb b/spec/services/ml/create_model_service_spec.rb
index 74c1dd5fec7..88e7c00d1f9 100644
--- a/spec/services/ml/create_model_service_spec.rb
+++ b/spec/services/ml/create_model_service_spec.rb
@@ -50,9 +50,10 @@ RSpec.describe ::Ml::CreateModelService, feature_category: :mlops do
let(:name) { existing_model.name }
let(:project) { existing_model.project }
- it 'raises an error', :aggregate_failures do
- expect { create_model }.to raise_error(ActiveRecord::RecordInvalid)
+ it 'returns a model with errors', :aggregate_failures do
+ expect(create_model).not_to be_persisted
expect(Gitlab::InternalEvents).not_to have_received(:track_event)
+ expect(create_model.errors.full_messages).to eq(["Name has already been taken"])
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 0012cc112c1..910e1d85a6b 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -3319,18 +3319,6 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
let(:notification_trigger) { group.add_guest(added_user) }
end
end
-
- describe '#updated_group_member_expiration' do
- let_it_be(:group_member) { create(:group_member) }
-
- it 'emails the user that their group membership expiry has changed' do
- expect_next_instance_of(NotificationService) do |notification|
- allow(notification).to receive(:updated_group_member_expiration).with(group_member)
- end
-
- group_member.update!(expires_at: 5.days.from_now)
- end
- end
end
describe 'ProjectMember', :deliver_mails_inline do
@@ -3509,6 +3497,42 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
end
end
+ describe '#updated_member_expiration' do
+ subject(:updated_member_expiration) { notification.updated_member_expiration(member) }
+
+ context 'for group member' do
+ let_it_be(:member) { create(:group_member) }
+
+ it 'triggers a notification about the expiration change' do
+ updated_member_expiration
+
+ expect_delivery_jobs_count(1)
+ expect_enqueud_email('Group', member.id, mail: 'member_expiration_date_updated_email')
+ end
+ end
+
+ context 'for project member' do
+ let_it_be(:member) { create(:project_member) }
+
+ it 'does not trigger a notification' do
+ updated_member_expiration
+
+ expect_delivery_jobs_count(0)
+ end
+ end
+ end
+
+ describe '#updated_member_access_level' do
+ let_it_be(:member) { create(:group_member) }
+
+ it 'triggers a notification about the access_level change' do
+ notification.updated_member_access_level(member)
+
+ expect_delivery_jobs_count(1)
+ expect_enqueud_email('Group', member.id, mail: 'member_access_granted_email')
+ end
+ end
+
context 'guest user in private project', :deliver_mails_inline do
let(:private_project) { create(:project, :private) }
let(:guest) { create(:user) }
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 56a28004233..0715e56c130 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -1691,7 +1691,7 @@
- './ee/spec/models/gitlab_subscription_spec.rb'
- './ee/spec/models/gitlab_subscriptions/upcoming_reconciliation_spec.rb'
- './ee/spec/models/group_deletion_schedule_spec.rb'
-- './ee/spec/models/group_member_spec.rb'
+- './ee/spec/models/ee/group_member_spec.rb'
- './ee/spec/models/group_merge_request_approval_setting_spec.rb'
- './ee/spec/models/groups/repository_storage_move_spec.rb'
- './ee/spec/models/group_wiki_repository_spec.rb'
@@ -1715,7 +1715,7 @@
- './ee/spec/models/label_note_spec.rb'
- './ee/spec/models/ldap_group_link_spec.rb'
- './ee/spec/models/license_spec.rb'
-- './ee/spec/models/member_spec.rb'
+- './ee/spec/models/ee/member_spec.rb'
- './ee/spec/models/merge_request/blocking_spec.rb'
- './ee/spec/models/merge_request_block_spec.rb'
- './ee/spec/models/merge_requests/compliance_violation_spec.rb'
@@ -1740,7 +1740,7 @@
- './ee/spec/models/project_feature_spec.rb'
- './ee/spec/models/project_import_data_spec.rb'
- './ee/spec/models/project_import_state_spec.rb'
-- './ee/spec/models/project_member_spec.rb'
+- './ee/spec/models/ee/project_member_spec.rb'
- './ee/spec/models/project_repository_state_spec.rb'
- './ee/spec/models/project_security_setting_spec.rb'
- './ee/spec/models/protected_branch/required_code_owners_section_spec.rb'
diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb
index 5c783b5cfa7..17dc927f3cc 100644
--- a/spec/support/shared_examples/models/members_notifications_shared_example.rb
+++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb
@@ -19,22 +19,6 @@ RSpec.shared_examples 'members notifications' do |entity_type|
end
end
- describe "#after_update" do
- let(:member) { create(:"#{entity_type}_member", :developer) }
-
- it "calls NotificationService.update_#{entity_type}_member" do
- expect(notification_service).to receive(:"update_#{entity_type}_member").with(member)
-
- member.update_attribute(:access_level, Member::MAINTAINER)
- end
-
- it "does not send an email when the access level has not changed" do
- expect(notification_service).not_to receive(:"update_#{entity_type}_member")
-
- member.touch
- end
- end
-
describe '#after_commit' do
context 'on creation of a member requesting access' do
let(:member) do
diff --git a/spec/workers/click_house/events_sync_worker_spec.rb b/spec/workers/click_house/events_sync_worker_spec.rb
index 9662f26115a..dc3dea24e37 100644
--- a/spec/workers/click_house/events_sync_worker_spec.rb
+++ b/spec/workers/click_house/events_sync_worker_spec.rb
@@ -11,176 +11,20 @@ RSpec.describe ClickHouse::EventsSyncWorker, feature_category: :value_stream_man
)
end
- it_behaves_like 'an idempotent worker' do
- context 'when the event_sync_worker_for_click_house feature flag is on', :click_house do
- before do
- stub_feature_flags(event_sync_worker_for_click_house: true)
+ context 'when worker is enqueued' do
+ it 'calls ::ClickHouse::SyncStrategies::EventSyncStrategy with correct args' do
+ expect_next_instance_of(::ClickHouse::SyncStrategies::EventSyncStrategy) do |instance|
+ expect(instance).to receive(:execute)
end
- context 'when there is nothing to sync' do
- it 'adds metadata for the worker' do
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
- { status: :processed, records_inserted: 0, reached_end_of_table: true })
-
- worker.perform
-
- events = ClickHouse::Client.select('SELECT * FROM events', :main)
- expect(events).to be_empty
- end
- end
-
- context 'when syncing records' do
- let_it_be(:group) { create(:group) }
- let_it_be(:project) { create(:project, group: group) }
- let_it_be(:issue) { create(:issue, project: project) }
- let_it_be(:project_event2) { create(:event, :closed, project: project, target: issue) }
- let_it_be(:event_without_parent) { create(:event, :joined, project: nil, group: nil) }
- let_it_be(:group_event) { create(:event, :created, group: group, project: nil) }
- let_it_be(:project_event1) { create(:event, :created, project: project, target: issue) }
- # looks invalid but we have some records like this on PRD
-
- it 'inserts all records' do
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
- { status: :processed, records_inserted: 4, reached_end_of_table: true })
-
- worker.perform
-
- expected_records = [
- hash_including('id' => project_event2.id, 'path' => "#{group.id}/#{project.project_namespace.id}/",
- 'target_type' => 'Issue'),
- hash_including('id' => event_without_parent.id, 'path' => '', 'target_type' => ''),
- hash_including('id' => group_event.id, 'path' => "#{group.id}/", 'target_type' => ''),
- hash_including('id' => project_event1.id, 'path' => "#{group.id}/#{project.project_namespace.id}/",
- 'target_type' => 'Issue')
- ]
-
- events = ClickHouse::Client.select('SELECT * FROM events ORDER BY id', :main)
-
- expect(events).to match(expected_records)
-
- last_processed_id = ClickHouse::SyncCursor.cursor_for(:events)
- expect(last_processed_id).to eq(project_event1.id)
- end
-
- context 'when multiple batches are needed' do
- before do
- stub_const("#{described_class}::BATCH_SIZE", 1)
- stub_const("#{described_class}::INSERT_BATCH_SIZE", 1)
- end
-
- it 'inserts all records' do
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
- { status: :processed, records_inserted: 4, reached_end_of_table: true })
-
- worker.perform
-
- events = ClickHouse::Client.select('SELECT * FROM events', :main)
- expect(events.size).to eq(4)
- end
-
- context 'when new records are inserted while processing' do
- it 'does not process new records created during the iteration' do
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
- { status: :processed, records_inserted: 4,
- reached_end_of_table: true })
-
- # Simulating the case when there is an insert during the iteration
- call_count = 0
- allow(worker).to receive(:next_batch).and_wrap_original do |method|
- call_count += 1
- create(:event) if call_count == 3
- method.call
- end
-
- worker.perform
- end
- end
- end
-
- context 'when time limit is reached' do
- before do
- stub_const("#{described_class}::BATCH_SIZE", 1)
- end
-
- it 'stops the processing' do
- allow_next_instance_of(Analytics::CycleAnalytics::RuntimeLimiter) do |runtime_limiter|
- allow(runtime_limiter).to receive(:over_time?).and_return(false, true)
- end
-
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
- { status: :processed, records_inserted: 2, reached_end_of_table: false })
-
- worker.perform
-
- last_processed_id = ClickHouse::SyncCursor.cursor_for(:events)
- expect(last_processed_id).to eq(event_without_parent.id)
- end
- end
-
- context 'when syncing from a certain point' do
- before do
- ClickHouse::SyncCursor.update_cursor_for(:events, project_event2.id)
- end
-
- it 'syncs records after the cursor' do
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
- { status: :processed, records_inserted: 3, reached_end_of_table: true })
-
- worker.perform
-
- events = ClickHouse::Client.select('SELECT id FROM events ORDER BY id', :main)
- expect(events).to eq([{ 'id' => event_without_parent.id }, { 'id' => group_event.id },
- { 'id' => project_event1.id }])
- end
-
- context 'when there is nothing to sync' do
- it 'does nothing' do
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
- { status: :processed, records_inserted: 0, reached_end_of_table: true })
-
- ClickHouse::SyncCursor.update_cursor_for(:events, project_event1.id)
- worker.perform
-
- events = ClickHouse::Client.select('SELECT id FROM events ORDER BY id', :main)
- expect(events).to be_empty
- end
- end
- end
- end
- end
-
- context 'when clickhouse is not configured' do
- before do
- allow(ClickHouse::Client).to receive(:database_configured?).and_return(false)
- end
-
- it 'skips execution' do
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :disabled })
-
- worker.perform
- end
- end
- end
-
- context 'when exclusive lease error happens' do
- it 'skips execution' do
- stub_feature_flags(event_sync_worker_for_click_house: true)
- allow(ClickHouse::Client).to receive(:database_configured?).with(:main).and_return(true)
-
- expect(worker).to receive(:in_lock).and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :skipped })
-
worker.perform
end
- end
- context 'when the event_sync_worker_for_click_house feature flag is off' do
- before do
- stub_feature_flags(event_sync_worker_for_click_house: false)
- end
-
- it 'skips execution' do
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :disabled })
+ it 'correctly logs the metadata on done' do
+ expect_next_instance_of(::ClickHouse::SyncStrategies::EventSyncStrategy) do |instance|
+ expect(instance).to receive(:execute).and_return({ status: :ok })
+ end
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :ok })
worker.perform
end