diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-01-10 21:07:39 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-01-10 21:07:39 +0300 |
commit | 8cc4a6f23d41a1c57dc309130d2ce9ebc04d8334 (patch) | |
tree | 8391f5ee4f3391534131ae834b4b0a413845239e /spec | |
parent | 87f8fdb93cb1e63f8e9cedf7d3d00c8ade70b18c (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
22 files changed, 419 insertions, 191 deletions
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb index 4174faae1ed..4ae9b4def8e 100644 --- a/spec/factories/abuse_reports.rb +++ b/spec/factories/abuse_reports.rb @@ -5,5 +5,6 @@ FactoryBot.define do reporter factory: :user user message { 'User sends spam' } + reported_from_url { 'http://gitlab.com' } end end diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb index 591b397119c..f9fa3e6e109 100644 --- a/spec/features/abuse_report_spec.rb +++ b/spec/features/abuse_report_spec.rb @@ -2,130 +2,119 @@ require 'spec_helper' -RSpec.describe 'Abuse reports', feature_category: :insider_threat do - let_it_be(:abusive_user) { create(:user, username: 'abuser_mcabusive') } - let_it_be(:reporter1) { create(:user, username: 'reporter_mcreporty') } - let_it_be(:reporter2) { create(:user) } +RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do + let_it_be(:abusive_user) { create(:user) } + + let_it_be(:reporter1) { create(:user) } + let_it_be(:project) { create(:project, :public) } let_it_be(:issue) { create(:issue, project: project, author: abusive_user) } - let!(:group) do - create(:group).tap do |g| - g.add_owner(reporter1) - g.add_developer(abusive_user) - end - end - before do sign_in(reporter1) end - it 'allows a user to be reported for abuse from an issue', :js do - visit project_issue_path(project, issue) + context 'when reporting an issue for abuse' do + it 'allows a user to be reported for abuse from an issue', :js do + visit project_issue_path(project, issue) - click_button 'Issue actions' - click_link 'Report abuse to administrator' + click_button 'Issue actions' + click_link 'Report abuse to administrator' - wait_for_requests + wait_for_requests - fill_and_submit_form + fill_and_submit_form - expect(page).to have_content 'Thank you for your report' - end + expect(page).to have_content 'Thank you for your report' + end - it 'allows a user to be reported for abuse from their profile', :js do - visit user_path(abusive_user) + it 'redirects backs to the issue when cancel button is clicked' do + visit project_issue_path(project, issue) - click_button 'Report abuse to administrator' + click_button 'Issue actions' + click_link 'Report abuse to administrator' - choose "They're posting spam." - click_button 'Next' + wait_for_requests - wait_for_requests + click_link 'Cancel' - fill_and_submit_form + expect(page).to have_current_path(project_issue_path(project, issue)) + end + end - expect(page).to have_content 'Thank you for your report' + context 'when reporting a user profile for abuse' do + let_it_be(:reporter2) { create(:user) } - visit user_path(abusive_user) + it 'allows a user to be reported for abuse from their profile' do + visit user_path(abusive_user) - click_button 'Report abuse to administrator' + click_button 'Report abuse to administrator' - choose "They're posting spam." - click_button 'Next' + choose "They're posting spam." + click_button 'Next' - fill_and_submit_form + wait_for_requests - expect(page).to have_content 'You have already reported this user' - end + fill_and_submit_form - it 'allows multiple users to report a user', :js do - visit user_path(abusive_user) + expect(page).to have_content 'Thank you for your report' - click_button 'Report abuse to administrator' + visit user_path(abusive_user) - choose "They're posting spam." - click_button 'Next' + click_button 'Report abuse to administrator' - wait_for_requests + choose "They're posting spam." + click_button 'Next' - fill_and_submit_form + fill_and_submit_form - expect(page).to have_content 'Thank you for your report' + expect(page).to have_content 'User has already been reported for abuse' + end - sign_out(reporter1) - sign_in(reporter2) + it 'allows multiple users to report a user' do + visit user_path(abusive_user) - visit user_path(abusive_user) + click_button 'Report abuse to administrator' - click_button 'Report abuse to administrator' + choose "They're posting spam." + click_button 'Next' - choose "They're posting spam." - click_button 'Next' + wait_for_requests - wait_for_requests + fill_and_submit_form - fill_and_submit_form + expect(page).to have_content 'Thank you for your report' - expect(page).to have_content 'Thank you for your report' - end + gitlab_sign_out + gitlab_sign_in(reporter2) - describe 'Cancel', :js do - context 'when ref_url is not present (e.g. visit user page then click on report abuse)' do - it 'links the user back to where abuse report was triggered' do - origin_url = user_path(abusive_user) + visit user_path(abusive_user) - visit origin_url + click_button 'Report abuse to administrator' - click_button 'Report abuse to administrator' - choose "They're posting spam." - click_button 'Next' + choose "They're posting spam." + click_button 'Next' - wait_for_requests + wait_for_requests - click_link 'Cancel' + fill_and_submit_form - expect(page).to have_current_path(origin_url) - end + expect(page).to have_content 'Thank you for your report' end - context 'when ref_url is present (e.g. user is reported from one of their MRs)' do - it 'links the user back to ref_url' do - ref_url = group_group_members_path(group) + it 'redirects backs to user profile when cancel button is clicked' do + visit user_path(abusive_user) - visit ref_url + click_button 'Report abuse to administrator' - # visit abusive user's profile page - page.first('.js-user-link').click + choose "They're posting spam." + click_button 'Next' - click_button 'Report abuse to administrator' - choose "They're posting spam." - click_button 'Next' + wait_for_requests - click_link 'Cancel' + click_link 'Cancel' - expect(page).to have_current_path(ref_url) - end + expect(page).to have_current_path(user_path(abusive_user)) end end diff --git a/spec/features/merge_request/user_sees_discussions_navigation_spec.rb b/spec/features/merge_request/user_sees_discussions_navigation_spec.rb index 2afe7e3edba..d75a8049925 100644 --- a/spec/features/merge_request/user_sees_discussions_navigation_spec.rb +++ b/spec/features/merge_request/user_sees_discussions_navigation_spec.rb @@ -194,29 +194,10 @@ RSpec.describe 'Merge request > User sees discussions navigation', :js, feature_ end def goto_next_thread - begin - # this is required when moved_mr_sidebar is enabled - page.within('.issue-sticky-header') do - click_button 'Go to next unresolved thread' - end - rescue StandardError - click_button 'Go to next unresolved thread' - end - wait_for_scroll_end + click_button 'Go to next unresolved thread' end def goto_previous_thread - begin - page.within('.issue-sticky-header') do - click_button 'Go to previous unresolved thread' - end - rescue StandardError - click_button 'Go to previous unresolved thread' - end - wait_for_scroll_end - end - - def wait_for_scroll_end - sleep(1) + click_button 'Go to previous unresolved thread' end end diff --git a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js index 4f66348f9cd..69c4717e52b 100644 --- a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js +++ b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js @@ -112,7 +112,7 @@ describe('AbuseCategorySelector', () => { it('renders referer as a hidden fields', () => { expect(findReferer().attributes()).toMatchObject({ type: 'hidden', - name: 'ref_url', + name: 'abuse_report[reported_from_url]', value: REPORTED_FROM_URL, }); }); diff --git a/spec/frontend/constants_spec.js b/spec/frontend/constants_spec.js new file mode 100644 index 00000000000..b596b62f72c --- /dev/null +++ b/spec/frontend/constants_spec.js @@ -0,0 +1,30 @@ +import * as constants from '~/constants'; + +describe('Global JS constants', () => { + describe('getModifierKey()', () => { + afterEach(() => { + delete window.gl; + }); + + it.each` + isMac | removeSuffix | expectedKey + ${true} | ${false} | ${'⌘'} + ${false} | ${false} | ${'Ctrl+'} + ${true} | ${true} | ${'⌘'} + ${false} | ${true} | ${'Ctrl'} + `( + 'returns correct keystroke when isMac=$isMac and removeSuffix=$removeSuffix', + ({ isMac, removeSuffix, expectedKey }) => { + Object.assign(window, { + gl: { + client: { + isMac, + }, + }, + }); + + expect(constants.getModifierKey(removeSuffix)).toBe(expectedKey); + }, + ); + }); +}); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js index 96c670eaad2..fa0d76762df 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js @@ -335,10 +335,10 @@ describe('tags list row', () => { }); describe.each` - name | finderFunction | text | icon | clipboard - ${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the gitlab-org/gitlab-test/rails-12009 image repository at 01:29 UTC on 2020-11-03'} | ${'clock'} | ${false} - ${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:2cf3d2fdac1b04a14301d47d51cb88dcd26714c74f91440eeee99ce399089062'} | ${'log'} | ${true} - ${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:c2613843ab33aabf847965442b13a8b55a56ae28837ce182627c0716eb08c02b'} | ${'cloud-gear'} | ${true} + name | finderFunction | text | icon | clipboard + ${'published date detail'} | ${findPublishedDateDetail} | ${'Published to the gitlab-org/gitlab-test/rails-12009 image repository at 13:29:38 UTC on 2020-11-03'} | ${'clock'} | ${false} + ${'manifest detail'} | ${findManifestDetail} | ${'Manifest digest: sha256:2cf3d2fdac1b04a14301d47d51cb88dcd26714c74f91440eeee99ce399089062'} | ${'log'} | ${true} + ${'configuration detail'} | ${findConfigurationDetail} | ${'Configuration digest: sha256:c2613843ab33aabf847965442b13a8b55a56ae28837ce182627c0716eb08c02b'} | ${'cloud-gear'} | ${true} `('$name details row', ({ finderFunction, text, icon, clipboard }) => { it(`has ${text} as text`, async () => { mountComponent(); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js index c6b5138639e..0cbe2755f7e 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js @@ -61,14 +61,14 @@ describe('Package History', () => { ); }); describe.each` - name | amount | icon | text | timeAgoTooltip | link - ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'Test package version 1.0.0 was first created'} | ${mavenPackage.created_at} | ${null} - ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit #sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url} - ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url} - ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null} - ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null} - ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null} - ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit #sha-baz on branch branch-name, built by pipeline #3, and published to the registry'} | ${mavenPackage.created_at} | ${mockPipelineInfo.project.commit_url} + name | amount | icon | text | timeAgoTooltip | link + ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'Test package version 1.0.0 was first created'} | ${mavenPackage.created_at} | ${null} + ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url} + ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url} + ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null} + ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null} + ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null} + ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit sha-baz on branch branch-name, built by pipeline #3, and published to the registry'} | ${mavenPackage.created_at} | ${mockPipelineInfo.project.commit_url} `( 'with $amount pipelines history element $name', ({ name, icon, text, timeAgoTooltip, link, amount }) => { diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js index ec2e833552a..bb2fa9eb6f5 100644 --- a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js +++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js @@ -131,14 +131,14 @@ describe('Package History', () => { }); describe.each` - name | amount | icon | text | timeAgoTooltip | link - ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'@gitlab-org/package-15 version 1.0.0 was first created'} | ${packageData().createdAt} | ${null} - ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit #b83d6e39 on branch master'} | ${null} | ${onePipeline.commitPath} - ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by Administrator'} | ${onePipeline.createdAt} | ${onePipeline.path} - ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${packageData().createdAt} | ${null} - ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null} - ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null} - ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit #b83d6e39 on branch master, built by pipeline #3, and published to the registry'} | ${packageData().createdAt} | ${onePipeline.commitPath} + name | amount | icon | text | timeAgoTooltip | link + ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'@gitlab-org/package-15 version 1.0.0 was first created'} | ${packageData().createdAt} | ${null} + ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit b83d6e39 on branch master'} | ${null} | ${onePipeline.commitPath} + ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by Administrator'} | ${onePipeline.createdAt} | ${onePipeline.path} + ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${packageData().createdAt} | ${null} + ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null} + ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null} + ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit b83d6e39 on branch master, built by pipeline #3, and published to the registry'} | ${packageData().createdAt} | ${onePipeline.commitPath} `( 'with $amount pipelines history element $name', ({ name, icon, text, timeAgoTooltip, link, amount }) => { diff --git a/spec/frontend/users/profile/components/report_abuse_button_spec.js b/spec/frontend/users/profile/components/report_abuse_button_spec.js index bd39a089473..a09a437a9cc 100644 --- a/spec/frontend/users/profile/components/report_abuse_button_spec.js +++ b/spec/frontend/users/profile/components/report_abuse_button_spec.js @@ -1,5 +1,6 @@ import { GlButton } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import ReportAbuseButton from '~/users/profile/components/report_abuse_button.vue'; import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; @@ -69,4 +70,14 @@ describe('ReportAbuseButton', () => { expect(findAbuseCategorySelector().props('showDrawer')).toBe(false); }); }); + + describe('when user hovers out of the button', () => { + it(`should emit ${BV_HIDE_TOOLTIP} to close the tooltip`, () => { + jest.spyOn(wrapper.vm.$root, '$emit'); + + findReportAbuseButton().vm.$emit('mouseout'); + + expect(wrapper.vm.$root.$emit).toHaveBeenCalledWith(BV_HIDE_TOOLTIP); + }); + }); }); diff --git a/spec/frontend/work_items/components/work_item_notes_spec.js b/spec/frontend/work_items/components/work_item_notes_spec.js index 98b2e34b860..63947e66392 100644 --- a/spec/frontend/work_items/components/work_item_notes_spec.js +++ b/spec/frontend/work_items/components/work_item_notes_spec.js @@ -6,6 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import SystemNote from '~/work_items/components/notes/system_note.vue'; import WorkItemNotes from '~/work_items/components/work_item_notes.vue'; +import WorkItemCommentForm from '~/work_items/components/work_item_comment_form.vue'; import ActivityFilter from '~/work_items/components/notes/activity_filter.vue'; import workItemNotesQuery from '~/work_items/graphql/work_item_notes.query.graphql'; import workItemNotesByIidQuery from '~/work_items/graphql/work_item_notes_by_iid.query.graphql'; @@ -40,6 +41,7 @@ describe('WorkItemNotes component', () => { const findAllSystemNotes = () => wrapper.findAllComponents(SystemNote); const findActivityLabel = () => wrapper.find('label'); + const findWorkItemCommentForm = () => wrapper.findComponent(WorkItemCommentForm); const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver); const findSortingFilter = () => wrapper.findComponent(ActivityFilter); @@ -85,6 +87,13 @@ describe('WorkItemNotes component', () => { expect(findActivityLabel().exists()).toBe(true); }); + it('passes correct props to comment form component', async () => { + createComponent({ workItemId: mockWorkItemId, fetchByIid: false }); + await waitForPromises(); + + expect(findWorkItemCommentForm().props('fetchByIid')).toEqual(false); + }); + describe('when notes are loading', () => { it('renders skeleton loader', () => { expect(findSkeletonLoader().exists()).toBe(true); @@ -117,6 +126,10 @@ describe('WorkItemNotes component', () => { mockNotesByIidWidgetResponse.discussions.nodes.length, ); }); + + it('passes correct props to comment form component', () => { + expect(findWorkItemCommentForm().props('fetchByIid')).toEqual(true); + }); }); describe('Pagination', () => { diff --git a/spec/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens_spec.rb b/spec/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens_spec.rb new file mode 100644 index 00000000000..7075d4694ae --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_admin_mode_scope_for_personal_access_tokens_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::BackfillAdminModeScopeForPersonalAccessTokens, + :migration, schema: 20221228103133, feature_category: :authentication_and_authorization do + let(:users) { table(:users) } + let(:personal_access_tokens) { table(:personal_access_tokens) } + + let(:admin) { users.create!(name: 'admin', email: 'admin@example.com', projects_limit: 1, admin: true) } + let(:user) { users.create!(name: 'user', email: 'user@example.com', projects_limit: 1) } + + let!(:pat_admin_1) { personal_access_tokens.create!(name: 'admin 1', user_id: admin.id, scopes: "---\n- api\n") } + let!(:pat_user) { personal_access_tokens.create!(name: 'user 1', user_id: user.id, scopes: "---\n- api\n") } + let!(:pat_revoked) do + personal_access_tokens.create!(name: 'admin 2', user_id: admin.id, scopes: "---\n- api\n", revoked: true) + end + + let!(:pat_expired) do + personal_access_tokens.create!(name: 'admin 3', user_id: admin.id, scopes: "---\n- api\n", expires_at: 1.day.ago) + end + + let!(:pat_admin_mode) do + personal_access_tokens.create!(name: 'admin 4', user_id: admin.id, scopes: "---\n- admin_mode\n") + end + + let!(:pat_admin_2) { personal_access_tokens.create!(name: 'admin 5', user_id: admin.id, scopes: "---\n- read_api\n") } + let!(:pat_not_in_range) { personal_access_tokens.create!(name: 'admin 6', user_id: admin.id, scopes: "---\n- api\n") } + + subject do + described_class.new( + start_id: pat_admin_1.id, + end_id: pat_admin_2.id, + batch_table: :personal_access_tokens, + batch_column: :id, + sub_batch_size: 1, + pause_ms: 0, + connection: ApplicationRecord.connection + ) + end + + it "adds `admin_mode` scope to active personal access tokens of administrators" do + subject.perform + + expect(pat_admin_1.reload.scopes).to eq("---\n- api\n- admin_mode\n") + expect(pat_user.reload.scopes).to eq("---\n- api\n") + expect(pat_revoked.reload.scopes).to eq("---\n- api\n") + expect(pat_expired.reload.scopes).to eq("---\n- api\n") + expect(pat_admin_mode.reload.scopes).to eq("---\n- admin_mode\n") + expect(pat_admin_2.reload.scopes).to eq("---\n- read_api\n- admin_mode\n") + expect(pat_not_in_range.reload.scopes).to eq("---\n- api\n") + end +end diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 8923187142f..a1433b9a1d2 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -973,58 +973,58 @@ RSpec.describe Gitlab::Database::MigrationHelpers do describe '#foreign_key_exists?' do before do - key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( - :projects, :users, - { - column: :non_standard_id, - name: :fk_projects_users_non_standard_id, - on_delete: :cascade, - primary_key: :id - } - ) - allow(model).to receive(:foreign_keys).with(:projects).and_return([key]) + model.connection.execute(<<~SQL) + create table referenced ( + id bigserial primary key not null + ); + create table referencing ( + id bigserial primary key not null, + non_standard_id bigint not null, + constraint fk_referenced foreign key (non_standard_id) references referenced(id) on delete cascade + ); + SQL end shared_examples_for 'foreign key checks' do it 'finds existing foreign keys by column' do - expect(model.foreign_key_exists?(:projects, target_table, column: :non_standard_id)).to be_truthy + expect(model.foreign_key_exists?(:referencing, target_table, column: :non_standard_id)).to be_truthy end it 'finds existing foreign keys by name' do - expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id)).to be_truthy + expect(model.foreign_key_exists?(:referencing, target_table, name: :fk_referenced)).to be_truthy end it 'finds existing foreign_keys by name and column' do - expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id)).to be_truthy + expect(model.foreign_key_exists?(:referencing, target_table, name: :fk_referenced, column: :non_standard_id)).to be_truthy end it 'finds existing foreign_keys by name, column and on_delete' do - expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id, on_delete: :cascade)).to be_truthy + expect(model.foreign_key_exists?(:referencing, target_table, name: :fk_referenced, column: :non_standard_id, on_delete: :cascade)).to be_truthy end it 'finds existing foreign keys by target table only' do - expect(model.foreign_key_exists?(:projects, target_table)).to be_truthy + expect(model.foreign_key_exists?(:referencing, target_table)).to be_truthy end it 'compares by column name if given' do - expect(model.foreign_key_exists?(:projects, target_table, column: :user_id)).to be_falsey + expect(model.foreign_key_exists?(:referencing, target_table, column: :user_id)).to be_falsey end it 'compares by target column name if given' do - expect(model.foreign_key_exists?(:projects, target_table, primary_key: :user_id)).to be_falsey - expect(model.foreign_key_exists?(:projects, target_table, primary_key: :id)).to be_truthy + expect(model.foreign_key_exists?(:referencing, target_table, primary_key: :user_id)).to be_falsey + expect(model.foreign_key_exists?(:referencing, target_table, primary_key: :id)).to be_truthy end it 'compares by foreign key name if given' do - expect(model.foreign_key_exists?(:projects, target_table, name: :non_existent_foreign_key_name)).to be_falsey + expect(model.foreign_key_exists?(:referencing, target_table, name: :non_existent_foreign_key_name)).to be_falsey end it 'compares by foreign key name and column if given' do - expect(model.foreign_key_exists?(:projects, target_table, name: :non_existent_foreign_key_name, column: :non_standard_id)).to be_falsey + expect(model.foreign_key_exists?(:referencing, target_table, name: :non_existent_foreign_key_name, column: :non_standard_id)).to be_falsey end it 'compares by foreign key name, column and on_delete if given' do - expect(model.foreign_key_exists?(:projects, target_table, name: :fk_projects_users_non_standard_id, column: :non_standard_id, on_delete: :nullify)).to be_falsey + expect(model.foreign_key_exists?(:referencing, target_table, name: :fk_referenced, column: :non_standard_id, on_delete: :nullify)).to be_falsey end end @@ -1035,7 +1035,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do end context 'specifying a target table' do - let(:target_table) { :users } + let(:target_table) { :referenced } it_behaves_like 'foreign key checks' end @@ -1044,59 +1044,66 @@ RSpec.describe Gitlab::Database::MigrationHelpers do expect(model.foreign_key_exists?(:projects, :other_table)).to be_falsey end + it 'raises an error if an invalid on_delete is specified' do + # The correct on_delete key is "nullify" + expect { model.foreign_key_exists?(:referenced, on_delete: :set_null) }.to raise_error(ArgumentError) + end + context 'with foreign key using multiple columns' do before do - key = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( - :projects, :users, - { - column: [:partition_number, :id], - name: :fk_projects_users_partition_number_id, - on_delete: :cascade, - primary_key: [:partition_number, :id] - } - ) - allow(model).to receive(:foreign_keys).with(:projects).and_return([key]) + model.connection.execute(<<~SQL) + create table p_referenced ( + id bigserial not null, + partition_number bigint not null default 100, + primary key (partition_number, id) + ); + create table p_referencing ( + id bigserial primary key not null, + partition_number bigint not null, + constraint fk_partitioning foreign key (partition_number, id) references p_referenced(partition_number, id) on delete cascade + ); + SQL end it 'finds existing foreign keys by columns' do - expect(model.foreign_key_exists?(:projects, :users, column: [:partition_number, :id])).to be_truthy + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, column: [:partition_number, :id])).to be_truthy end it 'finds existing foreign keys by name' do - expect(model.foreign_key_exists?(:projects, :users, name: :fk_projects_users_partition_number_id)).to be_truthy + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :fk_partitioning)).to be_truthy end it 'finds existing foreign_keys by name and column' do - expect(model.foreign_key_exists?(:projects, :users, name: :fk_projects_users_partition_number_id, column: [:partition_number, :id])).to be_truthy + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :fk_partitioning, column: [:partition_number, :id])).to be_truthy end it 'finds existing foreign_keys by name, column and on_delete' do - expect(model.foreign_key_exists?(:projects, :users, name: :fk_projects_users_partition_number_id, column: [:partition_number, :id], on_delete: :cascade)).to be_truthy + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :fk_partitioning, column: [:partition_number, :id], on_delete: :cascade)).to be_truthy end it 'finds existing foreign keys by target table only' do - expect(model.foreign_key_exists?(:projects, :users)).to be_truthy + expect(model.foreign_key_exists?(:p_referencing, :p_referenced)).to be_truthy end it 'compares by column name if given' do - expect(model.foreign_key_exists?(:projects, :users, column: :id)).to be_falsey + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, column: :id)).to be_falsey end it 'compares by target column name if given' do - expect(model.foreign_key_exists?(:projects, :users, primary_key: :user_id)).to be_falsey - expect(model.foreign_key_exists?(:projects, :users, primary_key: [:partition_number, :id])).to be_truthy + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, primary_key: :user_id)).to be_falsey + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, primary_key: [:partition_number, :id])).to be_truthy end it 'compares by foreign key name if given' do - expect(model.foreign_key_exists?(:projects, :users, name: :non_existent_foreign_key_name)).to be_falsey + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :non_existent_foreign_key_name)).to be_falsey end it 'compares by foreign key name and column if given' do - expect(model.foreign_key_exists?(:projects, :users, name: :non_existent_foreign_key_name, column: [:partition_number, :id])).to be_falsey + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :non_existent_foreign_key_name, column: [:partition_number, :id])).to be_falsey end it 'compares by foreign key name, column and on_delete if given' do - expect(model.foreign_key_exists?(:projects, :users, name: :fk_projects_users_partition_number_id, column: [:partition_number, :id], on_delete: :nullify)).to be_falsey + expect(model.foreign_key_exists?(:p_referencing, :p_referenced, name: :fk_partitioning, column: [:partition_number, :id], on_delete: :nullify)).to be_falsey end end end diff --git a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb index 16180da3e3a..a8dbc4be16f 100644 --- a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb +++ b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb @@ -43,6 +43,14 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model, feature_categ end end + describe '#by_referenced_table_name' do + it 'finds the foreign keys for the referenced table' do + expected = described_class.find_by!(name: 'fk_constrained_to_referenced') + + expect(described_class.by_referenced_table_name('referenced_table')).to contain_exactly(expected) + end + end + describe '#by_constrained_table_identifier' do it 'throws an error when the identifier name is not fully qualified' do expect { described_class.by_constrained_table_identifier('constrained_table') }.to raise_error(ArgumentError, /not fully qualified/) @@ -55,9 +63,25 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model, feature_categ end end + describe '#by_constrained_table_name' do + it 'finds the foreign keys for the constrained table' do + expected = described_class.where(name: %w[fk_constrained_to_referenced fk_constrained_to_other_referenced]).to_a + + expect(described_class.by_constrained_table_name('constrained_table')).to match_array(expected) + end + end + + describe '#by_name' do + it 'finds foreign keys by name' do + expect(described_class.by_name('fk_constrained_to_referenced').pluck(:name)).to contain_exactly('fk_constrained_to_referenced') + end + end + context 'when finding columns for foreign keys' do using RSpec::Parameterized::TableSyntax + let(:fks) { described_class.by_constrained_table_name('constrained_table') } + where(:fk, :expected_constrained, :expected_referenced) do lazy { described_class.find_by(name: 'fk_constrained_to_referenced') } | %w[referenced_table_id referenced_table_id_b] | %w[id id_b] lazy { described_class.find_by(name: 'fk_constrained_to_other_referenced') } | %w[other_referenced_table_id] | %w[id] @@ -71,23 +95,70 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model, feature_categ it 'finds the correct referenced column names' do expect(fk.referenced_columns).to eq(expected_referenced) end + + describe '#by_constrained_columns' do + it 'finds the correct foreign key' do + expect(fks.by_constrained_columns(expected_constrained)).to contain_exactly(fk) + end + end + + describe '#by_referenced_columns' do + it 'finds the correct foreign key' do + expect(fks.by_referenced_columns(expected_referenced)).to contain_exactly(fk) + end + end end end describe '#on_delete_action' do + before do + ApplicationRecord.connection.execute(<<~SQL) + create table public.referenced_table_all_on_delete_actions ( + id bigserial primary key not null + ); + + create table public.constrained_table_all_on_delete_actions ( + id bigserial primary key not null, + ref_id_no_action bigint not null constraint fk_no_action references referenced_table_all_on_delete_actions(id), + ref_id_restrict bigint not null constraint fk_restrict references referenced_table_all_on_delete_actions(id) on delete restrict, + ref_id_nullify bigint not null constraint fk_nullify references referenced_table_all_on_delete_actions(id) on delete set null, + ref_id_cascade bigint not null constraint fk_cascade references referenced_table_all_on_delete_actions(id) on delete cascade, + ref_id_set_default bigint not null constraint fk_set_default references referenced_table_all_on_delete_actions(id) on delete set default + ) + SQL + end + + let(:fks) { described_class.by_constrained_table_name('constrained_table_all_on_delete_actions') } + + context 'with an invalid on_delete_action' do + it 'raises an error' do + # the correct value is :nullify, not :set_null + expect { fks.by_on_delete_action(:set_null) }.to raise_error(ArgumentError) + end + end + where(:fk_name, :expected_on_delete_action) do [ - %w[fk_constrained_to_referenced restrict], - %w[fk_constrained_to_other_referenced no_action] + %w[fk_no_action no_action], + %w[fk_restrict restrict], + %w[fk_nullify nullify], + %w[fk_cascade cascade], + %w[fk_set_default set_default] ] end with_them do - subject(:fk) { described_class.find_by(name: fk_name) } + subject(:fk) { fks.find_by(name: fk_name) } it 'has the appropriate on delete action' do expect(fk.on_delete_action).to eq(expected_on_delete_action) end + + describe '#by_on_delete_action' do + it 'finds the key by on delete action' do + expect(fks.by_on_delete_action(expected_on_delete_action)).to contain_exactly(fk) + end + end end end diff --git a/spec/migrations/queue_backfill_admin_mode_scope_for_personal_access_tokens_spec.rb b/spec/migrations/queue_backfill_admin_mode_scope_for_personal_access_tokens_spec.rb new file mode 100644 index 00000000000..8209f317550 --- /dev/null +++ b/spec/migrations/queue_backfill_admin_mode_scope_for_personal_access_tokens_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueBackfillAdminModeScopeForPersonalAccessTokens, + feature_category: :authentication_and_authorization do + describe '#up' do + it 'schedules background migration' do + migrate! + + expect(described_class::MIGRATION).to have_scheduled_batched_migration( + table_name: :personal_access_tokens, + column_name: :id, + interval: described_class::DELAY_INTERVAL) + end + end +end diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index 95e61488c80..29baccfe0ee 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -20,6 +20,11 @@ RSpec.describe AbuseReport, feature_category: :insider_threat do end describe 'validations' do + let(:http) { 'http://gitlab.com' } + let(:https) { 'https://gitlab.com' } + let(:ftp) { 'ftp://example.com' } + let(:javascript) { 'javascript:alert(window.opener.document.location)' } + it { is_expected.to validate_presence_of(:reporter) } it { is_expected.to validate_presence_of(:user) } it { is_expected.to validate_presence_of(:message) } @@ -28,8 +33,16 @@ RSpec.describe AbuseReport, feature_category: :insider_threat do it do is_expected.to validate_uniqueness_of(:user_id) .scoped_to(:reporter_id) - .with_message('You have already reported this user') + .with_message('has already been reported for abuse') end + + it { is_expected.to validate_length_of(:reported_from_url).is_at_most(512).allow_blank } + it { is_expected.to allow_value(http).for(:reported_from_url) } + it { is_expected.to allow_value(https).for(:reported_from_url) } + it { is_expected.not_to allow_value(ftp).for(:reported_from_url) } + it { is_expected.not_to allow_value(javascript).for(:reported_from_url) } + it { is_expected.to allow_value('http://localhost:9000').for(:reported_from_url) } + it { is_expected.to allow_value('https://gitlab.com').for(:reported_from_url) } end describe '#remove_user' do diff --git a/spec/models/integrations/apple_app_store_spec.rb b/spec/models/integrations/apple_app_store_spec.rb index dde26e383c7..1a57f556895 100644 --- a/spec/models/integrations/apple_app_store_spec.rb +++ b/spec/models/integrations/apple_app_store_spec.rb @@ -29,7 +29,7 @@ RSpec.describe Integrations::AppleAppStore, feature_category: :mobile_devops do describe '#fields' do it 'returns custom fields' do expect(apple_app_store_integration.fields.pluck(:name)).to eq(%w[app_store_issuer_id app_store_key_id - app_store_private_key]) + app_store_private_key]) end end diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb index 9d4c53f8d55..f65b5ff824b 100644 --- a/spec/models/personal_access_token_spec.rb +++ b/spec/models/personal_access_token_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PersonalAccessToken do +RSpec.describe PersonalAccessToken, feature_category: :authentication_and_authorization do subject { described_class } describe '.build' do @@ -210,6 +210,12 @@ RSpec.describe PersonalAccessToken do expect(personal_access_token).to be_valid end + it "allows creating a token with `admin_mode` scope" do + personal_access_token.scopes = [:api, :admin_mode] + + expect(personal_access_token).to be_valid + end + context 'when registry is disabled' do before do stub_container_registry_config(enabled: false) @@ -340,4 +346,27 @@ RSpec.describe PersonalAccessToken do end end end + + # During the implementation of Admin Mode for API, tokens of + # administrators should automatically get the `admin_mode` scope as well + # See https://gitlab.com/gitlab-org/gitlab/-/issues/42692 + describe '`admin_mode scope' do + subject { create(:personal_access_token, user: user, scopes: ['api']) } + + context 'with administrator user' do + let_it_be(:user) { create(:user, :admin) } + + it 'adds `admin_mode` scope before created' do + expect(subject.scopes).to contain_exactly('api', 'admin_mode') + end + end + + context 'with normal user' do + let_it_be(:user) { create(:user) } + + it 'does not add `admin_mode` scope before created' do + expect(subject.scopes).to contain_exactly('api') + end + end + end end diff --git a/spec/requests/abuse_reports_controller_spec.rb b/spec/requests/abuse_reports_controller_spec.rb index 71ecf8444bf..49a80689c65 100644 --- a/spec/requests/abuse_reports_controller_spec.rb +++ b/spec/requests/abuse_reports_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AbuseReportsController, feature_category: :users do +RSpec.describe AbuseReportsController, feature_category: :insider_threat do let(:reporter) { create(:user) } let(:user) { create(:user) } let(:attrs) do @@ -16,6 +16,18 @@ RSpec.describe AbuseReportsController, feature_category: :users do end describe 'GET new' do + let(:ref_url) { 'http://example.com' } + + it 'sets the instance variables' do + get new_abuse_report_path(user_id: user.id, ref_url: ref_url) + + expect(assigns(:abuse_report)).to be_kind_of(AbuseReport) + expect(assigns(:abuse_report)).to have_attributes( + user_id: user.id, + reported_from_url: ref_url + ) + end + context 'when the user has already been deleted' do it 'redirects the reporter to root_path' do user_id = user.id @@ -47,7 +59,9 @@ RSpec.describe AbuseReportsController, feature_category: :users do context 'when user is reported for abuse' do let(:ref_url) { 'http://example.com' } - let(:request_params) { { user_id: user.id, abuse_report: { category: abuse_category }, ref_url: ref_url } } + let(:request_params) do + { user_id: user.id, abuse_report: { category: abuse_category, reported_from_url: ref_url } } + end it 'renders new template' do subject @@ -62,9 +76,9 @@ RSpec.describe AbuseReportsController, feature_category: :users do expect(assigns(:abuse_report)).to be_kind_of(AbuseReport) expect(assigns(:abuse_report)).to have_attributes( user_id: user.id, - category: abuse_category + category: abuse_category, + reported_from_url: ref_url ) - expect(assigns(:ref_url)).to eq(ref_url) end end diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index 35909906e45..767f3e8b5b5 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -453,16 +453,10 @@ RSpec.describe API::Internal::Base, feature_category: :authentication_and_author expect(json_response['message']['error']).to eq('This endpoint has been requested too many times. Try again later.') end - context 'when rate_limit_gitlab_shell feature flag is disabled' do - before do - stub_feature_flags(rate_limit_gitlab_shell: false) - end - - it 'is not throttled by rate limiter' do - expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?) + it 'is not throttled by rate limiter' do + expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?) - subject - end + subject end context 'when the IP is in a trusted range' do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 6e8168c0ee1..4f7fad0bc69 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1169,7 +1169,7 @@ RSpec.describe API::Projects do expect(response).to have_gitlab_http_status(:bad_request) end - it "assigns attributes to project", :aggregate_failures do + it 'assigns attributes to project', :aggregate_failures do project = attributes_for(:project, { path: 'camelCasePath', issues_enabled: false, @@ -1198,6 +1198,11 @@ RSpec.describe API::Projects do attrs[:feature_flags_access_level] = 'disabled' attrs[:infrastructure_access_level] = 'disabled' attrs[:monitor_access_level] = 'disabled' + attrs[:snippets_access_level] = 'disabled' + attrs[:wiki_access_level] = 'disabled' + attrs[:builds_access_level] = 'disabled' + attrs[:merge_requests_access_level] = 'disabled' + attrs[:issues_access_level] = 'disabled' end post api('/projects', user), params: project @@ -1228,6 +1233,11 @@ RSpec.describe API::Projects do expect(project.project_feature.feature_flags_access_level).to eq(ProjectFeature::DISABLED) expect(project.project_feature.infrastructure_access_level).to eq(ProjectFeature::DISABLED) expect(project.project_feature.monitor_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.builds_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED) + expect(project.project_feature.snippets_access_level).to eq(ProjectFeature::DISABLED) end it 'assigns container_registry_enabled to project', :aggregate_failures do diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 393ada1da4f..555ba2bc978 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -353,15 +353,9 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do expect(response).to have_gitlab_http_status(:too_many_requests) end - context "when hotlinking detection is enabled" do - before do - stub_feature_flags(repository_archive_hotlinking_interception: true) - end - - it_behaves_like "hotlink interceptor" do - let(:http_request) do - get api(route, current_user), headers: headers - end + it_behaves_like "hotlink interceptor" do + let(:http_request) do + get api(route, current_user), headers: headers end end end diff --git a/spec/support/shared_examples/features/reportable_note_shared_examples.rb b/spec/support/shared_examples/features/reportable_note_shared_examples.rb index c35f711111b..9d859403465 100644 --- a/spec/support/shared_examples/features/reportable_note_shared_examples.rb +++ b/spec/support/shared_examples/features/reportable_note_shared_examples.rb @@ -36,7 +36,7 @@ RSpec.shared_examples 'reportable note' do |type| dropdown.click_link('Report abuse to administrator') expect(find('#user_name')['value']).to match(note.author.username) - expect(find('#abuse_report_message')['value']).to match(noteable_note_url(note)) + expect(find('#abuse_report_reported_from_url')['value']).to match(noteable_note_url(note)) end def open_dropdown(dropdown) |