diff options
23 files changed, 390 insertions, 55 deletions
diff --git a/app/controllers/organizations/application_controller.rb b/app/controllers/organizations/application_controller.rb index bd2dcfacabe..568cfe6399d 100644 --- a/app/controllers/organizations/application_controller.rb +++ b/app/controllers/organizations/application_controller.rb @@ -2,6 +2,7 @@ module Organizations class ApplicationController < ::ApplicationController + skip_before_action :authenticate_user! before_action :organization layout 'organization' diff --git a/app/finders/repositories/tree_finder.rb b/app/finders/repositories/tree_finder.rb index 231c1de1513..2a8971d4d86 100644 --- a/app/finders/repositories/tree_finder.rb +++ b/app/finders/repositories/tree_finder.rb @@ -13,7 +13,7 @@ module Repositories def execute(gitaly_pagination: false) raise CommitMissingError unless commit_exists? - request_params = { recursive: recursive } + request_params = { recursive: recursive, rescue_not_found: rescue_not_found } request_params[:pagination_params] = pagination_params if gitaly_pagination repository.tree(commit.id, path, **request_params).sorted_entries @@ -51,6 +51,10 @@ module Repositories params[:recursive] end + def rescue_not_found + params[:rescue_not_found] + end + def pagination_params { limit: params[:per_page] || Kaminari.config.default_per_page, diff --git a/app/models/repository.rb b/app/models/repository.rb index 0bd26e7ad4c..b8a46f80bc7 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -697,7 +697,7 @@ class Repository @head_tree ||= Tree.new(self, root_ref, nil, skip_flat_paths: skip_flat_paths) end - def tree(sha = :head, path = nil, recursive: false, skip_flat_paths: true, pagination_params: nil, ref_type: nil) + def tree(sha = :head, path = nil, recursive: false, skip_flat_paths: true, pagination_params: nil, ref_type: nil, rescue_not_found: true) if sha == :head return if empty? || root_ref.nil? @@ -709,7 +709,7 @@ class Repository end end - Tree.new(self, sha, path, recursive: recursive, skip_flat_paths: skip_flat_paths, pagination_params: pagination_params, ref_type: ref_type) + Tree.new(self, sha, path, recursive: recursive, skip_flat_paths: skip_flat_paths, pagination_params: pagination_params, ref_type: ref_type, rescue_not_found: rescue_not_found) end def blob_at_branch(branch_name, path) diff --git a/app/models/tree.rb b/app/models/tree.rb index 8622eb793c1..4d62334800d 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -7,7 +7,7 @@ class Tree def initialize( repository, sha, path = '/', recursive: false, skip_flat_paths: true, pagination_params: nil, - ref_type: nil) + ref_type: nil, rescue_not_found: true) path = '/' if path.blank? @repository = repository @@ -18,7 +18,9 @@ class Tree ref = ExtractsRef.qualify_ref(@sha, ref_type) - @entries, @cursor = Gitlab::Git::Tree.where(git_repo, ref, @path, recursive, skip_flat_paths, pagination_params) + @entries, @cursor = Gitlab::Git::Tree.where(git_repo, ref, @path, recursive, skip_flat_paths, rescue_not_found, + pagination_params) + @entries.each do |entry| entry.ref_type = self.ref_type end diff --git a/app/policies/organizations/organization_policy.rb b/app/policies/organizations/organization_policy.rb index 8cb4359b6fe..1c0d996c7d4 100644 --- a/app/policies/organizations/organization_policy.rb +++ b/app/policies/organizations/organization_policy.rb @@ -4,6 +4,13 @@ module Organizations class OrganizationPolicy < BasePolicy condition(:organization_user) { @subject.user?(@user) } + desc 'Organization is public' + condition(:public_organization, scope: :subject, score: 0) { true } + + rule { public_organization }.policy do + enable :read_organization + end + rule { admin }.policy do enable :admin_organization enable :read_organization diff --git a/app/presenters/packages/nuget/v2/metadata_index_presenter.rb b/app/presenters/packages/nuget/v2/metadata_index_presenter.rb new file mode 100644 index 00000000000..0ce7c8956b3 --- /dev/null +++ b/app/presenters/packages/nuget/v2/metadata_index_presenter.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Packages + module Nuget + module V2 + class MetadataIndexPresenter + def xml + Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| + xml['edmx'].Edmx('xmlns:edmx' => 'http://schemas.microsoft.com/ado/2007/06/edmx', Version: '1.0') do + xml['edmx'].DataServices('xmlns:m' => 'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata', + 'm:DataServiceVersion' => '2.0', 'm:MaxDataServiceVersion' => '2.0') do + xml.Schema(xmlns: 'http://schemas.microsoft.com/ado/2006/04/edm', Namespace: 'NuGetGallery.OData') do + xml.EntityType(Name: 'V2FeedPackage', 'm:HasStream' => true) do + xml.Key do + xml.PropertyRef(Name: 'Id') + xml.PropertyRef(Name: 'Version') + end + xml.Property(Name: 'Id', Type: 'Edm.String', Nullable: false) + xml.Property(Name: 'Version', Type: 'Edm.String', Nullable: false) + xml.Property(Name: 'Authors', Type: 'Edm.String') + xml.Property(Name: 'Dependencies', Type: 'Edm.String') + xml.Property(Name: 'Description', Type: 'Edm.String') + xml.Property(Name: 'DownloadCount', Type: 'Edm.Int64', Nullable: false) + xml.Property(Name: 'IconUrl', Type: 'Edm.String') + xml.Property(Name: 'Published', Type: 'Edm.DateTime', Nullable: false) + xml.Property(Name: 'ProjectUrl', Type: 'Edm.String') + xml.Property(Name: 'Tags', Type: 'Edm.String') + xml.Property(Name: 'Title', Type: 'Edm.String') + xml.Property(Name: 'LicenseUrl', Type: 'Edm.String') + end + end + xml.Schema(xmlns: 'http://schemas.microsoft.com/ado/2006/04/edm', Namespace: 'NuGetGallery') do + xml.EntityContainer(Name: 'V2FeedContext', 'm:IsDefaultEntityContainer' => true) do + xml.EntitySet(Name: 'Packages', EntityType: 'NuGetGallery.OData.V2FeedPackage') + xml.FunctionImport(Name: 'FindPackagesById', + ReturnType: 'Collection(NuGetGallery.OData.V2FeedPackage)', EntitySet: 'Packages') do + xml.Parameter(Name: 'id', Type: 'Edm.String', FixedLength: 'false', Unicode: 'false') + end + end + end + end + end + end + end + end + end + end +end diff --git a/config/feature_flags/development/handle_structured_gitaly_errors.yml b/config/feature_flags/development/handle_structured_gitaly_errors.yml new file mode 100644 index 00000000000..26a8082dec4 --- /dev/null +++ b/config/feature_flags/development/handle_structured_gitaly_errors.yml @@ -0,0 +1,8 @@ +--- +name: handle_structured_gitaly_errors +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128366 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/420865 +milestone: '16.3' +type: development +group: group::source code +default_enabled: false diff --git a/doc/api/packages/nuget.md b/doc/api/packages/nuget.md index aad4f0e9d45..a549d6af086 100644 --- a/doc/api/packages/nuget.md +++ b/doc/api/packages/nuget.md @@ -424,3 +424,54 @@ Example response: ] } ``` + +## V2 Feed Metadata Endpoint + +> Introduced in GitLab 16.3. + +Authentication is not required. Returns metadata for a V2 feed available endpoints: + +```plaintext +GET <route-prefix>/v2/$metadata +``` + +```shell + curl "https://gitlab.example.com/api/v4/projects/1/packages/nuget/v2/$metadata" +``` + +Example response: + +```xml +<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0"> + <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0" m:MaxDataServiceVersion="2.0"> + <Schema xmlns="http://schemas.microsoft.com/ado/2006/04/edm" Namespace="NuGetGallery.OData"> + <EntityType Name="V2FeedPackage" m:HasStream="true"> + <Key> + <PropertyRef Name="Id"/> + <PropertyRef Name="Version"/> + </Key> + <Property Name="Id" Type="Edm.String" Nullable="false"/> + <Property Name="Version" Type="Edm.String" Nullable="false"/> + <Property Name="Authors" Type="Edm.String"/> + <Property Name="Dependencies" Type="Edm.String"/> + <Property Name="Description" Type="Edm.String"/> + <Property Name="DownloadCount" Type="Edm.Int64" Nullable="false"/> + <Property Name="IconUrl" Type="Edm.String"/> + <Property Name="Published" Type="Edm.DateTime" Nullable="false"/> + <Property Name="ProjectUrl" Type="Edm.String"/> + <Property Name="Tags" Type="Edm.String"/> + <Property Name="Title" Type="Edm.String"/> + <Property Name="LicenseUrl" Type="Edm.String"/> + </EntityType> + </Schema> + <Schema xmlns="http://schemas.microsoft.com/ado/2006/04/edm" Namespace="NuGetGallery"> + <EntityContainer Name="V2FeedContext" m:IsDefaultEntityContainer="true"> + <EntitySet Name="Packages" EntityType="NuGetGallery.OData.V2FeedPackage"/> + <FunctionImport Name="FindPackagesById" ReturnType="Collection(NuGetGallery.OData.V2FeedPackage)" EntitySet="Packages"> + <Parameter Name="id" Type="Edm.String" FixedLength="false" Unicode="false"/> + </FunctionImport> + </EntityContainer> + </Schema> + </edmx:DataServices> +</edmx:Edmx> +``` diff --git a/lib/api/concerns/packages/nuget/public_endpoints.rb b/lib/api/concerns/packages/nuget/public_endpoints.rb index d5be136c7a2..b0c9177f452 100644 --- a/lib/api/concerns/packages/nuget/public_endpoints.rb +++ b/lib/api/concerns/packages/nuget/public_endpoints.rb @@ -60,6 +60,22 @@ module API .new(project_or_group_without_auth) .xml end + + # https://www.nuget.org/api/v2/$metadata + desc 'The NuGet V2 Feed Package $metadata endpoint' do + detail 'This feature was introduced in GitLab 16.3' + success code: 200 + tags %w[nuget_packages] + end + + get '$metadata', format: :xml, urgency: :low do + env['api.format'] = :xml + content_type 'application/xml; charset=utf-8' + # needed to allow browser default inline styles in xml response + header 'Content-Security-Policy', "nonce-#{SecureRandom.base64(16)}" + + present ::Packages::Nuget::V2::MetadataIndexPresenter.new.xml + end end end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 295d1d5ab16..4131f41743f 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -95,6 +95,10 @@ module API params ] end + + def rescue_not_found? + Feature.disabled?(:handle_structured_gitaly_errors) + end end desc 'Get a project repository tree' do @@ -123,13 +127,16 @@ module API end end get ':id/repository/tree', urgency: :low do - tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false)) + tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false).merge(rescue_not_found: rescue_not_found?)) not_found!("Tree") unless tree_finder.commit_exists? tree = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(tree_finder) present tree, with: Entities::TreeObject + + rescue Gitlab::Git::Index::IndexError => e + not_found!(e.message) end desc 'Get raw blob contents from the repository' diff --git a/lib/gitlab/git/rugged_impl/tree.rb b/lib/gitlab/git/rugged_impl/tree.rb index c7a981c7dd4..bc3ff01e1e2 100644 --- a/lib/gitlab/git/rugged_impl/tree.rb +++ b/lib/gitlab/git/rugged_impl/tree.rb @@ -16,7 +16,7 @@ module Gitlab TREE_SORT_ORDER = { tree: 0, blob: 1, commit: 2 }.freeze override :tree_entries - def tree_entries(repository, sha, path, recursive, skip_flat_paths, pagination_params = nil) + def tree_entries(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, pagination_params = nil) if use_rugged?(repository, :rugged_tree_entries) entries = execute_rugged_call( :tree_entries_with_flat_path_from_rugged, repository, sha, path, recursive, skip_flat_paths) diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb index 140dc791135..0895c0b8a22 100644 --- a/lib/gitlab/git/tree.rb +++ b/lib/gitlab/git/tree.rb @@ -15,22 +15,27 @@ module Gitlab # Uses rugged for raw objects # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/320 - def where(repository, sha, path = nil, recursive = false, skip_flat_paths = true, pagination_params = nil) + def where( + repository, sha, path = nil, recursive = false, skip_flat_paths = true, rescue_not_found = true, + pagination_params = nil) path = nil if path == '' || path == '/' - tree_entries(repository, sha, path, recursive, skip_flat_paths, pagination_params) + tree_entries(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, pagination_params) end - def tree_entries(repository, sha, path, recursive, skip_flat_paths, pagination_params = nil) + def tree_entries(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, pagination_params = nil) wrapped_gitaly_errors do repository.gitaly_commit_client.tree_entries( repository, sha, path, recursive, skip_flat_paths, pagination_params) end # Incorrect revision or path could lead to index error. - # We silently handle such errors by returning an empty set of entries and cursor. - rescue Gitlab::Git::Index::IndexError - [[], nil] + # We silently handle such errors by returning an empty set of entries and cursor + # unless the parameter rescue_not_found is set to false. + rescue Gitlab::Git::Index::IndexError => e + return [[], nil] if rescue_not_found + + raise e end private diff --git a/package.json b/package.json index 963b94db8b8..5ad960c7445 100644 --- a/package.json +++ b/package.json @@ -236,6 +236,7 @@ "@graphql-eslint/eslint-plugin": "3.20.1", "@testing-library/dom": "^7.16.2", "@types/jest": "^28.1.3", + "@types/lodash": "^4.14.197", "@vue/compat": "^3.2.47", "@vue/compiler-sfc": "^3.2.47", "@vue/test-utils": "1.3.6", diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 456a6a564a3..1961e522b89 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -243,7 +243,20 @@ module QA wait = kwargs.delete(:wait) || Capybara.default_max_wait_time text = kwargs.delete(:text) - find(element_selector_css(name, kwargs), text: text, wait: wait).click + begin + find(element_selector_css(name, kwargs), text: text, wait: wait).click + rescue Net::ReadTimeout => error + # In some situations due to perhaps a slow environment we can encounter errors + # where clicks are registered, but the calls to selenium-webdriver result in + # timeout errors. In these cases rescue from the error and attempt to continue in + # the test to avoid a flaky test failure. This should be safe as assertions in the + # tests will catch any case where the click wasn't actually registered. + QA::Runtime::Logger.warn "click_element -- #{error} -- #{error.backtrace.inspect}" + # There may be a 5xx error -- lets refresh the page like the warning page suggests + # and it if resolves itself we can avoid a flaky failure + refresh + end + page.validate_elements_present! if page end diff --git a/spec/finders/repositories/tree_finder_spec.rb b/spec/finders/repositories/tree_finder_spec.rb index 0d70d5f92d3..42b4047c4e8 100644 --- a/spec/finders/repositories/tree_finder_spec.rb +++ b/spec/finders/repositories/tree_finder_spec.rb @@ -26,10 +26,10 @@ RSpec.describe Repositories::TreeFinder do end it "accepts a gitaly_pagination argument" do - expect(repository).to receive(:tree).with(anything, anything, recursive: nil, pagination_params: { limit: 20, page_token: nil }).and_call_original + expect(repository).to receive(:tree).with(anything, anything, recursive: nil, rescue_not_found: nil, pagination_params: { limit: 20, page_token: nil }).and_call_original expect(tree_finder.execute(gitaly_pagination: true)).to be_an(Array) - expect(repository).to receive(:tree).with(anything, anything, recursive: nil).and_call_original + expect(repository).to receive(:tree).with(anything, anything, recursive: nil, rescue_not_found: nil).and_call_original expect(tree_finder.execute(gitaly_pagination: false)).to be_an(Array) end diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 4a20e0b1156..84ab8376fe1 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -9,13 +9,14 @@ RSpec.describe Gitlab::Git::Tree do let(:repository) { project.repository.raw } shared_examples 'repo' do - subject(:tree) { Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, pagination_params) } + subject(:tree) { Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, pagination_params) } let(:sha) { SeedRepo::Commit::ID } let(:path) { nil } let(:recursive) { false } let(:pagination_params) { nil } let(:skip_flat_paths) { false } + let(:rescue_not_found) { true } let(:entries) { tree.first } let(:cursor) { tree.second } @@ -30,8 +31,14 @@ RSpec.describe Gitlab::Git::Tree do context 'with an invalid ref' do let(:sha) { 'foobar-does-not-exist' } - it { expect(entries).to eq([]) } - it { expect(cursor).to be_nil } + context 'when handle_structured_gitaly_errors feature is disabled' do + before do + stub_feature_flags(handle_structured_gitaly_errors: false) + end + + it { expect(entries).to eq([]) } + it { expect(cursor).to be_nil } + end end context 'when path is provided' do @@ -162,11 +169,23 @@ RSpec.describe Gitlab::Git::Tree do end context 'and invalid reference is used' do - it 'returns no entries and nil cursor' do + before do allow(repository.gitaly_commit_client).to receive(:tree_entries).and_raise(Gitlab::Git::Index::IndexError) + end + + context 'when rescue_not_found is set to false' do + let(:rescue_not_found) { false } - expect(entries.count).to eq(0) - expect(cursor).to be_nil + it 'raises an IndexError error' do + expect { entries }.to raise_error(Gitlab::Git::Index::IndexError) + end + end + + context 'when rescue_not_found is set to true' do + it 'returns no entries and nil cursor' do + expect(entries.count).to eq(0) + expect(cursor).to be_nil + end end end end @@ -196,7 +215,7 @@ RSpec.describe Gitlab::Git::Tree do let(:entries_count) { entries.count } it 'returns all entries without a cursor' do - result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, { limit: entries_count, page_token: nil }) + result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: entries_count, page_token: nil }) expect(cursor).to be_nil expect(result.entries.count).to eq(entries_count) @@ -225,7 +244,7 @@ RSpec.describe Gitlab::Git::Tree do let(:entries_count) { entries.count } it 'returns all entries' do - result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, { limit: -1, page_token: nil }) + result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: -1, page_token: nil }) expect(result.count).to eq(entries_count) expect(cursor).to be_nil @@ -236,7 +255,7 @@ RSpec.describe Gitlab::Git::Tree do let(:token) { entries.second.id } it 'returns all entries after token' do - result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, { limit: -1, page_token: token }) + result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: -1, page_token: token }) expect(result.count).to eq(entries.count - 2) expect(cursor).to be_nil @@ -268,7 +287,7 @@ RSpec.describe Gitlab::Git::Tree do expected_entries = entries loop do - result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, { limit: 5, page_token: token }) + result, cursor = Gitlab::Git::Tree.where(repository, sha, path, recursive, skip_flat_paths, rescue_not_found, { limit: 5, page_token: token }) collected_entries += result.entries token = cursor&.next_cursor diff --git a/spec/policies/organizations/organization_policy_spec.rb b/spec/policies/organizations/organization_policy_spec.rb index 9914ce455d3..e51362227c9 100644 --- a/spec/policies/organizations/organization_policy_spec.rb +++ b/spec/policies/organizations/organization_policy_spec.rb @@ -7,6 +7,12 @@ RSpec.describe Organizations::OrganizationPolicy, feature_category: :cell do subject(:policy) { described_class.new(current_user, organization) } + context 'when the user is anonymous' do + let_it_be(:current_user) { nil } + + it { is_expected.to be_allowed(:read_organization) } + end + context 'when the user is an admin' do let_it_be(:current_user) { create(:user, :admin) } @@ -17,7 +23,7 @@ RSpec.describe Organizations::OrganizationPolicy, feature_category: :cell do context 'when admin mode is disabled' do it { is_expected.to be_disallowed(:admin_organization) } - it { is_expected.to be_disallowed(:read_organization) } + it { is_expected.to be_allowed(:read_organization) } end end @@ -30,11 +36,4 @@ RSpec.describe Organizations::OrganizationPolicy, feature_category: :cell do it { is_expected.to be_allowed(:read_organization) } end - - context 'when the user is not an organization user' do - let_it_be(:current_user) { create :user } - - it { is_expected.to be_disallowed(:admin_organization) } - it { is_expected.to be_disallowed(:read_organization) } - end end diff --git a/spec/presenters/packages/nuget/v2/metadata_index_presenter_spec.rb b/spec/presenters/packages/nuget/v2/metadata_index_presenter_spec.rb new file mode 100644 index 00000000000..598db641b75 --- /dev/null +++ b/spec/presenters/packages/nuget/v2/metadata_index_presenter_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Nuget::V2::MetadataIndexPresenter, feature_category: :package_registry do + describe '#xml' do + let(:presenter) { described_class.new } + + subject(:xml) { Nokogiri::XML(presenter.xml.to_xml) } + + specify { expect(xml.root.name).to eq('Edmx') } + + specify { expect(xml.at_xpath('//edmx:Edmx')).to be_present } + + specify { expect(xml.at_xpath('//edmx:Edmx/edmx:DataServices')).to be_present } + + specify do + expect(xml.css('*').map(&:name)).to include( + 'Schema', 'EntityType', 'Key', 'PropertyRef', 'EntityContainer', 'EntitySet', 'FunctionImport', 'Parameter' + ) + end + + specify do + expect(xml.css('*').select { |el| el.name == 'Property' }.map { |el| el.attribute_nodes.first.value }) + .to match_array( + %w[Id Version Authors Dependencies Description DownloadCount IconUrl Published ProjectUrl Tags Title + LicenseUrl] + ) + end + + specify { expect(xml.css('*').detect { |el| el.name == 'EntityContainer' }.attr('Name')).to eq('V2FeedContext') } + + specify { expect(xml.css('*').detect { |el| el.name == 'FunctionImport' }.attr('Name')).to eq('FindPackagesById') } + end +end diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb index 2d3781da42b..da74409cd77 100644 --- a/spec/requests/api/nuget_project_packages_spec.rb +++ b/spec/requests/api/nuget_project_packages_spec.rb @@ -50,6 +50,44 @@ RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do it_behaves_like 'accept get request on private project with access to package registry for everyone' end + describe 'GET /api/v4/projects/:id/packages/nuget/v2/$metadata' do + let(:url) { "/projects/#{target.id}/packages/nuget/v2/$metadata" } + + subject(:api_request) { get api(url) } + + it { is_expected.to have_request_urgency(:low) } + + context 'with valid target' do + using RSpec::Parameterized::TableSyntax + + where(:visibility_level, :user_role, :member, :expected_status) do + 'PUBLIC' | :developer | true | :success + 'PUBLIC' | :guest | true | :success + 'PUBLIC' | :developer | false | :success + 'PUBLIC' | :guest | false | :success + 'PUBLIC' | :anonymous | false | :success + 'PRIVATE' | :developer | true | :success + 'PRIVATE' | :guest | true | :success + 'PRIVATE' | :developer | false | :success + 'PRIVATE' | :guest | false | :success + 'PRIVATE' | :anonymous | false | :success + 'INTERNAL' | :developer | true | :success + 'INTERNAL' | :guest | true | :success + 'INTERNAL' | :developer | false | :success + 'INTERNAL' | :guest | false | :success + 'INTERNAL' | :anonymous | false | :success + end + + with_them do + before do + update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false)) + end + + it_behaves_like 'process nuget v2 $metadata service request', params[:user_role], params[:expected_status], params[:member] + end + end + end + describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do let(:url) { "/projects/#{target.id}/packages/nuget/metadata/#{package_name}/index.json" } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 8853eff0b3e..a94ed63bf47 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -37,6 +37,52 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do end end + context 'when path does not exist' do + let(:path) { 'bogus' } + + context 'when handle_structured_gitaly_errors feature is disabled' do + before do + stub_feature_flags(handle_structured_gitaly_errors: false) + end + + it 'returns an empty array' do + get api("#{route}?path=#{path}", current_user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an(Array) + expect(json_response).to be_an_empty + end + end + + context 'when handle_structured_gitaly_errors feature is enabled' do + before do + stub_feature_flags(handle_structured_gitaly_errors: true) + end + + it_behaves_like '404 response' do + let(:request) { get api("#{route}?path=#{path}", current_user) } + let(:message) { '404 invalid revision or path Not Found' } + end + end + end + + context 'when path is empty directory ' do + context 'when handle_structured_gitaly_errors feature is disabled' do + before do + stub_feature_flags(handle_structured_gitaly_errors: false) + end + + it 'returns an empty array' do + get api(route, current_user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an(Array) + end + end + end + context 'when repository is disabled' do include_context 'disabled repository' diff --git a/spec/requests/organizations/organizations_controller_spec.rb b/spec/requests/organizations/organizations_controller_spec.rb index 1dc3c3eaaa3..788d740504a 100644 --- a/spec/requests/organizations/organizations_controller_spec.rb +++ b/spec/requests/organizations/organizations_controller_spec.rb @@ -26,38 +26,40 @@ RSpec.describe Organizations::OrganizationsController, feature_category: :cell d end shared_examples 'basic organization controller action' do - before do - sign_in(user) + context 'when the user is not logged in' do + it_behaves_like 'successful response' + it_behaves_like 'action disabled by `ui_for_organizations` feature flag' end - context 'when the user does not have authorization' do - let_it_be(:user) { create(:user) } + context 'when the user is logged in' do + before do + sign_in(user) + end - it 'renders 404' do - gitlab_request + context 'with no association to an organization' do + let_it_be(:user) { create(:user) } - expect(response).to have_gitlab_http_status(:not_found) + it_behaves_like 'successful response' + it_behaves_like 'action disabled by `ui_for_organizations` feature flag' end - it_behaves_like 'action disabled by `ui_for_organizations` feature flag' - end + context 'as as admin', :enable_admin_mode do + let_it_be(:user) { create(:admin) } - context 'when the user is an admin', :enable_admin_mode do - let_it_be(:user) { create(:admin) } + it_behaves_like 'successful response' + it_behaves_like 'action disabled by `ui_for_organizations` feature flag' + end - it_behaves_like 'successful response' - it_behaves_like 'action disabled by `ui_for_organizations` feature flag' - end + context 'as an organization user' do + let_it_be(:user) { create :user } - context 'when the user is an organization user' do - let_it_be(:user) { create :user } + before do + create :organization_user, organization: organization, user: user + end - before do - create :organization_user, organization: organization, user: user + it_behaves_like 'successful response' + it_behaves_like 'action disabled by `ui_for_organizations` feature flag' end - - it_behaves_like 'successful response' - it_behaves_like 'action disabled by `ui_for_organizations` feature flag' end end diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb index 5854958a06e..2e66bae26ba 100644 --- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb @@ -51,6 +51,34 @@ RSpec.shared_examples 'process nuget service index request' do |user_type, statu end end +RSpec.shared_examples 'process nuget v2 $metadata service request' do |user_type, status, add_member = true| + context "for user type #{user_type}" do + before do + target.send("add_#{user_type}", user) if add_member && user_type != :anonymous + end + + it_behaves_like 'returning response status', status + + it 'returns a valid xml response' do + api_request + + doc = Nokogiri::XML(body) + + expect(response.media_type).to eq('application/xml') + expect(doc.at_xpath('//edmx:Edmx')).to be_present + expect(doc.at_xpath('//edmx:Edmx/edmx:DataServices')).to be_present + expect(doc.css('*').map(&:name)).to include( + 'Schema', 'EntityType', 'Key', 'PropertyRef', 'EntityContainer', 'EntitySet', 'FunctionImport', 'Parameter' + ) + expect(doc.css('*').select { |el| el.name == 'Property' }.map { |el| el.attribute_nodes.first.value }) + .to match_array(%w[Id Version Authors Dependencies Description DownloadCount IconUrl Published ProjectUrl + Tags Title LicenseUrl] + ) + expect(doc.css('*').detect { |el| el.name == 'FunctionImport' }.attr('Name')).to eq('FindPackagesById') + end + end +end + RSpec.shared_examples 'returning nuget metadata json response with json schema' do |json_schema| it 'returns a valid json response' do subject diff --git a/yarn.lock b/yarn.lock index 4a254c2fe77..e6c40b084f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2324,6 +2324,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash@^4.14.197": + version "4.14.197" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.197.tgz#e95c5ddcc814ec3e84c891910a01e0c8a378c54b" + integrity sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g== + "@types/mdast@^3.0.0": version "3.0.10" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" |