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>2022-07-13 15:09:54 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-13 15:09:54 +0300
commitaffec3ced2d85697d9a21d83e811285b030705f2 (patch)
treedb315bff6a2e4a2f3db1509eb573c082b046a320 /spec
parent152d3b652dd7daad10b3056f47e579acf601546d (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-rw-r--r--spec/lib/gitlab/background_migration/batched_migration_job_spec.rb64
-rw-r--r--spec/lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy_spec.rb67
-rw-r--r--spec/models/operations/feature_flags_client_spec.rb70
-rw-r--r--spec/requests/api/feature_flags_user_lists_spec.rb7
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_spec.rb10
-rw-r--r--spec/requests/api/graphql/mutations/work_items/update_spec.rb68
-rw-r--r--spec/requests/api/unleash_spec.rb42
-rw-r--r--spec/serializers/diffs_entity_spec.rb1
-rw-r--r--spec/serializers/diffs_metadata_entity_spec.rb1
-rw-r--r--spec/serializers/paginated_diff_entity_spec.rb1
-rw-r--r--spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb6
-rw-r--r--spec/services/feature_flags/create_service_spec.rb4
-rw-r--r--spec/services/feature_flags/destroy_service_spec.rb4
-rw-r--r--spec/services/feature_flags/update_service_spec.rb4
-rw-r--r--spec/services/work_items/create_service_spec.rb15
-rw-r--r--spec/services/work_items/parent_links/destroy_service_spec.rb41
-rw-r--r--spec/services/work_items/update_service_spec.rb23
-rw-r--r--spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb42
-rw-r--r--spec/support/shared_examples/services/feature_flags/client_shared_examples.rb19
20 files changed, 463 insertions, 36 deletions
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index b6efced698d..93efce6b58b 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -470,7 +470,7 @@ RSpec.describe DiffHelper do
end
describe '#conflicts' do
- let(:merge_request) { instance_double(MergeRequest) }
+ let(:merge_request) { instance_double(MergeRequest, cannot_be_merged?: true) }
let(:merge_ref_head_diff) { true }
let(:can_be_resolved_in_ui?) { true }
let(:allow_tree_conflicts) { false }
@@ -504,6 +504,14 @@ RSpec.describe DiffHelper do
end
end
+ context 'when merge request can be merged' do
+ let(:merge_request) { instance_double(MergeRequest, cannot_be_merged?: false) }
+
+ it 'returns nil' do
+ expect(helper.conflicts).to be_nil
+ end
+ end
+
context 'when conflicts cannot be resolved in UI' do
let(:can_be_resolved_in_ui?) { false }
diff --git a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
index f8b3a8681f0..98866bb765f 100644
--- a/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
+++ b/spec/lib/gitlab/background_migration/batched_migration_job_spec.rb
@@ -92,5 +92,69 @@ RSpec.describe Gitlab::BackgroundMigration::BatchedMigrationJob do
end
end
end
+
+ context 'when the subclass uses distinct each batch' do
+ let(:job_instance) do
+ job_class.new(start_id: 1,
+ end_id: 100,
+ batch_table: '_test_table',
+ batch_column: 'from_column',
+ sub_batch_size: 2,
+ pause_ms: 10,
+ connection: connection)
+ end
+
+ let(:job_class) do
+ Class.new(described_class) do
+ def perform(*job_arguments)
+ distinct_each_batch(operation_name: :insert) do |sub_batch|
+ sub_batch.pluck(:from_column).each do |value|
+ connection.execute("INSERT INTO _test_insert_table VALUES (#{value})")
+ end
+
+ sub_batch.size
+ end
+ end
+ end
+ end
+
+ let(:test_table) { table(:_test_table) }
+ let(:test_insert_table) { table(:_test_insert_table) }
+
+ before do
+ allow(job_instance).to receive(:sleep)
+
+ connection.create_table :_test_table do |t|
+ t.timestamps_with_timezone null: false
+ t.integer :from_column, null: false
+ end
+
+ connection.create_table :_test_insert_table, id: false do |t|
+ t.integer :to_column
+ t.index :to_column, unique: true
+ end
+
+ test_table.create!(id: 1, from_column: 5)
+ test_table.create!(id: 2, from_column: 10)
+ test_table.create!(id: 3, from_column: 10)
+ test_table.create!(id: 4, from_column: 5)
+ test_table.create!(id: 5, from_column: 15)
+ end
+
+ after do
+ connection.drop_table(:_test_table)
+ connection.drop_table(:_test_insert_table)
+ end
+
+ it 'calls the operation for each distinct batch' do
+ expect { perform_job }.to change { test_insert_table.pluck(:to_column) }.from([]).to([5, 10, 15])
+ end
+
+ it 'stores the affected rows' do
+ perform_job
+
+ expect(job_instance.batch_metrics.affected_rows[:insert]).to contain_exactly(2, 1)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy_spec.rb
new file mode 100644
index 00000000000..1a00fd7c8b3
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::LooseIndexScanBatchingStrategy, '#next_batch' do
+ let(:batching_strategy) { described_class.new(connection: ActiveRecord::Base.connection) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+
+ let!(:namespace1) { namespaces.create!(name: 'ns1', path: 'ns1') }
+ let!(:namespace2) { namespaces.create!(name: 'ns2', path: 'ns2') }
+ let!(:namespace3) { namespaces.create!(name: 'ns3', path: 'ns3') }
+ let!(:namespace4) { namespaces.create!(name: 'ns4', path: 'ns4') }
+ let!(:namespace5) { namespaces.create!(name: 'ns5', path: 'ns5') }
+ let!(:project1) { projects.create!(name: 'p1', namespace_id: namespace1.id, project_namespace_id: namespace1.id) }
+ let!(:project2) { projects.create!(name: 'p2', namespace_id: namespace2.id, project_namespace_id: namespace2.id) }
+ let!(:project3) { projects.create!(name: 'p3', namespace_id: namespace3.id, project_namespace_id: namespace3.id) }
+ let!(:project4) { projects.create!(name: 'p4', namespace_id: namespace4.id, project_namespace_id: namespace4.id) }
+ let!(:project5) { projects.create!(name: 'p5', namespace_id: namespace5.id, project_namespace_id: namespace5.id) }
+
+ let!(:issue1) { issues.create!(title: 'title', description: 'description', project_id: project2.id) }
+ let!(:issue2) { issues.create!(title: 'title', description: 'description', project_id: project1.id) }
+ let!(:issue3) { issues.create!(title: 'title', description: 'description', project_id: project2.id) }
+ let!(:issue4) { issues.create!(title: 'title', description: 'description', project_id: project3.id) }
+ let!(:issue5) { issues.create!(title: 'title', description: 'description', project_id: project2.id) }
+ let!(:issue6) { issues.create!(title: 'title', description: 'description', project_id: project4.id) }
+ let!(:issue7) { issues.create!(title: 'title', description: 'description', project_id: project5.id) }
+
+ it { expect(described_class).to be < Gitlab::BackgroundMigration::BatchingStrategies::BaseStrategy }
+
+ context 'when starting on the first batch' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy
+ .next_batch(:issues, :project_id, batch_min_value: project1.id, batch_size: 2, job_arguments: [])
+
+ expect(batch_bounds).to eq([project1.id, project2.id])
+ end
+ end
+
+ context 'when additional batches remain' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy
+ .next_batch(:issues, :project_id, batch_min_value: project2.id, batch_size: 3, job_arguments: [])
+
+ expect(batch_bounds).to eq([project2.id, project4.id])
+ end
+ end
+
+ context 'when on the final batch' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy
+ .next_batch(:issues, :project_id, batch_min_value: project4.id, batch_size: 3, job_arguments: [])
+
+ expect(batch_bounds).to eq([project4.id, project5.id])
+ end
+ end
+
+ context 'when no additional batches remain' do
+ it 'returns nil' do
+ batch_bounds = batching_strategy
+ .next_batch(:issues, :project_id, batch_min_value: project5.id + 1, batch_size: 1, job_arguments: [])
+
+ expect(batch_bounds).to be_nil
+ end
+ end
+end
diff --git a/spec/models/operations/feature_flags_client_spec.rb b/spec/models/operations/feature_flags_client_spec.rb
index 05988d676f3..2ed3222c65c 100644
--- a/spec/models/operations/feature_flags_client_spec.rb
+++ b/spec/models/operations/feature_flags_client_spec.rb
@@ -3,7 +3,15 @@
require 'spec_helper'
RSpec.describe Operations::FeatureFlagsClient do
- subject { create(:operations_feature_flags_client) }
+ let_it_be(:project) { create(:project) }
+
+ let!(:client) { create(:operations_feature_flags_client, project: project) }
+
+ subject { client }
+
+ before do
+ client.unleash_app_name = 'production'
+ end
describe 'associations' do
it { is_expected.to belong_to(:project) }
@@ -18,4 +26,64 @@ RSpec.describe Operations::FeatureFlagsClient do
expect(subject.token).not_to be_empty
end
end
+
+ describe '.update_last_feature_flag_updated_at!' do
+ subject { described_class.update_last_feature_flag_updated_at!(project) }
+
+ it 'updates the last_feature_flag_updated_at of the project client' do
+ freeze_time do
+ expect { subject }.to change { client.reload.last_feature_flag_updated_at }.from(nil).to(Time.current)
+ end
+ end
+ end
+
+ describe '#unleash_api_version' do
+ subject { client.unleash_api_version }
+
+ it { is_expected.to eq(described_class::DEFAULT_UNLEASH_API_VERSION) }
+ end
+
+ describe '#unleash_api_features' do
+ subject { client.unleash_api_features }
+
+ it 'fetches' do
+ expect(Operations::FeatureFlag).to receive(:for_unleash_client).with(project, 'production').once
+
+ subject
+ end
+
+ context 'when unleash app name is not set' do
+ before do
+ client.unleash_app_name = nil
+ end
+
+ it 'does not fetch' do
+ expect(Operations::FeatureFlag).not_to receive(:for_unleash_client)
+
+ subject
+ end
+ end
+ end
+
+ describe '#unleash_api_cache_key' do
+ subject { client.unleash_api_cache_key }
+
+ it 'constructs the cache key' do
+ is_expected.to eq("api_version:#{client.unleash_api_version}"\
+ ":app_name:#{client.unleash_app_name}"\
+ ":updated_at:#{client.last_feature_flag_updated_at.to_i}")
+ end
+
+ context 'when unleash app name is not set' do
+ before do
+ client.unleash_app_name = nil
+ end
+
+ it 'constructs the cache key without unleash app name' do
+ is_expected.to eq("api_version:#{client.unleash_api_version}"\
+ ":app_name:"\
+ ":updated_at:#{client.last_feature_flag_updated_at.to_i}")
+ end
+ end
+ end
end
diff --git a/spec/requests/api/feature_flags_user_lists_spec.rb b/spec/requests/api/feature_flags_user_lists_spec.rb
index e2a3f92df10..bfc57042ff4 100644
--- a/spec/requests/api/feature_flags_user_lists_spec.rb
+++ b/spec/requests/api/feature_flags_user_lists_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe API::FeatureFlagsUserLists do
let_it_be(:project, refind: true) { create(:project) }
+ let_it_be(:client, refind: true) { create(:operations_feature_flags_client, project: project) }
let_it_be(:developer) { create(:user) }
let_it_be(:reporter) { create(:user) }
@@ -215,6 +216,7 @@ RSpec.describe API::FeatureFlagsUserLists do
}
expect(response).to have_gitlab_http_status(:forbidden)
+ expect(client.reload.last_feature_flag_updated_at).to be_nil
end
it 'creates the flag' do
@@ -231,6 +233,7 @@ RSpec.describe API::FeatureFlagsUserLists do
})
expect(project.operations_feature_flags_user_lists.count).to eq(1)
expect(project.operations_feature_flags_user_lists.last.name).to eq('mylist')
+ expect(client.reload.last_feature_flag_updated_at).not_to be_nil
end
it 'requires name' do
@@ -298,6 +301,7 @@ RSpec.describe API::FeatureFlagsUserLists do
}
expect(response).to have_gitlab_http_status(:forbidden)
+ expect(client.reload.last_feature_flag_updated_at).to be_nil
end
it 'updates the list' do
@@ -313,6 +317,7 @@ RSpec.describe API::FeatureFlagsUserLists do
'user_xids' => '456,789'
})
expect(list.reload.name).to eq('mylist')
+ expect(client.reload.last_feature_flag_updated_at).not_to be_nil
end
it 'preserves attributes not listed in the request' do
@@ -377,6 +382,7 @@ RSpec.describe API::FeatureFlagsUserLists do
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response).to eq({ 'message' => '404 Not found' })
+ expect(client.reload.last_feature_flag_updated_at).to be_nil
end
it 'deletes the list' do
@@ -387,6 +393,7 @@ RSpec.describe API::FeatureFlagsUserLists do
expect(response).to have_gitlab_http_status(:no_content)
expect(response.body).to be_blank
expect(project.operations_feature_flags_user_lists.count).to eq(0)
+ expect(client.reload.last_feature_flag_updated_at).not_to be_nil
end
it 'does not delete the list if it is associated with a strategy' do
diff --git a/spec/requests/api/graphql/mutations/work_items/create_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
index ee818d9e37c..c33dda67a25 100644
--- a/spec/requests/api/graphql/mutations/work_items/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
@@ -124,6 +124,16 @@ RSpec.describe 'Create a work item' do
expect(mutation_response['workItem']).to be_nil
end
end
+
+ context 'when parent work item is not found' do
+ let_it_be(:parent) { build_stubbed(:work_item, id: non_existing_record_id)}
+
+ it 'returns a top level error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors.first['message']).to include('No object found for `parentId')
+ end
+ end
end
end
diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
index 58549c6e3a0..e2c4386772c 100644
--- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
@@ -166,13 +166,13 @@ RSpec.describe 'Update a work item' do
context 'when updating parent' do
let_it_be(:work_item) { create(:work_item, :task, project: project) }
+ let_it_be(:valid_parent) { create(:work_item, project: project) }
+ let_it_be(:invalid_parent) { create(:work_item, :task, project: project) }
context 'when parent work item type is invalid' do
- let_it_be(:parent_task) { create(:work_item, :task, project: project) }
-
let(:error) { "#{work_item.to_reference} cannot be added: Only Issue can be parent of Task." }
let(:input) do
- { 'hierarchyWidget' => { 'parentId' => parent_task.to_global_id.to_s }, 'title' => 'new title' }
+ { 'hierarchyWidget' => { 'parentId' => invalid_parent.to_global_id.to_s }, 'title' => 'new title' }
end
it 'returns response with errors' do
@@ -187,21 +187,19 @@ RSpec.describe 'Update a work item' do
end
context 'when parent work item has a valid type' do
- let_it_be(:parent) { create(:work_item, project: project) }
-
- let(:input) { { 'hierarchyWidget' => { 'parentId' => parent.to_global_id.to_s } } }
+ let(:input) { { 'hierarchyWidget' => { 'parentId' => valid_parent.to_global_id.to_s } } }
it 'sets the parent for the work item' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
work_item.reload
- end.to change(work_item, :work_item_parent).from(nil).to(parent)
+ end.to change(work_item, :work_item_parent).from(nil).to(valid_parent)
expect(response).to have_gitlab_http_status(:success)
expect(widgets_response).to include(
{
'children' => { 'edges' => [] },
- 'parent' => { 'id' => parent.to_global_id.to_s },
+ 'parent' => { 'id' => valid_parent.to_global_id.to_s },
'type' => 'HIERARCHY'
}
)
@@ -218,10 +216,62 @@ RSpec.describe 'Update a work item' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
work_item.reload
- end.to change(work_item, :work_item_parent).from(existing_parent).to(parent)
+ end.to change(work_item, :work_item_parent).from(existing_parent).to(valid_parent)
+ end
+ end
+ end
+
+ context 'when parentId is null' do
+ let(:input) { { 'hierarchyWidget' => { 'parentId' => nil } } }
+
+ context 'when parent is present' do
+ before do
+ work_item.update!(work_item_parent: valid_parent)
+ end
+
+ it 'removes parent and returns success message' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to change(work_item, :work_item_parent).from(valid_parent).to(nil)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(widgets_response)
+ .to include(
+ {
+ 'children' => { 'edges' => [] },
+ 'parent' => nil,
+ 'type' => 'HIERARCHY'
+ }
+ )
+ end
+ end
+
+ context 'when parent is not present' do
+ before do
+ work_item.update!(work_item_parent: nil)
+ end
+
+ it 'does not change work item and returns success message' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.not_to change(work_item, :work_item_parent)
+
+ expect(response).to have_gitlab_http_status(:success)
end
end
end
+
+ context 'when parent work item is not found' do
+ let(:input) { { 'hierarchyWidget' => { 'parentId' => "gid://gitlab/WorkItem/#{non_existing_record_id}" } } }
+
+ it 'returns a top level error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors.first['message']).to include('No object found for `parentId')
+ end
+ end
end
context 'when updating children' do
diff --git a/spec/requests/api/unleash_spec.rb b/spec/requests/api/unleash_spec.rb
index 6cb801538c6..7bdb89fb286 100644
--- a/spec/requests/api/unleash_spec.rb
+++ b/spec/requests/api/unleash_spec.rb
@@ -168,7 +168,7 @@ RSpec.describe API::Unleash do
end
%w(/feature_flags/unleash/:project_id/features /feature_flags/unleash/:project_id/client/features).each do |features_endpoint|
- describe "GET #{features_endpoint}" do
+ describe "GET #{features_endpoint}", :use_clean_rails_redis_caching do
let(:features_url) { features_endpoint.sub(':project_id', project_id.to_s) }
let(:client) { create(:operations_feature_flags_client, project: project) }
@@ -176,6 +176,46 @@ RSpec.describe API::Unleash do
it_behaves_like 'authenticated request'
+ context 'when a client fetches feature flags several times' do
+ let(:headers) { { 'UNLEASH-INSTANCEID' => client.token, 'UNLEASH-APPNAME' => 'production' } }
+
+ before do
+ create_list(:operations_feature_flag, 3, project: project)
+ end
+
+ it 'serializes feature flags for the first time and read cached data from the second time' do
+ expect(API::Entities::Unleash::ClientFeatureFlags)
+ .to receive(:represent).with(instance_of(Operations::FeatureFlagsClient), any_args)
+ .once
+
+ 5.times { get api(features_url), params: params, headers: headers }
+ end
+
+ it 'increments the cache key when feature flags are modified' do
+ expect(API::Entities::Unleash::ClientFeatureFlags)
+ .to receive(:represent).with(instance_of(Operations::FeatureFlagsClient), any_args)
+ .twice
+
+ 2.times { get api(features_url), params: params, headers: headers }
+
+ ::FeatureFlags::CreateService.new(project, project.owner, name: 'feature_flag').execute
+
+ 3.times { get api(features_url), params: params, headers: headers }
+ end
+
+ context 'when cache_unleash_client_api is disabled' do
+ before do
+ stub_feature_flags(cache_unleash_client_api: false)
+ end
+
+ it 'serializes feature flags every time' do
+ expect(::API::Entities::UnleashFeature).to receive(:represent).exactly(5).times
+
+ 5.times { get api(features_url), params: params, headers: headers }
+ end
+ end
+ end
+
context 'with version 2 feature flags' do
it 'does not return a flag without any strategies' do
create(:operations_feature_flag, project: project,
diff --git a/spec/serializers/diffs_entity_spec.rb b/spec/serializers/diffs_entity_spec.rb
index aef7d3732f8..72777bde30c 100644
--- a/spec/serializers/diffs_entity_spec.rb
+++ b/spec/serializers/diffs_entity_spec.rb
@@ -100,6 +100,7 @@ RSpec.describe DiffsEntity do
let(:options) { super().merge(merge_ref_head_diff: merge_ref_head_diff) }
before do
+ allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
end
diff --git a/spec/serializers/diffs_metadata_entity_spec.rb b/spec/serializers/diffs_metadata_entity_spec.rb
index 3311b434ce5..0e3d808aaac 100644
--- a/spec/serializers/diffs_metadata_entity_spec.rb
+++ b/spec/serializers/diffs_metadata_entity_spec.rb
@@ -65,6 +65,7 @@ RSpec.describe DiffsMetadataEntity do
let(:conflicts) { double(conflicts: double(files: [conflict_file]), can_be_resolved_in_ui?: false) }
before do
+ allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
end
diff --git a/spec/serializers/paginated_diff_entity_spec.rb b/spec/serializers/paginated_diff_entity_spec.rb
index db8bf92cbf5..9d4456c11d6 100644
--- a/spec/serializers/paginated_diff_entity_spec.rb
+++ b/spec/serializers/paginated_diff_entity_spec.rb
@@ -43,6 +43,7 @@ RSpec.describe PaginatedDiffEntity do
let(:options) { super().merge(merge_ref_head_diff: merge_ref_head_diff) }
before do
+ allow(merge_request).to receive(:cannot_be_merged?).and_return(true)
allow(MergeRequests::Conflicts::ListService).to receive(:new).and_return(conflicts)
end
diff --git a/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb b/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb
index 8a225494d5a..fd9a89fe8e2 100644
--- a/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb
+++ b/spec/services/ci/runners/reconcile_existing_runner_versions_service_spec.rb
@@ -32,6 +32,12 @@ RSpec.describe ::Ci::Runners::ReconcileExistingRunnerVersionsService, '#execute'
end
it 'creates and updates expected ci_runner_versions entries', :aggregate_failures do
+ expect(Ci::RunnerVersion).to receive(:insert_all)
+ .ordered
+ .with([{ version: '14.0.2' }], anything)
+ .once
+ .and_call_original
+
result = nil
expect { result = execute }
.to change { runner_version_14_0_0.reload.status }.from('not_available').to('recommended')
diff --git a/spec/services/feature_flags/create_service_spec.rb b/spec/services/feature_flags/create_service_spec.rb
index e37d41562f9..1c9bde70af3 100644
--- a/spec/services/feature_flags/create_service_spec.rb
+++ b/spec/services/feature_flags/create_service_spec.rb
@@ -41,6 +41,8 @@ RSpec.describe FeatureFlags::CreateService do
subject
end
+
+ it_behaves_like 'does not update feature flag client'
end
context 'when feature flag is saved correctly' do
@@ -62,6 +64,8 @@ RSpec.describe FeatureFlags::CreateService do
expect { subject }.to change { Operations::FeatureFlag.count }.by(1)
end
+ it_behaves_like 'update feature flag client'
+
context 'when Jira Connect subscription does not exist' do
it 'does not sync the feature flag to Jira' do
expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async)
diff --git a/spec/services/feature_flags/destroy_service_spec.rb b/spec/services/feature_flags/destroy_service_spec.rb
index d3796ef6b4d..740923db9b6 100644
--- a/spec/services/feature_flags/destroy_service_spec.rb
+++ b/spec/services/feature_flags/destroy_service_spec.rb
@@ -36,6 +36,8 @@ RSpec.describe FeatureFlags::DestroyService do
expect(audit_event_message).to eq("Deleted feature flag #{feature_flag.name}.")
end
+ it_behaves_like 'update feature flag client'
+
context 'when user is reporter' do
let(:user) { reporter }
@@ -57,6 +59,8 @@ RSpec.describe FeatureFlags::DestroyService do
it 'does not create audit log' do
expect { subject }.not_to change { AuditEvent.count }
end
+
+ it_behaves_like 'does not update feature flag client'
end
end
end
diff --git a/spec/services/feature_flags/update_service_spec.rb b/spec/services/feature_flags/update_service_spec.rb
index f5e94c4af0f..8f985d34961 100644
--- a/spec/services/feature_flags/update_service_spec.rb
+++ b/spec/services/feature_flags/update_service_spec.rb
@@ -58,6 +58,8 @@ RSpec.describe FeatureFlags::UpdateService do
)
end
+ it_behaves_like 'update feature flag client'
+
context 'with invalid params' do
let(:params) { { name: nil } }
@@ -79,6 +81,8 @@ RSpec.describe FeatureFlags::UpdateService do
subject
end
+
+ it_behaves_like 'does not update feature flag client'
end
context 'when user is reporter' do
diff --git a/spec/services/work_items/create_service_spec.rb b/spec/services/work_items/create_service_spec.rb
index 2cf5c60abd9..0d3c5391a4f 100644
--- a/spec/services/work_items/create_service_spec.rb
+++ b/spec/services/work_items/create_service_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe WorkItems::CreateService do
include AfterNextHelpers
let_it_be_with_reload(:project) { create(:project) }
+ let_it_be(:parent) { create(:work_item, project: project) }
let_it_be(:guest) { create(:user) }
let_it_be(:user_with_no_access) { create(:user) }
@@ -93,7 +94,7 @@ RSpec.describe WorkItems::CreateService do
it_behaves_like 'work item widgetable service' do
let(:widget_params) do
{
- hierarchy_widget: { parent_id: 1 }
+ hierarchy_widget: { parent: parent }
}
end
@@ -111,16 +112,18 @@ RSpec.describe WorkItems::CreateService do
let(:supported_widgets) do
[
- { klass: WorkItems::Widgets::HierarchyService::CreateService, callback: :after_create_in_transaction, params: { parent_id: 1 } }
+ {
+ klass: WorkItems::Widgets::HierarchyService::CreateService,
+ callback: :after_create_in_transaction,
+ params: { parent: parent }
+ }
]
end
end
describe 'hierarchy widget' do
context 'when parent is valid work item' do
- let_it_be(:parent) { create(:work_item, project: project) }
-
- let(:widget_params) { { hierarchy_widget: { parent_id: parent.id } } }
+ let(:widget_params) { { hierarchy_widget: { parent: parent } } }
let(:opts) do
{
@@ -149,7 +152,7 @@ RSpec.describe WorkItems::CreateService do
end
end
- context 'when hiearchy feature flag is disabled' do
+ context 'when hierarchy feature flag is disabled' do
before do
stub_feature_flags(work_items_hierarchy: false)
end
diff --git a/spec/services/work_items/parent_links/destroy_service_spec.rb b/spec/services/work_items/parent_links/destroy_service_spec.rb
new file mode 100644
index 00000000000..4c155909ac4
--- /dev/null
+++ b/spec/services/work_items/parent_links/destroy_service_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::ParentLinks::DestroyService do
+ describe '#execute' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:work_item) { create(:work_item, project: project) }
+ let_it_be(:task) { create(:work_item, :task, project: project) }
+ let_it_be(:parent_link) { create(:parent_link, work_item: task, work_item_parent: work_item)}
+
+ let(:parent_link_class) { WorkItems::ParentLink }
+
+ subject { described_class.new(parent_link, user).execute }
+
+ context 'when user has permissions to update work items' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'removes relation' do
+ expect { subject }.to change(parent_link_class, :count).by(-1)
+ end
+
+ it 'returns success message' do
+ is_expected.to eq(message: 'Relation was removed', status: :success)
+ end
+ end
+
+ context 'when user has insufficient permissions' do
+ it 'does not remove relation' do
+ expect { subject }.not_to change(parent_link_class, :count).from(1)
+ end
+
+ it 'returns error message' do
+ is_expected.to eq(message: 'No Work Item Link found', status: :error, http_status: 404)
+ end
+ end
+ end
+end
diff --git a/spec/services/work_items/update_service_spec.rb b/spec/services/work_items/update_service_spec.rb
index 247d10fe245..25b8caba0e8 100644
--- a/spec/services/work_items/update_service_spec.rb
+++ b/spec/services/work_items/update_service_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe WorkItems::UpdateService do
let_it_be(:developer) { create(:user) }
let_it_be(:project) { create(:project).tap { |proj| proj.add_developer(developer) } }
+ let_it_be(:parent) { create(:work_item, project: project) }
let_it_be_with_reload(:work_item) { create(:work_item, project: project, assignees: [developer]) }
let(:spam_params) { double }
@@ -80,7 +81,7 @@ RSpec.describe WorkItems::UpdateService do
it_behaves_like 'work item widgetable service' do
let(:widget_params) do
{
- hierarchy_widget: { parent_id: 1 },
+ hierarchy_widget: { parent: parent },
description_widget: { description: 'foo' },
weight_widget: { weight: 1 }
}
@@ -102,7 +103,7 @@ RSpec.describe WorkItems::UpdateService do
[
{ klass: WorkItems::Widgets::DescriptionService::UpdateService, callback: :update, params: { description: 'foo' } },
{ klass: WorkItems::Widgets::WeightService::UpdateService, callback: :update, params: { weight: 1 } },
- { klass: WorkItems::Widgets::HierarchyService::UpdateService, callback: :before_update_in_transaction, params: { parent_id: 1 } }
+ { klass: WorkItems::Widgets::HierarchyService::UpdateService, callback: :before_update_in_transaction, params: { parent: parent } }
]
end
end
@@ -144,6 +145,7 @@ RSpec.describe WorkItems::UpdateService do
end
context 'for the hierarchy widget' do
+ let(:opts) { { title: 'changed' } }
let_it_be(:child_work_item) { create(:work_item, :task, project: project) }
let(:widget_params) { { hierarchy_widget: { children_ids: [child_work_item.id] } } }
@@ -156,6 +158,23 @@ RSpec.describe WorkItems::UpdateService do
expect(work_item.work_item_children).to include(child_work_item)
end
+
+ context 'when child type is invalid' do
+ let_it_be(:child_work_item) { create(:work_item, project: project) }
+
+ it 'returns error status' do
+ expect(subject[:status]).to be(:error)
+ expect(subject[:message])
+ .to match("#{child_work_item.to_reference} cannot be added: Only Task can be assigned as a child in hierarchy.")
+ end
+
+ it 'does not update work item attributes' do
+ expect do
+ update_work_item
+ work_item.reload
+ end.to not_change(WorkItems::ParentLink, :count).and(not_change(work_item, :title))
+ end
+ end
end
end
end
diff --git a/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb b/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb
index 4765f185a56..528e20f95ad 100644
--- a/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb
+++ b/spec/services/work_items/widgets/hierarchy_service/update_service_spec.rb
@@ -21,8 +21,8 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService do
describe '#update' do
subject { described_class.new(widget: widget, current_user: user).before_update_in_transaction(params: params) }
- context 'when parent_id and children_ids params are present' do
- let(:params) { { parent_id: parent_work_item.id, children_ids: [child_work_item.id] } }
+ context 'when parent and children_ids params are present' do
+ let(:params) { { parent: parent_work_item, children_ids: [child_work_item.id] } }
it_behaves_like 'raises a WidgetError' do
let(:message) { 'A Work Item can be a parent or a child, but not both.' }
@@ -95,7 +95,7 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService do
context 'when updating parent' do
let_it_be(:work_item) { create(:work_item, :task, project: project) }
- let(:params) {{ parent_id: parent_work_item.id } }
+ let(:params) {{ parent: parent_work_item } }
context 'when work_items_hierarchy feature flag is disabled' do
before do
@@ -107,15 +107,6 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService do
end
end
- context 'when parent_id does not match an existing work item' do
- let(:invalid_id) { non_existing_record_iid }
- let(:params) {{ parent_id: invalid_id } }
-
- it_behaves_like 'raises a WidgetError' do
- let(:message) { "No Work Item found with ID: #{invalid_id}." }
- end
- end
-
context 'when user has insufficient permissions to link work items' do
it_behaves_like 'raises a WidgetError' do
let(:message) { not_found_error }
@@ -127,16 +118,35 @@ RSpec.describe WorkItems::Widgets::HierarchyService::UpdateService do
project.add_developer(user)
end
- it 'correctly sets work item parent' do
- subject
-
+ it 'correctly sets new parent' do
+ expect(subject[:status]).to eq(:success)
expect(work_item.work_item_parent).to eq(parent_work_item)
end
+ context 'when parent is nil' do
+ let(:params) { { parent: nil } }
+
+ it 'removes the work item parent if present' do
+ work_item.update!(work_item_parent: parent_work_item)
+
+ expect do
+ subject
+ work_item.reload
+ end.to change(work_item, :work_item_parent).from(parent_work_item).to(nil)
+ end
+
+ it 'returns success status if parent not present', :aggregate_failure do
+ work_item.update!(work_item_parent: nil)
+
+ expect(subject[:status]).to eq(:success)
+ expect(work_item.reload.work_item_parent).to be_nil
+ end
+ end
+
context 'when type is invalid' do
let_it_be(:parent_task) { create(:work_item, :task, project: project)}
- let(:params) {{ parent_id: parent_task.id } }
+ let(:params) {{ parent: parent_task } }
it_behaves_like 'raises a WidgetError' do
let(:message) { "#{work_item.to_reference} cannot be added: Only Issue can be parent of Task." }
diff --git a/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb b/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb
new file mode 100644
index 00000000000..a62cffc0e1b
--- /dev/null
+++ b/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+shared_examples_for 'update feature flag client' do
+ let!(:client) { create(:operations_feature_flags_client, project: project) }
+
+ it 'updates last feature flag updated at' do
+ freeze_time do
+ expect { subject }.to change { client.reload.last_feature_flag_updated_at }.from(nil).to(Time.current)
+ end
+ end
+end
+
+shared_examples_for 'does not update feature flag client' do
+ let!(:client) { create(:operations_feature_flags_client, project: project) }
+
+ it 'does not update last feature flag updated at' do
+ expect { subject }.not_to change { client.reload.last_feature_flag_updated_at }
+ end
+end