diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-06 12:08:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-06 12:08:54 +0300 |
commit | 4903f95b04db58edc1931ec917d2313c916a06b2 (patch) | |
tree | 722e74b8e49f12f8f7aebffc2fa58c9ccc126fb1 /spec | |
parent | 6367f9bd9cca41c932997c94a338556ded4ced8f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/features/profiles/user_deletes_saved_reply_spec.rb | 27 | ||||
-rw-r--r-- | spec/features/projects/work_items/work_item_children_spec.rb (renamed from spec/features/work_items/work_item_children_spec.rb) | 0 | ||||
-rw-r--r-- | spec/features/projects/work_items/work_item_spec.rb (renamed from spec/features/work_items/work_item_spec.rb) | 0 | ||||
-rw-r--r-- | spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap | 31 | ||||
-rw-r--r-- | spec/frontend/saved_replies/components/list_item_spec.js | 32 | ||||
-rw-r--r-- | spec/lib/gitlab/database/async_constraints/migration_helpers_spec.rb | 104 | ||||
-rw-r--r-- | spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb | 10 | ||||
-rw-r--r-- | spec/migrations/20230221093533_add_tmp_partial_index_on_vulnerability_report_types_spec.rb | 22 | ||||
-rw-r--r-- | spec/requests/users_controller_spec.rb | 100 | ||||
-rw-r--r-- | spec/serializers/profile/event_entity_spec.rb | 149 |
10 files changed, 452 insertions, 23 deletions
diff --git a/spec/features/profiles/user_deletes_saved_reply_spec.rb b/spec/features/profiles/user_deletes_saved_reply_spec.rb new file mode 100644 index 00000000000..35bd6018ee3 --- /dev/null +++ b/spec/features/profiles/user_deletes_saved_reply_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Profile > Saved replies > User deletes saved reply', :js, + feature_category: :user_profile do + let_it_be(:user) { create(:user) } + let_it_be(:saved_reply) { create(:saved_reply, user: user) } + + before do + sign_in(user) + end + + it 'shows the user a list of their saved replies' do + visit profile_saved_replies_path + + find('[data-testid="saved-reply-delete-btn"]').click + + page.within('.gl-modal') do + click_button 'Delete' + end + + wait_for_requests + + expect(page).not_to have_content(saved_reply.name) + end +end diff --git a/spec/features/work_items/work_item_children_spec.rb b/spec/features/projects/work_items/work_item_children_spec.rb index 43a6b2771f6..43a6b2771f6 100644 --- a/spec/features/work_items/work_item_children_spec.rb +++ b/spec/features/projects/work_items/work_item_children_spec.rb diff --git a/spec/features/work_items/work_item_spec.rb b/spec/features/projects/work_items/work_item_spec.rb index 3c71a27ff82..3c71a27ff82 100644 --- a/spec/features/work_items/work_item_spec.rb +++ b/spec/features/projects/work_items/work_item_spec.rb diff --git a/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap b/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap index 3abdfcdaf20..154ce2bd089 100644 --- a/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap +++ b/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap @@ -10,6 +10,21 @@ exports[`Saved replies list item component renders list item 1`] = ` <strong> test </strong> + + <div + class="gl-ml-auto" + > + <gl-button-stub + aria-label="Delete" + buttontextclasses="" + category="secondary" + data-testid="saved-reply-delete-btn" + icon="remove" + size="medium" + title="Delete" + variant="danger" + /> + </div> </div> <div @@ -17,5 +32,21 @@ exports[`Saved replies list item component renders list item 1`] = ` > /assign_reviewer </div> + + <gl-modal-stub + actionprimary="[object Object]" + actionsecondary="[object Object]" + arialabel="" + dismisslabel="Close" + modalclass="" + modalid="delete-saved-reply-2" + size="sm" + title="Delete saved reply" + titletag="h4" + > + <gl-sprintf-stub + message="Are you sure you want to delete %{name}? This action cannot be undone." + /> + </gl-modal-stub> </li> `; diff --git a/spec/frontend/saved_replies/components/list_item_spec.js b/spec/frontend/saved_replies/components/list_item_spec.js index cad1000473b..1d80844f30a 100644 --- a/spec/frontend/saved_replies/components/list_item_spec.js +++ b/spec/frontend/saved_replies/components/list_item_spec.js @@ -1,11 +1,31 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import { shallowMount } from '@vue/test-utils'; +import { GlModal } from '@gitlab/ui'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { createMockDirective } from 'helpers/vue_mock_directive'; +import waitForPromises from 'helpers/wait_for_promises'; import ListItem from '~/saved_replies/components/list_item.vue'; +import deleteSavedReplyMutation from '~/saved_replies/queries/delete_saved_reply.mutation.graphql'; let wrapper; +let deleteSavedReplyMutationResponse; function createComponent(propsData = {}) { + Vue.use(VueApollo); + + deleteSavedReplyMutationResponse = jest + .fn() + .mockResolvedValue({ data: { savedReplyDestroy: { errors: [] } } }); + return shallowMount(ListItem, { propsData, + directives: { + GlModal: createMockDirective('gl-modal'), + }, + apolloProvider: createMockApollo([ + [deleteSavedReplyMutation, deleteSavedReplyMutationResponse], + ]), }); } @@ -19,4 +39,16 @@ describe('Saved replies list item component', () => { expect(wrapper.element).toMatchSnapshot(); }); + + describe('delete button', () => { + it('calls Apollo mutate', async () => { + wrapper = createComponent({ reply: { name: 'test', content: '/assign_reviewer', id: 1 } }); + + wrapper.findComponent(GlModal).vm.$emit('primary'); + + await waitForPromises(); + + expect(deleteSavedReplyMutationResponse).toHaveBeenCalledWith({ id: 1 }); + }); + }); }); diff --git a/spec/lib/gitlab/database/async_constraints/migration_helpers_spec.rb b/spec/lib/gitlab/database/async_constraints/migration_helpers_spec.rb index 08d255a4bb8..4dd510499ab 100644 --- a/spec/lib/gitlab/database/async_constraints/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/async_constraints/migration_helpers_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::Database::AsyncConstraints::MigrationHelpers, feature_cat let(:column_name) { 'parent_id' } let(:fk_name) { nil } - context 'with regular tables' do + context 'with async FK validation on regular tables' do before do allow(migration).to receive(:puts) allow(migration.connection).to receive(:transaction_open?).and_return(false) @@ -183,4 +183,106 @@ RSpec.describe Gitlab::Database::AsyncConstraints::MigrationHelpers, feature_cat end end end + + context 'with async check constraint validations' do + let(:table_name) { '_test_async_check_constraints' } + let(:check_name) { 'partitioning_constraint' } + + before do + allow(migration).to receive(:puts) + allow(migration.connection).to receive(:transaction_open?).and_return(false) + + connection.create_table(table_name) do |t| + t.integer column_name + end + + migration.add_check_constraint( + table_name, "#{column_name} = 1", + check_name, validate: false) + end + + describe '#prepare_async_check_constraint_validation' do + it 'creates the record for async validation' do + expect do + migration.prepare_async_check_constraint_validation(table_name, name: check_name) + end.to change { constraint_model.where(name: check_name).count }.by(1) + + record = constraint_model.find_by(name: check_name) + + expect(record.table_name).to eq(table_name) + expect(record).to be_check_constraint + end + + context 'when the check constraint does not exist' do + it 'returns an error' do + expect do + migration.prepare_async_check_constraint_validation(table_name, name: 'missing') + end.to raise_error RuntimeError, /Could not find check constraint "missing" on table "#{table_name}"/ + end + end + + context 'when the record already exists' do + it 'does attempt to create the record' do + create(:postgres_async_constraint_validation, + table_name: table_name, + name: check_name, + constraint_type: :check_constraint) + + expect do + migration.prepare_async_check_constraint_validation(table_name, name: check_name) + end.not_to change { constraint_model.where(name: check_name).count } + end + end + + context 'when the async validation table does not exist' do + it 'does not raise an error' do + connection.drop_table(constraint_model.table_name) + + expect(constraint_model).not_to receive(:safe_find_or_create_by!) + + expect { migration.prepare_async_check_constraint_validation(table_name, name: check_name) } + .not_to raise_error + end + end + end + + describe '#unprepare_async_check_constraint_validation' do + context 'with check constraints' do + before do + migration.prepare_async_check_constraint_validation(table_name, name: check_name) + end + + it 'destroys the record' do + expect do + migration.unprepare_async_check_constraint_validation(table_name, name: check_name) + end.to change { constraint_model.where(name: check_name).count }.by(-1) + end + + context 'when the async validation table does not exist' do + it 'does not raise an error' do + connection.drop_table(constraint_model.table_name) + + expect(constraint_model).not_to receive(:find_by) + + expect { migration.unprepare_async_check_constraint_validation(table_name, name: check_name) } + .not_to raise_error + end + end + end + + context 'with other types of constraints' do + let(:constraint) { create(:postgres_async_constraint_validation, table_name: table_name, name: check_name) } + + it 'does not destroy the record' do + constraint.update_column(:constraint_type, 99) + + expect do + migration.unprepare_async_check_constraint_validation(table_name, name: check_name) + end.not_to change { constraint_model.where(name: check_name).count } + + expect(constraint).to be_present + end + end + end + end end diff --git a/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb b/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb index 7fea2eb1bcc..da9cc18f7d7 100644 --- a/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb +++ b/spec/lib/gitlab/database/async_constraints/postgres_async_constraint_validation_spec.rb @@ -48,6 +48,16 @@ RSpec.describe Gitlab::Database::AsyncConstraints::PostgresAsyncConstraintValida is_expected.to match_array([failed_validation, new_validation]) end end + + describe '.check_constraint_type' do + before do + new_validation.update!(constraint_type: :check_constraint) + end + + subject { described_class.check_constraint_type } + + it { is_expected.to eq([new_validation]) } + end end describe '.table_available?' do diff --git a/spec/migrations/20230221093533_add_tmp_partial_index_on_vulnerability_report_types_spec.rb b/spec/migrations/20230221093533_add_tmp_partial_index_on_vulnerability_report_types_spec.rb new file mode 100644 index 00000000000..cbf6b2882c4 --- /dev/null +++ b/spec/migrations/20230221093533_add_tmp_partial_index_on_vulnerability_report_types_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "spec_helper" + +require_migration! + +RSpec.describe AddTmpPartialIndexOnVulnerabilityReportTypes, feature_category: :vulnerability_management do + let(:async_index) { Gitlab::Database::AsyncIndexes::PostgresAsyncIndex } + let(:index_name) { described_class::INDEX_NAME } + + it "schedules the index" do + reversible_migration do |migration| + migration.before -> do + expect(async_index.where(name: index_name).count).to be(0) + end + + migration.after -> do + expect(async_index.where(name: index_name).count).to be(1) + end + end + end +end diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 11d8be24e06..75ae0c970c0 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -174,39 +174,95 @@ RSpec.describe UsersController, feature_category: :user_management do end context 'requested in json format' do - let(:project) { create(:project) } + context 'when profile_tabs_vue feature flag is turned OFF' do + let(:project) { create(:project) } - before do - project.add_developer(user) - Gitlab::DataBuilder::Push.build_sample(project, user) + before do + project.add_developer(user) + Gitlab::DataBuilder::Push.build_sample(project, user) + stub_feature_flags(profile_tabs_vue: false) + sign_in(user) + end - sign_in(user) - end + it 'loads events' do + get user_activity_url user.username, format: :json - it 'loads events' do - get user_activity_url user.username, format: :json + expect(response.media_type).to eq('application/json') + expect(Gitlab::Json.parse(response.body)['count']).to eq(1) + end - expect(response.media_type).to eq('application/json') - expect(Gitlab::Json.parse(response.body)['count']).to eq(1) - end + it 'hides events if the user cannot read cross project' do + allow(Ability).to receive(:allowed?).and_call_original + expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } - it 'hides events if the user cannot read cross project' do - allow(Ability).to receive(:allowed?).and_call_original - expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } + get user_activity_url user.username, format: :json - get user_activity_url user.username, format: :json + expect(response.media_type).to eq('application/json') + expect(Gitlab::Json.parse(response.body)['count']).to eq(0) + end - expect(response.media_type).to eq('application/json') - expect(Gitlab::Json.parse(response.body)['count']).to eq(0) + it 'hides events if the user has a private profile' do + Gitlab::DataBuilder::Push.build_sample(project, private_user) + + get user_activity_url private_user.username, format: :json + + expect(response.media_type).to eq('application/json') + expect(Gitlab::Json.parse(response.body)['count']).to eq(0) + end end - it 'hides events if the user has a private profile' do - Gitlab::DataBuilder::Push.build_sample(project, private_user) + context 'when profile_tabs_vue feature flag is turned ON' do + let(:project) { create(:project) } - get user_activity_url private_user.username, format: :json + before do + project.add_developer(user) + Gitlab::DataBuilder::Push.build_sample(project, user) + stub_feature_flags(profile_tabs_vue: true) + sign_in(user) + end - expect(response.media_type).to eq('application/json') - expect(Gitlab::Json.parse(response.body)['count']).to eq(0) + it 'loads events' do + get user_activity_url user.username, format: :json + + expect(response.media_type).to eq('application/json') + expect(Gitlab::Json.parse(response.body).count).to eq(1) + end + + it 'hides events if the user cannot read cross project' do + allow(Ability).to receive(:allowed?).and_call_original + expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } + + get user_activity_url user.username, format: :json + + expect(response.media_type).to eq('application/json') + expect(Gitlab::Json.parse(response.body).count).to eq(0) + end + + it 'hides events if the user has a private profile' do + Gitlab::DataBuilder::Push.build_sample(project, private_user) + + get user_activity_url private_user.username, format: :json + + expect(response.media_type).to eq('application/json') + expect(Gitlab::Json.parse(response.body).count).to eq(0) + end + + it 'hides events if the user has a private profile' do + project = create(:project, :private) + private_event_user = create(:user, include_private_contributions: true) + push_data = Gitlab::DataBuilder::Push.build_sample(project, private_event_user) + EventCreateService.new.push(project, private_event_user, push_data) + + get user_activity_url private_event_user.username, format: :json + + response_body = Gitlab::Json.parse(response.body) + event = response_body.first + expect(response.media_type).to eq('application/json') + expect(response_body.count).to eq(1) + expect(event).to include('created_at', 'author', 'action') + expect(event['action']).to eq('private') + expect(event).not_to include('ref', 'commit', 'target', 'resource_parent') + end end end end diff --git a/spec/serializers/profile/event_entity_spec.rb b/spec/serializers/profile/event_entity_spec.rb new file mode 100644 index 00000000000..1551fc76466 --- /dev/null +++ b/spec/serializers/profile/event_entity_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Profile::EventEntity, feature_category: :user_profile do + let_it_be(:group) { create(:group) } # rubocop:disable RSpec/FactoryBot/AvoidCreate + let_it_be(:project) { build(:project_empty_repo, group: group) } + let_it_be(:user) { create(:user) } # rubocop:disable RSpec/FactoryBot/AvoidCreate + let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project) } # rubocop:disable RSpec/FactoryBot/AvoidCreate + + let(:target_user) { user } + let(:event) { build(:event, :merged, author: user, project: project, target: merge_request) } + let(:request) { double(described_class, current_user: user, target_user: target_user) } # rubocop:disable RSpec/VerifiedDoubles + let(:entity) { described_class.new(event, request: request) } + + subject { entity.as_json } + + before do + group.add_maintainer(user) + end + + it 'exposes fields', :aggregate_failures do + expect(subject[:created_at]).to eq(event.created_at) + expect(subject[:action]).to eq(event.action) + expect(subject[:author][:id]).to eq(target_user.id) + expect(subject[:author][:name]).to eq(target_user.name) + expect(subject[:author][:path]).to eq(target_user.username) + end + + context 'for push events' do + let_it_be(:commit_from) { Gitlab::Git::BLANK_SHA } + let_it_be(:commit_title) { 'My commit' } + let(:event) { build(:push_event, project: project, author: target_user) } + + it 'exposes ref fields' do + build(:push_event_payload, event: event, ref_count: 3) + + expect(subject[:ref][:type]).to eq(event.ref_type) + expect(subject[:ref][:count]).to eq(event.ref_count) + expect(subject[:ref][:name]).to eq(event.ref_name) + expect(subject[:ref][:path]).to be_nil + end + + shared_examples 'returns ref path' do + specify do + expect(subject[:ref][:path]).to be_present + end + end + + context 'with tag' do + before do + allow(project.repository).to receive(:tag_exists?).and_return(true) + build(:push_event_payload, event: event, ref_type: :tag) + end + + it_behaves_like 'returns ref path' + end + + context 'with branch' do + before do + allow(project.repository).to receive(:branch_exists?).and_return(true) + build(:push_event_payload, event: event, ref_type: :branch) + end + + it_behaves_like 'returns ref path' + end + + it 'exposes commit fields' do + build(:push_event_payload, event: event, commit_title: commit_title, commit_from: commit_from, commit_count: 2) + + compare_path = "/#{group.path}/#{project.path}/-/compare/#{commit_from}...#{event.commit_to}" + expect(subject[:commit][:compare_path]).to eq(compare_path) + expect(event.commit_id).to include(subject[:commit][:truncated_sha]) + expect(subject[:commit][:path]).to be_present + expect(subject[:commit][:title]).to eq(commit_title) + expect(subject[:commit][:count]).to eq(2) + expect(commit_from).to include(subject[:commit][:from_truncated_sha]) + expect(event.commit_to).to include(subject[:commit][:to_truncated_sha]) + expect(subject[:commit][:create_mr_path]).to be_nil + end + + it 'exposes create_mr_path' do + allow(project).to receive(:default_branch).and_return('main') + allow(project.repository).to receive(:branch_exists?).and_return(true) + build(:push_event_payload, event: event, action: :created, commit_from: commit_from, commit_count: 2) + + new_mr_path = "/#{group.path}/#{project.path}/-/merge_requests/new?" \ + "merge_request%5Bsource_branch%5D=#{event.branch_name}" + expect(subject[:commit][:create_mr_path]).to eq(new_mr_path) + end + end + + context 'with target' do + let_it_be(:note) { build(:note_on_merge_request, :with_attachment, noteable: merge_request, project: project) } + + context 'when target does not responds to :reference_link_text' do + let(:event) { build(:event, :commented, project: project, target: note, author: target_user) } + + it 'exposes target fields' do + expect(subject[:target]).not_to include(:reference_link_text) + expect(subject[:target][:target_type]).to eq(note.class.to_s) + expect(subject[:target][:target_url]).to be_present + expect(subject[:target][:title]).to eq(note.title) + expect(subject[:target][:first_line_in_markdown]).to be_present + expect(subject[:target][:attachment][:url]).to eq(note.attachment.url) + end + end + + context 'when target responds to :reference_link_text' do + it 'exposes reference_link_text' do + expect(subject[:target][:reference_link_text]).to eq(merge_request.reference_link_text) + end + end + end + + context 'with resource parent' do + it 'exposes resource parent fields' do + resource_parent = event.resource_parent + + expect(subject[:resource_parent][:type]).to eq('project') + expect(subject[:resource_parent][:full_name]).to eq(resource_parent.full_name) + expect(subject[:resource_parent][:full_path]).to eq(resource_parent.full_path) + end + end + + context 'for private events' do + let(:event) { build(:event, :merged, author: target_user) } + + context 'when include_private_contributions? is true' do + let(:target_user) { build(:user, include_private_contributions: true) } + + it 'exposes only created_at, action, and author', :aggregate_failures do + expect(subject[:created_at]).to eq(event.created_at) + expect(subject[:action]).to eq('private') + expect(subject[:author][:id]).to eq(target_user.id) + expect(subject[:author][:name]).to eq(target_user.name) + expect(subject[:author][:path]).to eq(target_user.username) + + is_expected.not_to include(:ref, :commit, :target, :resource_parent) + end + end + + context 'when include_private_contributions? is false' do + let(:target_user) { build(:user, include_private_contributions: false) } + + it { is_expected.to be_empty } + end + end +end |