diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-01 00:09:49 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-01 00:09:49 +0300 |
commit | 9877050db1dd1693c672a6b29a356c5b2a7edce0 (patch) | |
tree | 07e8b3837333294337121b96f296eb5622a8a7e9 /spec | |
parent | 370736438075748c36abd7fd7dd32a8ef98048f9 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
45 files changed, 1437 insertions, 243 deletions
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb index cf0e481495c..475e7d3229c 100644 --- a/spec/controllers/projects/error_tracking_controller_spec.rb +++ b/spec/controllers/projects/error_tracking_controller_spec.rb @@ -307,6 +307,18 @@ RSpec.describe Projects::ErrorTrackingController do end describe 'format json' do + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it 'returns 404 error' do + update_issue + + expect(response).to have_gitlab_http_status(:not_found) + end + end + context 'when update result is successful' do before do allow(issue_update_service).to receive(:execute) diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 2d8454988d9..8070e17b7af 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -70,6 +70,7 @@ RSpec.describe 'Database schema' do oauth_applications: %w[owner_id], product_analytics_events_experimental: %w[event_id txn_id user_id], project_build_artifacts_size_refreshes: %w[last_job_artifact_id], + project_error_tracking_settings: %w[sentry_project_id], project_group_links: %w[group_id], project_statistics: %w[namespace_id], projects: %w[creator_id ci_id mirror_user_id], diff --git a/spec/factories/error_tracking/error.rb b/spec/factories/error_tracking/error.rb index bebdffb3614..62d16244122 100644 --- a/spec/factories/error_tracking/error.rb +++ b/spec/factories/error_tracking/error.rb @@ -13,7 +13,7 @@ FactoryBot.define do message { 'message' } culprit { 'culprit' } external_url { 'http://example.com/id' } - project_id { 'project1' } + project_id { '111111' } project_name { 'project name' } project_slug { 'project_name' } short_id { 'ID' } diff --git a/spec/factories/project_error_tracking_settings.rb b/spec/factories/project_error_tracking_settings.rb index ed743d8283c..a8ad1af6345 100644 --- a/spec/factories/project_error_tracking_settings.rb +++ b/spec/factories/project_error_tracking_settings.rb @@ -9,6 +9,7 @@ FactoryBot.define do project_name { 'Sentry Project' } organization_name { 'Sentry Org' } integrated { false } + sentry_project_id { 10 } trait :disabled do enabled { false } diff --git a/spec/finders/ci/runner_jobs_finder_spec.rb b/spec/finders/ci/runner_jobs_finder_spec.rb index 3569582d70f..755b21ec08f 100644 --- a/spec/finders/ci/runner_jobs_finder_spec.rb +++ b/spec/finders/ci/runner_jobs_finder_spec.rb @@ -5,12 +5,17 @@ require 'spec_helper' RSpec.describe Ci::RunnerJobsFinder do let(:project) { create(:project) } let(:runner) { create(:ci_runner, :instance) } + let(:user) { create(:user) } + let(:params) { {} } - subject { described_class.new(runner, params).execute } + subject { described_class.new(runner, user, params).execute } + + before do + project.add_developer(user) + end describe '#execute' do context 'when params is empty' do - let(:params) { {} } let!(:job) { create(:ci_build, runner: runner, project: project) } let!(:job1) { create(:ci_build, project: project) } @@ -20,6 +25,50 @@ RSpec.describe Ci::RunnerJobsFinder do end end + context 'when the user has guest access' do + it 'does not returns jobs the user does not have permission to see' do + another_project = create(:project) + job = create(:ci_build, runner: runner, project: another_project) + + another_project.add_guest(user) + + is_expected.not_to match_array(job) + end + end + + context 'when the user has permission to read all resources' do + let(:user) { create(:user, :admin) } + + it 'returns all the jobs assigned to a runner' do + jobs = create_list(:ci_build, 5, runner: runner, project: project) + + is_expected.to match_array(jobs) + end + end + + context 'when the user has different access levels in different projects' do + it 'returns only the jobs the user has permission to see' do + guest_project = create(:project) + reporter_project = create(:project) + + _guest_jobs = create_list(:ci_build, 2, runner: runner, project: guest_project) + reporter_jobs = create_list(:ci_build, 3, runner: runner, project: reporter_project) + + guest_project.add_guest(user) + reporter_project.add_reporter(user) + + is_expected.to match_array(reporter_jobs) + end + end + + context 'when the user has reporter access level or greater' do + it 'returns jobs assigned to the Runner that the user has accesss to' do + jobs = create_list(:ci_build, 3, runner: runner, project: project) + + is_expected.to match_array(jobs) + end + end + context 'when params contains status' do Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status| context "when status is #{target_status}" do diff --git a/spec/finders/packages/conan/package_finder_spec.rb b/spec/finders/packages/conan/package_finder_spec.rb index b26f8900090..6848786818b 100644 --- a/spec/finders/packages/conan/package_finder_spec.rb +++ b/spec/finders/packages/conan/package_finder_spec.rb @@ -2,22 +2,53 @@ require 'spec_helper' RSpec.describe ::Packages::Conan::PackageFinder do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:project) { create(:project) } let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :public) } + let_it_be(:private_project) { create(:project, :private) } + + let_it_be(:conan_package) { create(:conan_package, project: project) } + let_it_be(:conan_package2) { create(:conan_package, project: project) } + let_it_be(:errored_package) { create(:conan_package, :error, project: project) } + let_it_be(:private_package) { create(:conan_package, project: private_project) } describe '#execute' do - let!(:conan_package) { create(:conan_package, project: project) } - let!(:conan_package2) { create(:conan_package, project: project) } + let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" } + let(:finder) { described_class.new(user, query: query) } + + subject { finder.execute } + + where(:visibility, :role, :packages_visible) do + :private | :maintainer | true + :private | :developer | true + :private | :reporter | true + :private | :guest | false + :private | :anonymous | false + + :internal | :maintainer | true + :internal | :developer | true + :internal | :reporter | true + :internal | :guest | true + :internal | :anonymous | false + + :public | :maintainer | true + :public | :developer | true + :public | :reporter | true + :public | :guest | true + :public | :anonymous | true + end - subject { described_class.new(user, query: query).execute } + with_them do + let(:expected_packages) { packages_visible ? [conan_package, conan_package2] : [] } + let(:user) { role == :anonymous ? nil : super() } - context 'packages that are not installable' do - let!(:conan_package3) { create(:conan_package, :error, project: project) } - let!(:non_visible_project) { create(:project, :private) } - let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) } - let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" } + before do + project.update_column(:visibility_level, Gitlab::VisibilityLevel.string_options[visibility.to_s]) + project.add_user(user, role) unless role == :anonymous + end - it { is_expected.to eq [conan_package, conan_package2] } + it { is_expected.to eq(expected_packages) } end end end diff --git a/spec/fixtures/api/schemas/entities/member.json b/spec/fixtures/api/schemas/entities/member.json index dec98123e85..88f7d87b269 100644 --- a/spec/fixtures/api/schemas/entities/member.json +++ b/spec/fixtures/api/schemas/entities/member.json @@ -53,7 +53,7 @@ }, "user": { "allOf": [ - { "$ref": "member_user.json" } + { "$ref": "member_user_default.json" } ] }, "state": { "type": "integer" }, diff --git a/spec/fixtures/api/schemas/entities/member_user_default.json b/spec/fixtures/api/schemas/entities/member_user_default.json new file mode 100644 index 00000000000..e0b3dba5699 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/member_user_default.json @@ -0,0 +1,35 @@ +{ + "type": "object", + "required": [ + "id", + "name", + "username", + "created_at", + "last_activity_on", + "avatar_url", + "web_url", + "blocked", + "show_status" + ], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "username": { "type": "string" }, + "created_at": { "type": ["string"] }, + "avatar_url": { "type": ["string", "null"] }, + "web_url": { "type": "string" }, + "blocked": { "type": "boolean" }, + "two_factor_enabled": { "type": "boolean" }, + "availability": { "type": ["string", "null"] }, + "last_activity_on": { "type": ["string", "null"] }, + "status": { + "type": "object", + "required": ["emoji"], + "properties": { + "emoji": { "type": "string" } + }, + "additionalProperties": false + }, + "show_status": { "type": "boolean" } + } +} diff --git a/spec/fixtures/api/schemas/entities/member_user.json b/spec/fixtures/api/schemas/entities/member_user_for_admin_member.json index 0750e81e115..0750e81e115 100644 --- a/spec/fixtures/api/schemas/entities/member_user.json +++ b/spec/fixtures/api/schemas/entities/member_user_for_admin_member.json diff --git a/spec/frontend/notebook/cells/markdown_spec.js b/spec/frontend/notebook/cells/markdown_spec.js index de415b5bfe0..c757b55faf4 100644 --- a/spec/frontend/notebook/cells/markdown_spec.js +++ b/spec/frontend/notebook/cells/markdown_spec.js @@ -130,7 +130,7 @@ describe('Markdown component', () => { expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64'); expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64'); expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64'); - expect(columns[3].innerHTML).toContain('<img>'); + expect(columns[3].innerHTML).toContain('<img src="attachment:bogus">'); expect(columns[4].innerHTML).toContain('<img src="https://www.google.com/'); }); }); diff --git a/spec/frontend/projects/settings/access_dropdown_spec.js b/spec/frontend/projects/settings/access_dropdown_spec.js index 65b01172e7e..d51360a7597 100644 --- a/spec/frontend/projects/settings/access_dropdown_spec.js +++ b/spec/frontend/projects/settings/access_dropdown_spec.js @@ -159,4 +159,21 @@ describe('AccessDropdown', () => { expect(template).not.toContain(user.name); }); }); + + describe('deployKeyRowHtml', () => { + const deployKey = { + id: 1, + title: 'title <script>alert(document.domain)</script>', + fullname: 'fullname <script>alert(document.domain)</script>', + avatar_url: '', + username: '', + }; + + it('escapes deploy key title and fullname', () => { + const template = dropdown.deployKeyRowHtml(deployKey); + + expect(template).not.toContain(deployKey.title); + expect(template).not.toContain(deployKey.fullname); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js index 74f50b878e2..ee4d3a2630a 100644 --- a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js +++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js @@ -1,57 +1,30 @@ -import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; +import { GlDropdown } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants'; import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue'; import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue'; +import DropdownHeader from '~/vue_shared/components/color_select_dropdown/dropdown_header.vue'; import { color } from './mock_data'; -const showDropdown = jest.fn(); -const focusInput = jest.fn(); - const defaultProps = { dropdownTitle: '', selectedColor: color, - dropdownButtonText: '', + dropdownButtonText: 'Pick a color', variant: '', isVisible: false, }; -const GlDropdownStub = { - template: ` - <div> - <slot name="header"></slot> - <slot></slot> - </div> - `, - methods: { - show: showDropdown, - hide: jest.fn(), - }, -}; - -const DropdownHeaderStub = { - template: ` - <div>Hello, I am a header</div> - `, - methods: { - focusInput, - }, -}; - describe('DropdownContent', () => { let wrapper; const createComponent = ({ propsData = {} } = {}) => { - wrapper = shallowMount(DropdownContents, { + wrapper = mountExtended(DropdownContents, { propsData: { ...defaultProps, ...propsData, }, - stubs: { - GlDropdown: GlDropdownStub, - DropdownHeader: DropdownHeaderStub, - }, }); }; @@ -60,16 +33,17 @@ describe('DropdownContent', () => { }); const findColorView = () => wrapper.findComponent(DropdownContentsColorView); - const findDropdownHeader = () => wrapper.findComponent(DropdownHeaderStub); - const findDropdown = () => wrapper.findComponent(GlDropdownStub); + const findDropdownHeader = () => wrapper.findComponent(DropdownHeader); + const findDropdown = () => wrapper.findComponent(GlDropdown); it('calls dropdown `show` method on `isVisible` prop change', async () => { createComponent(); + const spy = jest.spyOn(wrapper.vm.$refs.dropdown, 'show'); await wrapper.setProps({ isVisible: true, }); - expect(showDropdown).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledTimes(1); }); it('does not emit `setColor` event on dropdown hide if color did not change', () => { @@ -110,4 +84,12 @@ describe('DropdownContent', () => { expect(findDropdownHeader().exists()).toBe(true); }); + + it('handles no selected color', () => { + createComponent({ propsData: { selectedColor: {} } }); + + expect(wrapper.findByTestId('fallback-button-text').text()).toEqual( + defaultProps.dropdownButtonText, + ); + }); }); diff --git a/spec/graphql/resolvers/ci/config_resolver_spec.rb b/spec/graphql/resolvers/ci/config_resolver_spec.rb index 7a6104fc503..dc030b1313b 100644 --- a/spec/graphql/resolvers/ci/config_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/config_resolver_spec.rb @@ -7,24 +7,13 @@ RSpec.describe Resolvers::Ci::ConfigResolver do describe '#resolve' do let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } + let_it_be(:project) { create(:project, :repository) } let_it_be(:sha) { nil } let_it_be(:content) do File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml')) end - let(:ci_lint) do - ci_lint_double = instance_double(::Gitlab::Ci::Lint) - allow(ci_lint_double).to receive(:validate).and_return(fake_result) - - ci_lint_double - end - - before do - allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint) - end - subject(:response) do resolve(described_class, args: { project_path: project.full_path, content: content, sha: sha }, @@ -51,52 +40,77 @@ RSpec.describe Resolvers::Ci::ConfigResolver do end end - context 'with a valid .gitlab-ci.yml' do - context 'with a sha' do - let(:sha) { '1231231' } + context 'when the user can create a pipeline' do + let(:ci_lint) do + ci_lint_double = instance_double(::Gitlab::Ci::Lint) + allow(ci_lint_double).to receive(:validate).and_return(fake_result) - it_behaves_like 'a valid config file' + ci_lint_double end - context 'without a sha' do - it_behaves_like 'a valid config file' + before do + allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint) + + project.add_developer(user) end - end - context 'with an invalid .gitlab-ci.yml' do - let(:content) { 'invalid' } + context 'with a valid .gitlab-ci.yml' do + context 'with a sha' do + let(:sha) { '1231231' } - let(:fake_result) do - Gitlab::Ci::Lint::Result.new( - jobs: [], - merged_yaml: content, - errors: ['Invalid configuration format'], - warnings: [], - includes: [] - ) + it_behaves_like 'a valid config file' + end + + context 'without a sha' do + it_behaves_like 'a valid config file' + end end - it 'responds with errors about invalid syntax' do - expect(response[:status]).to eq(:invalid) - expect(response[:errors]).to eq(['Invalid configuration format']) + context 'with an invalid .gitlab-ci.yml' do + let(:content) { 'invalid' } + + let(:fake_result) do + Gitlab::Ci::Lint::Result.new( + jobs: [], + merged_yaml: content, + errors: ['Invalid configuration format'], + warnings: [], + includes: [] + ) + end + + it 'responds with errors about invalid syntax' do + expect(response[:status]).to eq(:invalid) + expect(response[:errors]).to match_array(['Invalid configuration format']) + end end - end - context 'with an invalid SHA' do - let_it_be(:sha) { ':' } + context 'with an invalid SHA' do + let_it_be(:sha) { ':' } - let(:ci_lint) do - ci_lint_double = instance_double(::Gitlab::Ci::Lint) - allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument) + let(:ci_lint) do + ci_lint_double = instance_double(::Gitlab::Ci::Lint) + allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument) - ci_lint_double + ci_lint_double + end + + it 'logs the invalid SHA to Sentry' do + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception) + .with(GRPC::InvalidArgument, sha: ':') + + response + end end + end - it 'logs the invalid SHA to Sentry' do - expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception) - .with(GRPC::InvalidArgument, sha: ':') + context 'when the user cannot create a pipeline' do + before do + project.add_guest(user) + end - response + it 'returns an error stating that the user cannot access the linting' do + expect(response).to be_instance_of(::Gitlab::Graphql::Errors::ResourceNotAvailable) end end end diff --git a/spec/helpers/integrations_helper_spec.rb b/spec/helpers/integrations_helper_spec.rb index dccbc110be6..95dfc51e8fd 100644 --- a/spec/helpers/integrations_helper_spec.rb +++ b/spec/helpers/integrations_helper_spec.rb @@ -150,19 +150,4 @@ RSpec.describe IntegrationsHelper do end end end - - describe '#jira_issue_breadcrumb_link' do - let(:issue_reference) { nil } - - subject { helper.jira_issue_breadcrumb_link(issue_reference) } - - context 'when issue_reference contains HTML' do - let(:issue_reference) { "<script>alert('XSS')</script>" } - - it 'escapes issue reference' do - is_expected.not_to include(issue_reference) - is_expected.to include(html_escape(issue_reference)) - end - end - end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index e0c98bbc161..4502729866c 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -52,6 +52,7 @@ RSpec.describe ProjectsHelper do context 'api_url present' do let(:json) do { + sentry_project_id: error_tracking_setting.sentry_project_id, name: error_tracking_setting.project_name, organization_name: error_tracking_setting.organization_name, organization_slug: error_tracking_setting.organization_slug, diff --git a/spec/helpers/timeboxes_helper_spec.rb b/spec/helpers/timeboxes_helper_spec.rb index e31f2df7372..f9fb40a616b 100644 --- a/spec/helpers/timeboxes_helper_spec.rb +++ b/spec/helpers/timeboxes_helper_spec.rb @@ -38,4 +38,23 @@ RSpec.describe TimeboxesHelper do end end end + + describe "#recent_releases_with_counts" do + let_it_be(:milestone) { create(:milestone) } + let_it_be(:project) { milestone.project } + let_it_be(:user) { create(:user) } + + subject { helper.recent_releases_with_counts(milestone, user) } + + before do + project.add_developer(user) + end + + it "returns releases with counts" do + _old_releases = create_list(:release, 2, project: project, milestones: [milestone]) + recent_public_releases = create_list(:release, 3, project: project, milestones: [milestone], released_at: '2022-01-01T18:00:00Z') + + is_expected.to match([match_array(recent_public_releases), 5, 2]) + end + end end diff --git a/spec/initializers/net_http_response_patch_spec.rb b/spec/initializers/net_http_response_patch_spec.rb new file mode 100644 index 00000000000..3bd0d8c3907 --- /dev/null +++ b/spec/initializers/net_http_response_patch_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Net::HTTPResponse patch header read timeout' do + describe '.each_response_header' do + let(:server_response) do + <<~EOS + Content-Type: text/html + Header-Two: foo + + Hello World + EOS + end + + before do + stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1) + end + + subject(:each_response_header) { Net::HTTPResponse.each_response_header(socket) { |k, v| } } + + context 'with Net::BufferedIO' do + let(:socket) { Net::BufferedIO.new(StringIO.new(server_response)) } + + it 'does not forward start time to the socket' do + allow(socket).to receive(:readuntil).and_call_original + expect(socket).to receive(:readuntil).with("\n", true) + + each_response_header + end + + context 'when the response contains many consecutive spaces' do + before do + expect(socket).to receive(:readuntil).and_return( + "a: #{' ' * 100_000} b", + '' + ) + end + + it 'has no regex backtracking issues' do + Timeout.timeout(1) do + each_response_header + end + end + end + end + + context 'with Gitlab::BufferedIo' do + let(:mock_io) { StringIO.new(server_response) } + let(:socket) { Gitlab::BufferedIo.new(mock_io) } + + it 'forwards start time to the socket' do + allow(socket).to receive(:readuntil).and_call_original + expect(socket).to receive(:readuntil).with("\n", true, kind_of(Numeric)) + + each_response_header + end + + context 'when the response contains an infinite number of headers' do + before do + read_counter = 0 + + allow(mock_io).to receive(:read_nonblock) do + read_counter += 1 + raise 'Test did not raise HeaderReadTimeout' if read_counter > 10 + + sleep 0.01 + +"Yet-Another-Header: foo\n" + end + end + + it 'raises a timeout error' do + expect { each_response_header }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, + /Request timed out after reading headers for 0\.[0-9]+ seconds/) + end + end + end + end +end diff --git a/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb index c53c0849931..567a0a4fcc3 100644 --- a/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb @@ -25,18 +25,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do let(:project_data) do { 'visibility' => 'private', - 'created_at' => 10.days.ago, - 'archived' => false, - 'shared_runners_enabled' => true, - 'container_registry_enabled' => true, - 'only_allow_merge_if_pipeline_succeeds' => true, - 'only_allow_merge_if_all_discussions_are_resolved' => true, - 'request_access_enabled' => true, - 'printing_merge_request_link_enabled' => true, - 'remove_source_branch_after_merge' => true, - 'autoclose_referenced_issues' => true, - 'suggestion_commit_message' => 'message', - 'wiki_enabled' => true + 'created_at' => '2016-08-12T09:41:03' } end @@ -58,17 +47,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do expect(imported_project).not_to be_nil expect(imported_project.group).to eq(group) - expect(imported_project.suggestion_commit_message).to eq('message') - expect(imported_project.archived?).to eq(project_data['archived']) - expect(imported_project.shared_runners_enabled?).to eq(project_data['shared_runners_enabled']) - expect(imported_project.container_registry_enabled?).to eq(project_data['container_registry_enabled']) - expect(imported_project.only_allow_merge_if_pipeline_succeeds?).to eq(project_data['only_allow_merge_if_pipeline_succeeds']) - expect(imported_project.only_allow_merge_if_all_discussions_are_resolved?).to eq(project_data['only_allow_merge_if_all_discussions_are_resolved']) - expect(imported_project.request_access_enabled?).to eq(project_data['request_access_enabled']) - expect(imported_project.printing_merge_request_link_enabled?).to eq(project_data['printing_merge_request_link_enabled']) - expect(imported_project.remove_source_branch_after_merge?).to eq(project_data['remove_source_branch_after_merge']) - expect(imported_project.autoclose_referenced_issues?).to eq(project_data['autoclose_referenced_issues']) - expect(imported_project.wiki_enabled?).to eq(project_data['wiki_enabled']) + expect(imported_project.visibility).to eq(project_data['visibility']) + expect(imported_project.created_at).to eq(project_data['created_at']) end end diff --git a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb index 822bb9a5605..a1d77b9732d 100644 --- a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb +++ b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb @@ -25,8 +25,8 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer let(:data) do { - 'name' => 'source_name', - 'visibility' => 'private' + 'visibility' => 'private', + 'created_at' => '2016-11-18T09:29:42.634Z' } end @@ -76,8 +76,21 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer end end - it 'converts all keys to symbols' do - expect(transformed_data.keys).to contain_exactly(:name, :path, :import_type, :visibility_level, :namespace_id) + context 'when data has extra keys' do + it 'returns a fixed number of keys' do + data = { + 'visibility' => 'private', + 'created_at' => '2016-11-18T09:29:42.634Z', + 'my_key' => 'my_key', + 'another_key' => 'another_key', + 'last_key' => 'last_key' + } + + transformed_data = described_class.new.transform(context, data) + + expect(transformed_data.keys) + .to contain_exactly(:created_at, :import_type, :name, :namespace_id, :path, :visibility_level) + end end end end diff --git a/spec/lib/gitlab/buffered_io_spec.rb b/spec/lib/gitlab/buffered_io_spec.rb index f8896abd46e..c6939b819e2 100644 --- a/spec/lib/gitlab/buffered_io_spec.rb +++ b/spec/lib/gitlab/buffered_io_spec.rb @@ -1,54 +1,50 @@ -# rubocop:disable Style/FrozenStringLiteralComment +# frozen_string_literal: true + require 'spec_helper' RSpec.describe Gitlab::BufferedIo do describe '#readuntil' do - let(:never_ending_tcp_socket) do - Class.new do - def initialize(*_) - @read_counter = 0 - end + let(:mock_io) { StringIO.new('a') } + let(:start_time) { Process.clock_gettime(Process::CLOCK_MONOTONIC) } - def setsockopt(*_); end + before do + stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1) + end - def closed? - false - end + subject(:readuntil) do + Gitlab::BufferedIo.new(mock_io).readuntil('a', false, start_time) + end - def close - true - end + it 'does not raise a timeout error' do + expect { readuntil }.not_to raise_error + end - def to_io - StringIO.new('Hello World!') - end + context 'when the response contains infinitely long headers' do + before do + read_counter = 0 - def write_nonblock(data, *_) - data.size - end + allow(mock_io).to receive(:read_nonblock) do |buffer_size, *_| + read_counter += 1 + raise 'Test did not raise HeaderReadTimeout' if read_counter > 10 - def read_nonblock(buffer_size, *_) sleep 0.01 - @read_counter += 1 - - raise 'Test did not raise HeaderReadTimeout' if @read_counter > 10 - 'H' * buffer_size end end - end - before do - stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1) - end + it 'raises a timeout error' do + expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/) + end - subject(:readuntil) do - Gitlab::BufferedIo.new(never_ending_tcp_socket.new).readuntil('a') - end + context 'when not passing start_time' do + subject(:readuntil) do + Gitlab::BufferedIo.new(mock_io).readuntil('a', false) + end - it 'raises a timeout error' do - expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/) + it 'raises a timeout error' do + expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/) + end + end end end end -# rubocop:enable Style/FrozenStringLiteralComment diff --git a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb index 3953a1aa81a..fd3218d7d84 100644 --- a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb +++ b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb @@ -21,7 +21,9 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do end context 'with available_runner_releases configured up to 14.1.1' do - let(:available_runner_releases) { %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2 14.1.0 14.1.1] } + let(:available_runner_releases) do + %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2-rc1 14.0.2 14.1.0 14.1.1] + end context 'with nil runner_version' do let(:runner_version) { nil } @@ -62,10 +64,11 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do 'v14.1.0/1.1.0' | :recommended # suffixes are correctly handled 'v14.1.0' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes 'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available + 'v14.0.2-rc1' | :recommended # recommended upgrade since 14.0.2 is available and we'll move out of a release candidate 'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version 'v13.10.1' | :available # available upgrade: 14.1.1 - 'v13.10.1~beta.1574.gf6ea9389' | :available # suffixes are correctly handled - 'v13.10.1/1.1.0' | :available # suffixes are correctly handled + 'v13.10.1~beta.1574.gf6ea9389' | :recommended # suffixes are correctly handled, official 13.10.1 is available + 'v13.10.1/1.1.0' | :recommended # suffixes are correctly handled, official 13.10.1 is available 'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available 'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version 'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version diff --git a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb new file mode 100644 index 00000000000..116917d3e06 --- /dev/null +++ b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do + subject(:importer) { described_class.new(project, user.id) } + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + + let(:issue) { create(:issue, project: project) } + let(:commit_id) { nil } + + let(:issue_event) do + Gitlab::GithubImport::Representation::IssueEvent.from_json_hash( + 'id' => 6501124486, + 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG', + 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486', + 'actor' => { 'id' => 4, 'login' => 'alice' }, + 'event' => 'closed', + 'created_at' => '2022-04-26 18:30:53 UTC', + 'commit_id' => commit_id, + 'issue_db_id' => issue.id + ) + end + + let(:expected_event_attrs) do + { + project_id: project.id, + author_id: user.id, + target_id: issue.id, + target_type: Issue.name, + action: 'closed', + created_at: issue_event.created_at, + updated_at: issue_event.created_at + }.stringify_keys + end + + let(:expected_state_event_attrs) do + { + user_id: user.id, + issue_id: issue.id, + state: 'closed', + created_at: issue_event.created_at + }.stringify_keys + end + + it 'creates expected event and state event' do + importer.execute(issue_event) + + expect(issue.events.count).to eq 1 + expect(issue.events[0].attributes) + .to include expected_event_attrs + + expect(issue.resource_state_events.count).to eq 1 + expect(issue.resource_state_events[0].attributes) + .to include expected_state_event_attrs + end + + context 'when closed by commit' do + let!(:closing_commit) { create(:commit, project: project) } + let(:commit_id) { closing_commit.id } + + it 'creates expected event and state event' do + importer.execute(issue_event) + + expect(issue.events.count).to eq 1 + state_event = issue.resource_state_events.last + expect(state_event.source_commit).to eq commit_id[0..40] + end + end +end diff --git a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb new file mode 100644 index 00000000000..03a80c11f96 --- /dev/null +++ b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab_redis_cache do + let(:importer) { described_class.new(issue_event, project, client) } + + let(:project) { create(:project) } + let(:client) { instance_double('Gitlab::GithubImport::Client') } + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project) } + + let(:issue_event) do + Gitlab::GithubImport::Representation::IssueEvent.from_json_hash( + 'id' => 6501124486, + 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG', + 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486', + 'actor' => { 'id' => actor_id, 'login' => 'alice' }, + 'event' => event_name, + 'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d', + 'commit_url' => + 'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d', + 'created_at' => '2022-04-26 18:30:53 UTC', + 'performed_via_github_app' => nil + ) + end + + let(:actor_id) { user.id } + let(:event_name) { 'closed' } + + shared_examples 'triggers specific event importer' do |importer_class| + it importer_class.name do + specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles + + expect(importer_class) + .to receive(:new).with(project, user.id) + .and_return(specific_importer) + expect(specific_importer).to receive(:execute).with(issue_event) + + importer.execute + end + end + + describe '#execute' do + before do + allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder| + allow(finder).to receive(:author_id_for) + .with(issue_event, author_key: :actor) + .and_return(user.id, true) + end + + issue_event.attributes[:issue_db_id] = issue.id + end + + context "when it's closed issue event" do + let(:event_name) { 'closed' } + + it_behaves_like 'triggers specific event importer', + Gitlab::GithubImport::Importer::Events::Closed + end + + context "when it's unknown issue event" do + let(:event_name) { 'fake' } + + it 'logs warning and skips' do + expect(Gitlab::GithubImport::Logger).to receive(:debug) + .with( + message: 'UNSUPPORTED_EVENT_TYPE', + event_type: issue_event.event, + event_github_id: issue_event.id + ) + + importer.execute + end + end + end +end diff --git a/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb new file mode 100644 index 00000000000..087faeffe02 --- /dev/null +++ b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter do + let(:client) { double } + + let_it_be(:project) { create(:project, :import_started, import_source: 'http://somegithub.com') } + let_it_be(:issue) { create(:issue, project: project) } + + subject { described_class.new(project, client, parallel: parallel) } + + let(:parallel) { true } + + it { is_expected.to include_module(Gitlab::GithubImport::ParallelScheduling) } + + describe '#importer_class' do + it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::IssueEventImporter) } + end + + describe '#representation_class' do + it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::IssueEvent) } + end + + describe '#sidekiq_worker_class' do + it { expect(subject.sidekiq_worker_class).to eq(Gitlab::GithubImport::ImportIssueEventWorker) } + end + + describe '#object_type' do + it { expect(subject.object_type).to eq(:issue_event) } + end + + describe '#collection_method' do + it { expect(subject.collection_method).to eq(:issue_timeline) } + end + + describe '#page_counter_id' do + it { expect(subject.page_counter_id(issue)).to eq("issues/#{issue.iid}/issue_timeline") } + end + + describe '#id_for_already_imported_cache' do + let(:event) { instance_double('Event', id: 1) } + + it { expect(subject.id_for_already_imported_cache(event)).to eq(1) } + end + + describe '#collection_options' do + it do + expect(subject.collection_options) + .to eq({ state: 'all', sort: 'created', direction: 'asc' }) + end + end + + describe '#each_object_to_import', :clean_gitlab_redis_cache do + let(:issue_event) do + struct = Struct.new(:id, :event, :created_at, :issue_db_id, keyword_init: true) + struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC') + end + + let(:page) do + instance_double( + Gitlab::GithubImport::Client::Page, + number: 1, objects: [issue_event] + ) + end + + let(:page_counter) { instance_double(Gitlab::GithubImport::PageCounter) } + + before do + allow(client).to receive(:each_page) + .once + .with( + :issue_timeline, + project.import_source, + issue.iid, + { state: 'all', sort: 'created', direction: 'asc', page: 1 } + ).and_yield(page) + end + + it 'imports each issue event page by page' do + counter = 0 + subject.each_object_to_import do |object| + expect(object).to eq issue_event + expect(issue_event.issue_db_id).to eq issue.id + counter += 1 + end + expect(counter).to eq 1 + end + + it 'triggers page number increment' do + expect(Gitlab::GithubImport::PageCounter) + .to receive(:new).with(project, 'issues/1/issue_timeline') + .and_return(page_counter) + expect(page_counter).to receive(:current).and_return(1) + expect(page_counter) + .to receive(:set).with(page.number).and_return(true) + + counter = 0 + subject.each_object_to_import { counter += 1 } + expect(counter).to eq 1 + end + + context 'when page is already processed' do + before do + page_counter = Gitlab::GithubImport::PageCounter.new( + project, subject.page_counter_id(issue) + ) + page_counter.set(page.number) + end + + it "doesn't process this page" do + counter = 0 + subject.each_object_to_import { counter += 1 } + expect(counter).to eq 0 + end + end + + context 'when event is already processed' do + it "doesn't process this event" do + subject.mark_as_imported(issue_event) + + counter = 0 + subject.each_object_to_import { counter += 1 } + expect(counter).to eq 0 + end + end + end +end diff --git a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb new file mode 100644 index 00000000000..412e1aefc19 --- /dev/null +++ b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do + shared_examples 'an IssueEvent' do + it 'returns an instance of IssueEvent' do + expect(issue_event).to be_an_instance_of(described_class) + end + + context 'the returned IssueEvent' do + it 'includes the issue event id' do + expect(issue_event.id).to eq(6501124486) + end + + it 'includes the issue event "event"' do + expect(issue_event.event).to eq('closed') + end + + it 'includes the issue event commit_id' do + expect(issue_event.commit_id).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') + end + + it 'includes the issue_db_id' do + expect(issue_event.issue_db_id).to eq(100500) + end + + context 'when actor data present' do + it 'includes the actor details' do + expect(issue_event.actor) + .to be_an_instance_of(Gitlab::GithubImport::Representation::User) + + expect(issue_event.actor.id).to eq(4) + expect(issue_event.actor.login).to eq('alice') + end + end + + context 'when actor data is empty' do + let(:with_actor) { false } + + it 'does not return such info' do + expect(issue_event.actor).to eq nil + end + end + + it 'includes the created timestamp' do + expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC') + end + end + + describe '#github_identifiers' do + it 'returns a hash with needed identifiers' do + expect(issue_event.github_identifiers).to eq({ id: 6501124486 }) + end + end + end + + describe '.from_api_response' do + let(:response) do + event_resource = Struct.new( + :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, + :issue_db_id, :created_at, :performed_via_github_app, + keyword_init: true + ) + user_resource = Struct.new(:id, :login, keyword_init: true) + event_resource.new( + id: 6501124486, + node_id: 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG', + url: 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486', + actor: with_actor ? user_resource.new(id: 4, login: 'alice') : nil, + event: 'closed', + commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d', + commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\ + '/570e7b2abdd848b95f2f578043fc23bd6f6fd24d', + issue_db_id: 100500, + created_at: '2022-04-26 18:30:53 UTC', + performed_via_github_app: nil + ) + end + + let(:with_actor) { true } + + it_behaves_like 'an IssueEvent' do + let(:issue_event) { described_class.from_api_response(response) } + end + end + + describe '.from_json_hash' do + it_behaves_like 'an IssueEvent' do + let(:hash) do + { + 'id' => 6501124486, + 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG', + 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486', + 'actor' => (with_actor ? { 'id' => 4, 'login' => 'alice' } : nil), + 'event' => 'closed', + 'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d', + 'commit_url' => + 'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d', + "issue_db_id" => 100500, + 'created_at' => '2022-04-26 18:30:53 UTC', + 'performed_via_github_app' => nil + } + end + + let(:with_actor) { true } + + let(:issue_event) { described_class.from_json_hash(hash) } + end + end +end diff --git a/spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb b/spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb new file mode 100644 index 00000000000..64dbc939348 --- /dev/null +++ b/spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::GithubImport::SingleEndpointNotesImporting do + let(:importer_class) do + Class.new do + def self.name + 'MyImporter' + end + + include(Gitlab::GithubImport::SingleEndpointNotesImporting) + end + end + + let(:importer_instance) { importer_class.new } + + describe '#parent_collection' do + it { expect { importer_instance.parent_collection }.to raise_error(NotImplementedError) } + end + + describe '#parent_imported_cache_key' do + it { expect { importer_instance.parent_imported_cache_key }.to raise_error(NotImplementedError) } + end + + describe '#page_counter_id' do + it { expect { importer_instance.page_counter_id(build(:merge_request)) }.to raise_error(NotImplementedError) } + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 6a49e47e761..af910b08fae 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -420,6 +420,8 @@ project: - zentao_integration # dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417 - dingtalk_integration +# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/640 +- feishu_integration - redmine_integration - youtrack_integration - custom_issue_tracker_integration diff --git a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb index fe3b638d20f..dea584e5019 100644 --- a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb +++ b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb @@ -86,6 +86,65 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do include_examples 'logs raised exception and terminates validator process group' end end + + context 'archive path validation' do + let(:filesize) { nil } + + before do + expect(Gitlab::Import::Logger) + .to receive(:info) + .with( + import_upload_archive_path: filepath, + import_upload_archive_size: filesize, + message: error_message + ) + end + + context 'when archive path is traversed' do + let(:filepath) { '/foo/../bar' } + let(:error_message) { 'Invalid path' } + + it 'returns false' do + expect(subject.valid?).to eq(false) + end + end + + context 'when archive path is not a string' do + let(:filepath) { 123 } + let(:error_message) { 'Archive path is not a string' } + + it 'returns false' do + expect(subject.valid?).to eq(false) + end + end + + context 'which archive path is a symlink' do + let(:filepath) { File.join(Dir.tmpdir, 'symlink') } + let(:error_message) { 'Archive path is a symlink' } + + before do + FileUtils.ln_s(filepath, filepath, force: true) + end + + it 'returns false' do + expect(subject.valid?).to eq(false) + end + end + + context 'when archive path is not a file' do + let(:filepath) { Dir.mktmpdir } + let(:filesize) { File.size(filepath) } + let(:error_message) { 'Archive path is not a file' } + + after do + FileUtils.rm_rf(filepath) + end + + it 'returns false' do + expect(subject.valid?).to eq(false) + end + end + end end def create_compressed_file diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb index a77d51fab88..6ed094f11c8 100644 --- a/spec/lib/gitlab/version_info_spec.rb +++ b/spec/lib/gitlab/version_info_spec.rb @@ -2,28 +2,44 @@ require 'fast_spec_helper' -RSpec.describe 'Gitlab::VersionInfo' do +RSpec.describe Gitlab::VersionInfo do before do - @unknown = Gitlab::VersionInfo.new - @v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1) - @v0_1_0 = Gitlab::VersionInfo.new(0, 1, 0) - @v1_0_0 = Gitlab::VersionInfo.new(1, 0, 0) - @v1_0_1 = Gitlab::VersionInfo.new(1, 0, 1) - @v1_1_0 = Gitlab::VersionInfo.new(1, 1, 0) - @v2_0_0 = Gitlab::VersionInfo.new(2, 0, 0) + @unknown = described_class.new + @v0_0_1 = described_class.new(0, 0, 1) + @v0_1_0 = described_class.new(0, 1, 0) + @v1_0_0 = described_class.new(1, 0, 0) + @v1_0_1 = described_class.new(1, 0, 1) + @v1_0_1_b1 = described_class.new(1, 0, 1, '-b1') + @v1_0_1_rc1 = described_class.new(1, 0, 1, '-rc1') + @v1_0_1_rc2 = described_class.new(1, 0, 1, '-rc2') + @v1_1_0 = described_class.new(1, 1, 0) + @v1_1_0_beta1 = described_class.new(1, 1, 0, '-beta1') + @v2_0_0 = described_class.new(2, 0, 0) + @v13_10_1_1574_89 = described_class.parse("v13.10.1~beta.1574.gf6ea9389", parse_suffix: true) + @v13_10_1_1575_89 = described_class.parse("v13.10.1~beta.1575.gf6ea9389", parse_suffix: true) + @v13_10_1_1575_90 = described_class.parse("v13.10.1~beta.1575.gf6ea9390", parse_suffix: true) end describe '>' do it { expect(@v2_0_0).to be > @v1_1_0 } it { expect(@v1_1_0).to be > @v1_0_1 } + it { expect(@v1_0_1_b1).to be > @v1_0_0 } + it { expect(@v1_0_1_rc1).to be > @v1_0_0 } + it { expect(@v1_0_1_rc1).to be > @v1_0_1_b1 } + it { expect(@v1_0_1_rc2).to be > @v1_0_1_rc1 } + it { expect(@v1_0_1).to be > @v1_0_1_rc1 } + it { expect(@v1_0_1).to be > @v1_0_1_rc2 } it { expect(@v1_0_1).to be > @v1_0_0 } it { expect(@v1_0_0).to be > @v0_1_0 } + it { expect(@v1_1_0_beta1).to be > @v1_0_1_rc2 } + it { expect(@v1_1_0).to be > @v1_1_0_beta1 } it { expect(@v0_1_0).to be > @v0_0_1 } end describe '>=' do - it { expect(@v2_0_0).to be >= Gitlab::VersionInfo.new(2, 0, 0) } + it { expect(@v2_0_0).to be >= described_class.new(2, 0, 0) } it { expect(@v2_0_0).to be >= @v1_1_0 } + it { expect(@v1_0_1_rc2).to be >= @v1_0_1_rc1 } end describe '<' do @@ -31,64 +47,115 @@ RSpec.describe 'Gitlab::VersionInfo' do it { expect(@v0_1_0).to be < @v1_0_0 } it { expect(@v1_0_0).to be < @v1_0_1 } it { expect(@v1_0_1).to be < @v1_1_0 } + it { expect(@v1_0_0).to be < @v1_0_1_rc2 } + it { expect(@v1_0_1_rc1).to be < @v1_0_1 } + it { expect(@v1_0_1_rc1).to be < @v1_0_1_rc2 } + it { expect(@v1_0_1_rc2).to be < @v1_0_1 } it { expect(@v1_1_0).to be < @v2_0_0 } + it { expect(@v13_10_1_1574_89).to be < @v13_10_1_1575_89 } + it { expect(@v13_10_1_1575_89).to be < @v13_10_1_1575_90 } end describe '<=' do - it { expect(@v0_0_1).to be <= Gitlab::VersionInfo.new(0, 0, 1) } + it { expect(@v0_0_1).to be <= described_class.new(0, 0, 1) } it { expect(@v0_0_1).to be <= @v0_1_0 } + it { expect(@v1_0_1_b1).to be <= @v1_0_1_rc1 } + it { expect(@v1_0_1_rc1).to be <= @v1_0_1_rc2 } + it { expect(@v1_1_0_beta1).to be <= @v1_1_0 } end describe '==' do - it { expect(@v0_0_1).to eq(Gitlab::VersionInfo.new(0, 0, 1)) } - it { expect(@v0_1_0).to eq(Gitlab::VersionInfo.new(0, 1, 0)) } - it { expect(@v1_0_0).to eq(Gitlab::VersionInfo.new(1, 0, 0)) } + it { expect(@v0_0_1).to eq(described_class.new(0, 0, 1)) } + it { expect(@v0_1_0).to eq(described_class.new(0, 1, 0)) } + it { expect(@v1_0_0).to eq(described_class.new(1, 0, 0)) } + it { expect(@v1_0_1_rc1).to eq(described_class.new(1, 0, 1, '-rc1')) } end describe '!=' do it { expect(@v0_0_1).not_to eq(@v0_1_0) } + it { expect(@v1_0_1_rc1).not_to eq(@v1_0_1_rc2) } end describe '.unknown' do it { expect(@unknown).not_to be @v0_0_1 } - it { expect(@unknown).not_to be Gitlab::VersionInfo.new } + it { expect(@unknown).not_to be described_class.new } it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) } it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) } end describe '.parse' do - it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) } - it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) } - it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) } - it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1")).to eq(@v1_0_0) } - it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) } - it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) } - it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid } + it { expect(described_class.parse("1.0.0")).to eq(@v1_0_0) } + it { expect(described_class.parse("1.0.0.1")).to eq(@v1_0_0) } + it { expect(described_class.parse("1.0.0-ee")).to eq(@v1_0_0) } + it { expect(described_class.parse("1.0.0-rc1")).to eq(@v1_0_0) } + it { expect(described_class.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) } + it { expect(described_class.parse("git 1.0.0b1")).to eq(@v1_0_0) } + it { expect(described_class.parse("git 1.0b1")).not_to be_valid } + + context 'with parse_suffix: true' do + let(:versions) do + <<-VERSIONS.lines + 0.0.1 + 0.1.0 + 1.0.0 + 1.0.1-b1 + 1.0.1-rc1 + 1.0.1-rc2 + 1.0.1 + 1.1.0-beta1 + 1.1.0 + 2.0.0 + v13.10.0-pre + v13.10.0-rc1 + v13.10.0-rc2 + v13.10.0 + v13.10.1~beta.1574.gf6ea9389 + v13.10.1~beta.1575.gf6ea9389 + v13.10.1-rc1 + v13.10.1-rc2 + v13.10.1 + VERSIONS + end + + let(:parsed_versions) do + versions.map(&:strip).map { |version| described_class.parse(version, parse_suffix: true) } + end + + it 'versions are returned in a correct order' do + expect(parsed_versions.shuffle.sort).to eq(parsed_versions) + end + end end describe '.to_s' do it { expect(@v1_0_0.to_s).to eq("1.0.0") } + it { expect(@v1_0_1_rc1.to_s).to eq("1.0.1-rc1") } it { expect(@unknown.to_s).to eq("Unknown") } end describe '.hash' do - it { expect(Gitlab::VersionInfo.parse("1.0.0").hash).to eq(@v1_0_0.hash) } - it { expect(Gitlab::VersionInfo.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) } - it { expect(Gitlab::VersionInfo.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) } + it { expect(described_class.parse("1.0.0").hash).to eq(@v1_0_0.hash) } + it { expect(described_class.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) } + it { expect(described_class.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) } + it { expect(described_class.parse("1.0.1-rc1", parse_suffix: true).hash).to eq(@v1_0_1_rc1.hash) } end describe '.eql?' do - it { expect(Gitlab::VersionInfo.parse("1.0.0").eql?(@v1_0_0)).to be_truthy } - it { expect(Gitlab::VersionInfo.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy } + it { expect(described_class.parse("1.0.0").eql?(@v1_0_0)).to be_truthy } + it { expect(described_class.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy } + it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc1)).to be_truthy } + it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc2)).to be_falsey } + it { expect(@v1_0_1_rc1.eql?(@v1_0_1)).to be_falsey } it { expect(@v1_0_1.eql?(@v1_0_0)).to be_falsey } it { expect(@v1_1_0.eql?(@v1_0_0)).to be_falsey } it { expect(@v1_0_0.eql?(@v1_0_0)).to be_truthy } - it { expect([@v1_0_0, @v1_1_0, @v1_0_0].uniq).to eq [@v1_0_0, @v1_1_0] } + it { expect([@v1_0_0, @v1_1_0, @v1_0_0, @v1_0_1_rc1, @v1_0_1_rc1].uniq).to eq [@v1_0_0, @v1_1_0, @v1_0_1_rc1] } end describe '.same_minor_version?' do it { expect(@v0_1_0.same_minor_version?(@v0_0_1)).to be_falsey } it { expect(@v1_0_1.same_minor_version?(@v1_0_0)).to be_truthy } + it { expect(@v1_0_1_rc1.same_minor_version?(@v1_0_0)).to be_truthy } it { expect(@v1_0_0.same_minor_version?(@v1_0_1)).to be_truthy } it { expect(@v1_1_0.same_minor_version?(@v1_0_0)).to be_falsey } it { expect(@v2_0_0.same_minor_version?(@v1_0_0)).to be_falsey } @@ -98,5 +165,6 @@ RSpec.describe 'Gitlab::VersionInfo' do it { expect(@v0_1_0.without_patch).to eq(@v0_1_0) } it { expect(@v1_0_0.without_patch).to eq(@v1_0_0) } it { expect(@v1_0_1.without_patch).to eq(@v1_0_0) } + it { expect(@v1_0_1_rc1.without_patch).to eq(@v1_0_0) } end end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 61f008416ea..dcb9890c7cd 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -1065,6 +1065,14 @@ RSpec.describe ApplicationSetting do it { is_expected.to validate_numericality_of(:sidekiq_job_limiter_compression_threshold_bytes).only_integer.is_greater_than_or_equal_to(0) } it { is_expected.to validate_numericality_of(:sidekiq_job_limiter_limit_bytes).only_integer.is_greater_than_or_equal_to(0) } end + + context 'prometheus settings' do + it 'validates metrics_method_call_threshold' do + allow(subject).to receive(:prometheus_metrics_enabled).and_return(true) + + is_expected.to validate_numericality_of(:metrics_method_call_threshold).is_greater_than_or_equal_to(0) + end + end end context 'restrict creating duplicates' do diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb index 2939a40a84f..15b6b45eaba 100644 --- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb +++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb @@ -10,7 +10,9 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do let(:sentry_client) { instance_double(ErrorTracking::SentryClient) } - subject(:setting) { build(:project_error_tracking_setting, project: project) } + let(:sentry_project_id) { 10 } + + subject(:setting) { build(:project_error_tracking_setting, project: project, sentry_project_id: sentry_project_id) } describe 'Associations' do it { is_expected.to belong_to(:project) } @@ -270,7 +272,7 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do end describe '#issue_details' do - let(:issue) { build(:error_tracking_sentry_detailed_error) } + let(:issue) { build(:error_tracking_sentry_detailed_error, project_id: sentry_project_id) } let(:commit_id) { issue.first_release_version } let(:result) { subject.issue_details(opts) } let(:opts) { { issue_id: 1 } } @@ -317,12 +319,33 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do end end + describe '#issue_latest_event' do + let(:error_event) { build(:error_tracking_sentry_error_event, project_id: sentry_project_id) } + let(:result) { subject.issue_latest_event(opts) } + let(:opts) { { issue_id: 1 } } + + before do + stub_reactive_cache(subject, error_event, {}) + synchronous_reactive_cache(subject) + + allow(subject).to receive(:sentry_client).and_return(sentry_client) + allow(sentry_client).to receive(:issue_latest_event).with(opts).and_return(error_event) + end + + it 'returns the error event' do + expect(result[:latest_event].project_id).to eq(sentry_project_id) + end + end + describe '#update_issue' do let(:result) { subject.update_issue(**opts) } let(:opts) { { issue_id: 1, params: {} } } before do allow(subject).to receive(:sentry_client).and_return(sentry_client) + allow(sentry_client).to receive(:issue_details) + .with({ issue_id: 1 }) + .and_return(Gitlab::ErrorTracking::DetailedError.new(project_id: sentry_project_id)) end context 'when sentry response is successful' do @@ -344,6 +367,56 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do expect(result).to eq(error: 'Unexpected Error') end end + + context 'when sentry_project_id is not set' do + let(:sentry_projects) do + [ + Gitlab::ErrorTracking::Project.new( + id: 1111, + name: 'Some Project', + organization_name: 'Org' + ), + Gitlab::ErrorTracking::Project.new( + id: sentry_project_id, + name: setting.project_name, + organization_name: setting.organization_name + ) + ] + end + + context 'when sentry_project_id is not set' do + before do + setting.update!(sentry_project_id: nil) + + allow(sentry_client).to receive(:projects).and_return(sentry_projects) + allow(sentry_client).to receive(:update_issue).with(opts).and_return(true) + end + + it 'tries to backfill it from sentry API' do + expect(result).to eq(updated: true) + + expect(setting.reload.sentry_project_id).to eq(sentry_project_id) + end + + context 'when the project cannot be found on sentry' do + before do + sentry_projects.pop + end + + it 'raises error' do + expect { result }.to raise_error(/Couldn't find project/) + end + end + end + + context 'when mismatching sentry_project_id is detected' do + it 'raises error' do + setting.update!(sentry_project_id: sentry_project_id + 1) + + expect { result }.to raise_error(/The Sentry issue appers to be outside/) + end + end + end end describe 'slugs' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 196254d4609..48bd3bdf7eb 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4057,6 +4057,41 @@ RSpec.describe User do end end + describe '#authorized_project_mirrors' do + it 'returns project mirrors where the user has access equal to or above the given level' do + guest_project = create(:project) + reporter_project = create(:project) + maintainer_project = create(:project) + + guest_group = create(:group) + reporter_group = create(:group) + maintainer_group = create(:group) + + _guest_group_project = create(:project, group: guest_group) + reporter_group_project = create(:project, group: reporter_group) + maintainer_group_project = create(:project, group: maintainer_group) + + user = create(:user) + + guest_project.add_guest(user) + reporter_project.add_reporter(user) + maintainer_project.add_maintainer(user) + + guest_group.add_guest(user) + reporter_group.add_reporter(user) + maintainer_group.add_maintainer(user) + + project_mirrors = user.authorized_project_mirrors(Gitlab::Access::REPORTER) + + expect(project_mirrors.pluck(:project_id)).to contain_exactly( + reporter_group_project.id, + maintainer_group_project.id, + reporter_project.id, + maintainer_project.id + ) + end + end + shared_context '#ci_owned_runners' do let(:user) { create(:user) } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index eaace86025c..5884dc3f23f 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -2361,4 +2361,25 @@ RSpec.describe ProjectPolicy do it { is_expected.to be_disallowed(:register_project_runners) } end end + + describe 'update_sentry_issue' do + using RSpec::Parameterized::TableSyntax + + where(:role, :allowed) do + :owner | true + :maintainer | true + :developer | true + :reporter | false + :guest | false + end + + let(:project) { public_project } + let(:current_user) { public_send(role) } + + with_them do + it do + expect(subject.can?(:update_sentry_issue)).to be(allowed) + end + end + end end diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb index 3000bdc2ce7..31b85a0b1d6 100644 --- a/spec/requests/api/ci/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -804,6 +804,23 @@ RSpec.describe API::Ci::Runners do expect(json_response).to be_an(Array) expect(json_response.length).to eq(2) end + + context 'when user does not have authorization to see all jobs' do + it 'shows only jobs it has permission to see' do + create(:ci_build, :running, runner: two_projects_runner, project: project) + create(:ci_build, :running, runner: two_projects_runner, project: project2) + + project.add_guest(user2) + project2.add_maintainer(user2) + get api("/runners/#{two_projects_runner.id}/jobs", user2) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + + expect(json_response).to be_an(Array) + expect(json_response.length).to eq(1) + end + end end context 'when valid status is provided' do diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 48f2c45bd98..c1217292d5c 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -483,6 +483,29 @@ RSpec.describe API::Labels do let(:request) { api("/projects/#{project.id}/labels", user) } let(:params) { { name: valid_label_title_1 } } end + + context 'with group label' do + let_it_be(:group) { create(:group) } + let_it_be(:group_label) { create(:group_label, title: valid_group_label_title_1, group: group) } + + before do + project.update!(group: group) + end + + it 'returns 401 if user does not have access' do + delete api("/projects/#{project.id}/labels/#{group_label.id}", user) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'returns 204 if user has access' do + group.add_developer(user) + + delete api("/projects/#{project.id}/labels/#{group_label.id}", user) + + expect(response).to have_gitlab_http_status(:no_content) + end + end end describe 'PUT /projects/:id/labels' do @@ -537,6 +560,44 @@ RSpec.describe API::Labels do expect(response).to have_gitlab_http_status(:bad_request) end + + context 'with group label' do + let_it_be(:group) { create(:group) } + let_it_be(:group_label) { create(:group_label, title: valid_group_label_title_1, group: group) } + + before do + project.update!(group: group) + end + + it 'allows updating of group label priority' do + put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: { priority: 5 } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['priority']).to eq(5) + end + + it 'returns 401 when updating other fields' do + put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: { + priority: 5, + new_name: 'new label name' + } + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'returns 200 when user has access to the group label' do + group.add_developer(user) + + put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: { + priority: 5, + new_name: 'new label name' + } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['priority']).to eq(5) + expect(json_response['name']).to eq('new label name') + end + end end describe 'PUT /projects/:id/labels/promote' do diff --git a/spec/requests/jira_routing_spec.rb b/spec/requests/jira_routing_spec.rb index a627eea33a8..e0e170044de 100644 --- a/spec/requests/jira_routing_spec.rb +++ b/spec/requests/jira_routing_spec.rb @@ -25,27 +25,49 @@ RSpec.describe 'Jira referenced paths', type: :request do expect(response).to redirect_to(redirect_path) end - context 'with encoded subgroup path' do - where(:jira_path, :redirect_path) do - '/group/group@sub_group@sub_group_project' | '/group/sub_group/sub_group_project' - '/group@sub_group/group@sub_group@sub_group_project' | '/group/sub_group/sub_group_project' - '/group/group@sub_group@sub_group_project/commit/1234567' | '/group/sub_group/sub_group_project/commit/1234567' - '/group/group@sub_group@sub_group_project/tree/1234567' | '/group/sub_group/sub_group_project/-/tree/1234567' + shared_examples 'redirects to jira path' do + it 'redirects to canonical path with legacy prefix' do + redirects_to_canonical_path "/-/jira#{jira_path}", redirect_path end - with_them do - context 'with legacy prefix' do - it 'redirects to canonical path' do - redirects_to_canonical_path "/-/jira#{jira_path}", redirect_path - end - end - - it 'redirects to canonical path' do - redirects_to_canonical_path jira_path, redirect_path - end + it 'redirects to canonical path' do + redirects_to_canonical_path jira_path, redirect_path end end + let(:jira_path) { '/group/group@sub_group@sub_group_project' } + let(:redirect_path) { '/group/sub_group/sub_group_project' } + + it_behaves_like 'redirects to jira path' + + context 'contains @ before the first /' do + let(:jira_path) { '/group@sub_group/group@sub_group@sub_group_project' } + let(:redirect_path) { '/group/sub_group/sub_group_project' } + + it_behaves_like 'redirects to jira path' + end + + context 'including commit path' do + let(:jira_path) { '/group/group@sub_group@sub_group_project/commit/1234567' } + let(:redirect_path) { '/group/sub_group/sub_group_project/commit/1234567' } + + it_behaves_like 'redirects to jira path' + end + + context 'including tree path' do + let(:jira_path) { '/group/group@sub_group@sub_group_project/tree/1234567' } + let(:redirect_path) { '/group/sub_group/sub_group_project/-/tree/1234567' } + + it_behaves_like 'redirects to jira path' + end + + context 'malicious path' do + let(:jira_path) { '/group/@@malicious.server' } + let(:redirect_path) { '/malicious.server' } + + it_behaves_like 'redirects to jira path' + end + context 'regular paths with legacy prefix' do where(:jira_path, :redirect_path) do '/-/jira/group/group_project' | '/group/group_project' diff --git a/spec/serializers/member_user_entity_spec.rb b/spec/serializers/member_user_entity_spec.rb index 0e6d4bcc3fb..85f29845d65 100644 --- a/spec/serializers/member_user_entity_spec.rb +++ b/spec/serializers/member_user_entity_spec.rb @@ -11,7 +11,7 @@ RSpec.describe MemberUserEntity do let(:entity_hash) { entity.as_json } it 'matches json schema' do - expect(entity.to_json).to match_schema('entities/member_user') + expect(entity.to_json).to match_schema('entities/member_user_default') end it 'correctly exposes `avatar_url`' do @@ -27,10 +27,8 @@ RSpec.describe MemberUserEntity do expect(entity_hash[:blocked]).to be(true) end - it 'correctly exposes `two_factor_enabled`' do - allow(user).to receive(:two_factor_enabled?).and_return(true) - - expect(entity_hash[:two_factor_enabled]).to be(true) + it 'does not expose `two_factor_enabled` by default' do + expect(entity_hash[:two_factor_enabled]).to be(nil) end it 'correctly exposes `status.emoji`' do @@ -44,4 +42,66 @@ RSpec.describe MemberUserEntity do it 'correctly exposes `last_activity_on`' do expect(entity_hash[:last_activity_on]).to be(user.last_activity_on) end + + context 'when options includes a source' do + let(:current_user) { create(:user) } + let(:options) { { current_user: current_user, source: source } } + let(:entity) { described_class.new(user, options) } + + shared_examples 'correctly exposes user two_factor_enabled' do + context 'when the current_user has a role lower than minimum manage member role' do + before do + source.add_user(current_user, Gitlab::Access::DEVELOPER) + end + + it 'does not expose user two_factor_enabled' do + expect(entity_hash[:two_factor_enabled]).to be(nil) + end + + it 'matches json schema' do + expect(entity.to_json).to match_schema('entities/member_user_default') + end + end + + context 'when the current user has a minimum manage member role or higher' do + before do + source.add_user(current_user, minimum_manage_member_role) + end + + it 'matches json schema' do + expect(entity.to_json).to match_schema('entities/member_user_for_admin_member') + end + + it 'exposes user two_factor_enabled' do + expect(entity_hash[:two_factor_enabled]).to be(false) + end + end + + context 'when the current user is self' do + let(:current_user) { user } + + it 'exposes user two_factor_enabled' do + expect(entity_hash[:two_factor_enabled]).to be(false) + end + + it 'matches json schema' do + expect(entity.to_json).to match_schema('entities/member_user_for_admin_member') + end + end + end + + context 'when the source is a group' do + let(:source) { create(:group) } + let(:minimum_manage_member_role) { Gitlab::Access::OWNER } + + it_behaves_like 'correctly exposes user two_factor_enabled' + end + + context 'when the source is a project' do + let(:source) { create(:project) } + let(:minimum_manage_member_role) { Gitlab::Access::MAINTAINER } + + it_behaves_like 'correctly exposes user two_factor_enabled' + end + end end diff --git a/spec/services/bulk_imports/file_decompression_service_spec.rb b/spec/services/bulk_imports/file_decompression_service_spec.rb index 1d6aa79a37f..77348428d60 100644 --- a/spec/services/bulk_imports/file_decompression_service_spec.rb +++ b/spec/services/bulk_imports/file_decompression_service_spec.rb @@ -80,7 +80,8 @@ RSpec.describe BulkImports::FileDecompressionService do subject { described_class.new(tmpdir: tmpdir, filename: 'symlink.gz') } it 'raises an error and removes the file' do - expect { subject.execute }.to raise_error(described_class::ServiceError, 'Invalid file') + expect { subject.execute } + .to raise_error(BulkImports::FileDecompressionService::ServiceError, 'File decompression error') expect(File.exist?(symlink)).to eq(false) end diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 914b3d3b867..ba2a50c6edb 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -303,7 +303,7 @@ RSpec.describe Issues::CreateService do context 'user is reporter or above' do before do - project.add_reporter(user) + project.add_developer(user) end it 'assigns the sentry error' do diff --git a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb index e8ccb12e6b7..e7360de34f6 100644 --- a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb +++ b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb @@ -16,6 +16,6 @@ RSpec.shared_context 'sentry error tracking context' do before do allow(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting) - project.add_reporter(user) + project.add_developer(user) end end diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb index 135fa4cf5a4..e6b0772aec1 100644 --- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb @@ -19,33 +19,66 @@ RSpec.shared_examples 'conan ping endpoint' do end RSpec.shared_examples 'conan search endpoint' do - before do - project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) - - # Do not pass the HTTP_AUTHORIZATION header, - # in order to test that this public project's packages - # are visible to anonymous search. - get api(url), params: params - end + using RSpec::Parameterized::TableSyntax subject { json_response['results'] } - context 'returns packages with a matching name' do - let(:params) { { q: package.conan_recipe } } + context 'with a public project' do + before do + project.update!(visibility: 'public') + + # Do not pass the HTTP_AUTHORIZATION header, + # in order to test that this public project's packages + # are visible to anonymous search. + get api(url), params: params + end + + context 'returns packages with a matching name' do + let(:params) { { q: package.conan_recipe } } + + it { is_expected.to contain_exactly(package.conan_recipe) } + end + + context 'returns packages using a * wildcard' do + let(:params) { { q: "#{package.name[0, 3]}*" } } - it { is_expected.to contain_exactly(package.conan_recipe) } + it { is_expected.to contain_exactly(package.conan_recipe) } + end + + context 'does not return non-matching packages' do + let(:params) { { q: "foo" } } + + it { is_expected.to be_blank } + end end - context 'returns packages using a * wildcard' do + context 'with a private project' do let(:params) { { q: "#{package.name[0, 3]}*" } } - it { is_expected.to contain_exactly(package.conan_recipe) } - end + where(:role, :packages_visible) do + :maintainer | true + :developer | true + :reporter | true + :guest | false + :anonymous | false + end - context 'does not return non-matching packages' do - let(:params) { { q: "foo" } } + with_them do + before do + project.update!(visibility: 'private') + project.team.truncate + user.project_authorizations.delete_all + project.add_user(user, role) unless role == :anonymous + + get api(url), params: params, headers: headers + end - it { is_expected.to be_blank } + if params[:packages_visible] + it { is_expected.to contain_exactly(package.conan_recipe) } + else + it { is_expected.to be_blank } + end + end end end diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index 5ccd0d2efdb..f6ea0bc7307 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -259,6 +259,7 @@ RSpec.describe 'Every Sidekiq worker' do 'Gitlab::GithubImport::AdvanceStageWorker' => 3, 'Gitlab::GithubImport::ImportDiffNoteWorker' => 5, 'Gitlab::GithubImport::ImportIssueWorker' => 5, + 'Gitlab::GithubImport::ImportIssueEventWorker' => 5, 'Gitlab::GithubImport::ImportLfsObjectWorker' => 5, 'Gitlab::GithubImport::ImportNoteWorker' => 5, 'Gitlab::GithubImport::ImportPullRequestMergedByWorker' => 5, @@ -268,6 +269,7 @@ RSpec.describe 'Every Sidekiq worker' do 'Gitlab::GithubImport::Stage::FinishImportWorker' => 5, 'Gitlab::GithubImport::Stage::ImportBaseDataWorker' => 5, 'Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker' => 5, + 'Gitlab::GithubImport::Stage::ImportIssueEventsWorker' => 5, 'Gitlab::GithubImport::Stage::ImportLfsObjectsWorker' => 5, 'Gitlab::GithubImport::Stage::ImportNotesWorker' => 5, 'Gitlab::GithubImport::Stage::ImportPullRequestsMergedByWorker' => 5, diff --git a/spec/workers/gitlab/github_import/import_issue_event_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_event_worker_spec.rb new file mode 100644 index 00000000000..6af450151e3 --- /dev/null +++ b/spec/workers/gitlab/github_import/import_issue_event_worker_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::GithubImport::ImportIssueEventWorker do + subject(:worker) { described_class.new } + + describe '#import' do + let(:project) do + instance_double('Project', full_path: 'foo/bar', id: 1, import_state: nil) + end + + let(:client) { instance_double('Gitlab::GithubImport::Client') } + let(:importer) { instance_double('Gitlab::GithubImport::Importer::IssueEventImporter') } + + let(:event_hash) do + { + 'id' => 6501124486, + 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG', + 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486', + 'actor' => { 'id' => 4, 'login' => 'alice' }, + 'event' => 'closed', + 'commit_id' => nil, + 'commit_url' => nil, + 'created_at' => '2022-04-26 18:30:53 UTC', + 'performed_via_github_app' => nil + } + end + + it 'imports an issue event' do + expect(Gitlab::GithubImport::Importer::IssueEventImporter) + .to receive(:new) + .with( + an_instance_of(Gitlab::GithubImport::Representation::IssueEvent), + project, + client + ) + .and_return(importer) + + expect(importer).to receive(:execute) + + expect(Gitlab::GithubImport::ObjectCounter) + .to receive(:increment) + .and_call_original + + worker.import(project, client, event_hash) + end + end +end diff --git a/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb new file mode 100644 index 00000000000..b3c6a48767c --- /dev/null +++ b/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::GithubImport::Stage::ImportIssueEventsWorker do + subject(:worker) { described_class.new } + + let(:project) { create(:project) } + let!(:group) { create(:group, projects: [project]) } + let(:feature_flag_state) { [group] } + + describe '#import' do + let(:importer) { instance_double('Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter') } + let(:client) { instance_double('Gitlab::GithubImport::Client') } + + before do + stub_feature_flags(github_importer_issue_events_import: feature_flag_state) + end + + it 'imports all the issue events' do + waiter = Gitlab::JobWaiter.new(2, '123') + + expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter) + .to receive(:new) + .with(project, client) + .and_return(importer) + + expect(importer).to receive(:execute).and_return(waiter) + + expect(Gitlab::GithubImport::AdvanceStageWorker) + .to receive(:perform_async) + .with(project.id, { '123' => 2 }, :notes) + + worker.import(client, project) + end + + context 'when feature flag is disabled' do + let(:feature_flag_state) { false } + + it 'skips issue events import and calls next stage' do + expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter).not_to receive(:new) + expect(Gitlab::GithubImport::AdvanceStageWorker).to receive(:perform_async).with(project.id, {}, :notes) + + worker.import(client, project) + end + end + end +end diff --git a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb index c0dd4f488cc..a88256b3cae 100644 --- a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do expect(Gitlab::GithubImport::AdvanceStageWorker) .to receive(:perform_async) - .with(project.id, { '123' => 2 }, :notes) + .with(project.id, { '123' => 2 }, :issue_events) worker.import(client, project) end |