diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-14 03:08:59 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-14 03:08:59 +0300 |
commit | 13d294a8d8be05421e7d5e34577033ba5b34059c (patch) | |
tree | f9aab2c3930cb85e9afbb111063930a1bdd1c156 /spec | |
parent | a269fb8e7cca24b826dd3f53485641ffce93bbee (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
34 files changed, 1495 insertions, 129 deletions
diff --git a/spec/controllers/concerns/harbor/artifact_spec.rb b/spec/controllers/concerns/harbor/artifact_spec.rb new file mode 100644 index 00000000000..6716d615a3b --- /dev/null +++ b/spec/controllers/concerns/harbor/artifact_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Harbor::Artifact do + controller(ActionController::Base) do + include ::Harbor::Artifact + end + it_behaves_like 'raises NotImplementedError when calling #container' +end diff --git a/spec/controllers/concerns/harbor/repository_spec.rb b/spec/controllers/concerns/harbor/repository_spec.rb new file mode 100644 index 00000000000..cae038ceed2 --- /dev/null +++ b/spec/controllers/concerns/harbor/repository_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Harbor::Repository do + controller(ActionController::Base) do + include ::Harbor::Repository + end + it_behaves_like 'raises NotImplementedError when calling #container' +end diff --git a/spec/controllers/concerns/harbor/tag_spec.rb b/spec/controllers/concerns/harbor/tag_spec.rb new file mode 100644 index 00000000000..0d72ef303b0 --- /dev/null +++ b/spec/controllers/concerns/harbor/tag_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Harbor::Tag do + controller(ActionController::Base) do + include ::Harbor::Tag + end + it_behaves_like 'raises NotImplementedError when calling #container' +end diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index 3945637c2c3..5ac26b7a260 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -233,7 +233,7 @@ FactoryBot.define do factory :harbor_integration, class: 'Integrations::Harbor' do project active { true } - type { 'HarborService' } + type { 'Integrations::Harbor' } url { 'https://demo.goharbor.io' } project_name { 'testproject' } diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb index 7541e54f014..be1db970e9d 100644 --- a/spec/features/groups/merge_requests_spec.rb +++ b/spec/features/groups/merge_requests_spec.rb @@ -86,7 +86,7 @@ RSpec.describe 'Group merge requests page' do expect(page).to have_selector('.empty-state') expect(page).to have_link('Select project to create merge request') - expect(page).not_to have_selector('.issues-filters') + expect(page).to have_selector('.issues-filters') end context 'with no open merge requests' do diff --git a/spec/lib/gitlab/harbor/client_spec.rb b/spec/lib/gitlab/harbor/client_spec.rb index bc5b593370a..4e80b8b53e3 100644 --- a/spec/lib/gitlab/harbor/client_spec.rb +++ b/spec/lib/gitlab/harbor/client_spec.rb @@ -3,12 +3,277 @@ require 'spec_helper' RSpec.describe Gitlab::Harbor::Client do - let(:harbor_integration) { build(:harbor_integration) } + let_it_be(:harbor_integration) { create(:harbor_integration) } subject(:client) { described_class.new(harbor_integration) } + describe '#initialize' do + context 'if integration is nil' do + let(:harbor_integration) { nil } + + it 'raises ConfigError' do + expect { client }.to raise_error(described_class::ConfigError) + end + end + + context 'integration is provided' do + it 'is initialized successfully' do + expect { client }.not_to raise_error + end + end + end + + describe '#get_repositories' do + context 'with valid params' do + let(:mock_response) do + [ + { + "artifact_count": 1, + "creation_time": "2022-03-13T09:36:43.240Z", + "id": 1, + "name": "jihuprivate/busybox", + "project_id": 4, + "pull_count": 0, + "update_time": "2022-03-13T09:36:43.240Z" + } + ] + end + + let(:mock_repositories) do + { + body: mock_response, + total_count: 2 + } + end + + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }) + .to_return(status: 200, body: mock_response.to_json, headers: { "x-total-count": 2 }) + end + + it 'get repositories' do + expect(client.get_repositories({}).deep_stringify_keys).to eq(mock_repositories.deep_stringify_keys) + end + end + + context 'when harbor project does not exist' do + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }) + .to_return(status: 404, body: {}.to_json) + end + + it 'raises Gitlab::Harbor::Client::Error' do + expect do + client.get_repositories({}) + end.to raise_error(Gitlab::Harbor::Client::Error, 'request error') + end + end + + context 'with invalid response' do + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }) + .to_return(status: 200, body: '[not json}') + end + + it 'raises Gitlab::Harbor::Client::Error' do + expect do + client.get_repositories({}) + end.to raise_error(Gitlab::Harbor::Client::Error, 'invalid response format') + end + end + end + + describe '#get_artifacts' do + context 'with valid params' do + let(:mock_response) do + [ + { + "digest": "sha256:661e8e44e5d7290fbd42d0495ab4ff6fdf1ad251a9f358969b3264a22107c14d", + "icon": "sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06", + "id": 1, + "project_id": 1, + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-04-23T08:04:08.901Z", + "repository_id": 1, + "size": 126745886, + "tags": [ + { + "artifact_id": 1, + "id": 1, + "immutable": false, + "name": "2", + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-04-23T08:04:08.920Z", + "repository_id": 1, + "signed": false + } + ], + "type": "IMAGE" + } + ] + end + + let(:mock_artifacts) do + { + body: mock_response, + total_count: 1 + } + end + + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }) + .to_return(status: 200, body: mock_response.to_json, headers: { "x-total-count": 1 }) + end + + it 'get artifacts' do + expect(client.get_artifacts({ repository_name: 'test' }) + .deep_stringify_keys).to eq(mock_artifacts.deep_stringify_keys) + end + end + + context 'when harbor repository does not exist' do + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }) + .to_return(status: 404, body: {}.to_json) + end + + it 'raises Gitlab::Harbor::Client::Error' do + expect do + client.get_artifacts({ repository_name: 'test' }) + end.to raise_error(Gitlab::Harbor::Client::Error, 'request error') + end + end + + context 'with invalid response' do + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }) + .to_return(status: 200, body: '[not json}') + end + + it 'raises Gitlab::Harbor::Client::Error' do + expect do + client.get_artifacts({ repository_name: 'test' }) + end.to raise_error(Gitlab::Harbor::Client::Error, 'invalid response format') + end + end + end + + describe '#get_tags' do + context 'with valid params' do + let(:mock_response) do + [ + { + "artifact_id": 1, + "id": 1, + "immutable": false, + "name": "2", + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-04-23T08:04:08.920Z", + "repository_id": 1, + "signed": false + } + ] + end + + let(:mock_tags) do + { + body: mock_response, + total_count: 1 + } + end + + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts/1/tags") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }) + .to_return(status: 200, body: mock_response.to_json, headers: { "x-total-count": 1 }) + end + + it 'get tags' do + expect(client.get_tags({ repository_name: 'test', artifact_name: '1' }) + .deep_stringify_keys).to eq(mock_tags.deep_stringify_keys) + end + end + + context 'when harbor artifact does not exist' do + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts/1/tags") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }) + .to_return(status: 404, body: {}.to_json) + end + + it 'raises Gitlab::Harbor::Client::Error' do + expect do + client.get_tags({ repository_name: 'test', artifact_name: '1' }) + end.to raise_error(Gitlab::Harbor::Client::Error, 'request error') + end + end + + context 'with invalid response' do + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts/1/tags") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }) + .to_return(status: 200, body: '[not json}') + end + + it 'raises Gitlab::Harbor::Client::Error' do + expect do + client.get_tags({ repository_name: 'test', artifact_name: '1' }) + end.to raise_error(Gitlab::Harbor::Client::Error, 'invalid response format') + end + end + end + describe '#ping' do - let!(:harbor_ping_request) { stub_harbor_request("https://demo.goharbor.io/api/v2.0/ping") } + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/ping") + .with( + headers: { + 'Content-Type': 'application/json' + }) + .to_return(status: 200, body: 'pong') + end it "calls api/v2.0/ping successfully" do expect(client.ping).to eq(success: true) diff --git a/spec/lib/gitlab/harbor/query_spec.rb b/spec/lib/gitlab/harbor/query_spec.rb new file mode 100644 index 00000000000..dcb9a16b27b --- /dev/null +++ b/spec/lib/gitlab/harbor/query_spec.rb @@ -0,0 +1,375 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Harbor::Query do + let_it_be(:harbor_integration) { create(:harbor_integration) } + + let(:params) { {} } + + subject(:query) { described_class.new(harbor_integration, ActionController::Parameters.new(params)) } + + describe 'Validations' do + context 'page' do + context 'with valid page' do + let(:params) { { page: 1 } } + + it 'initialize successfully' do + expect(query.valid?).to eq(true) + end + end + + context 'with invalid page' do + let(:params) { { page: -1 } } + + it 'initialize failed' do + expect(query.valid?).to eq(false) + end + end + end + + context 'limit' do + context 'with valid limit' do + let(:params) { { limit: 1 } } + + it 'initialize successfully' do + expect(query.valid?).to eq(true) + end + end + + context 'with invalid limit' do + context 'with limit less than 0' do + let(:params) { { limit: -1 } } + + it 'initialize failed' do + expect(query.valid?).to eq(false) + end + end + + context 'with limit greater than 25' do + let(:params) { { limit: 26 } } + + it 'initialize failed' do + expect(query.valid?).to eq(false) + end + end + end + end + + context 'repository_id' do + context 'with valid repository_id' do + let(:params) { { repository_id: 'test' } } + + it 'initialize successfully' do + expect(query.valid?).to eq(true) + end + end + + context 'with invalid repository_id' do + let(:params) { { repository_id: 'test@@' } } + + it 'initialize failed' do + expect(query.valid?).to eq(false) + end + end + end + + context 'artifact_id' do + context 'with valid artifact_id' do + let(:params) { { artifact_id: 'test' } } + + it 'initialize successfully' do + expect(query.valid?).to eq(true) + end + end + + context 'with invalid artifact_id' do + let(:params) { { artifact_id: 'test@@' } } + + it 'initialize failed' do + expect(query.valid?).to eq(false) + end + end + end + + context 'sort' do + context 'with valid sort' do + let(:params) { { sort: 'creation_time desc' } } + + it 'initialize successfully' do + expect(query.valid?).to eq(true) + end + end + + context 'with invalid sort' do + let(:params) { { sort: 'blabla desc' } } + + it 'initialize failed' do + expect(query.valid?).to eq(false) + end + end + end + + context 'search' do + context 'with valid search' do + let(:params) { { search: 'name=desc' } } + + it 'initialize successfully' do + expect(query.valid?).to eq(true) + end + end + + context 'with invalid search' do + let(:params) { { search: 'blabla' } } + + it 'initialize failed' do + expect(query.valid?).to eq(false) + end + end + end + end + + describe '#repositories' do + let(:response) { { total_count: 0, repositories: [] } } + + def expect_query_option_include(expected_params) + expect_next_instance_of(Gitlab::Harbor::Client) do |client| + expect(client).to receive(:get_repositories) + .with(hash_including(expected_params)) + .and_return(response) + end + + query.repositories + end + + context 'when params is {}' do + it 'fills default params' do + expect_query_option_include(page_size: 10, page: 1) + end + end + + context 'when params contains options' do + let(:params) { { search: 'name=bu', sort: 'creation_time desc', limit: 20, page: 3 } } + + it 'fills params with standard of Harbor' do + expect_query_option_include(q: 'name=~bu', sort: '-creation_time', page_size: 20, page: 3) + end + end + + context 'when params contains invalid sort option' do + let(:params) { { search: 'name=bu', sort: 'blabla desc', limit: 20, page: 3 } } + + it 'ignores invalid sort params' do + expect(query.valid?).to eq(false) + end + end + + context 'when client.get_repositories returns data' do + let(:response_with_data) do + { + total_count: 1, + body: + [ + { + "id": 3, + "name": "testproject/thirdbusybox", + "artifact_count": 1, + "creation_time": "2022-03-15T07:12:14.479Z", + "update_time": "2022-03-15T07:12:14.479Z", + "project_id": 3, + "pull_count": 0 + }.with_indifferent_access + ] + } + end + + it 'returns the right repositories data' do + expect_next_instance_of(Gitlab::Harbor::Client) do |client| + expect(client).to receive(:get_repositories) + .with(hash_including(page_size: 10, page: 1)) + .and_return(response_with_data) + end + + expect(query.repositories.first).to include( + "name": "testproject/thirdbusybox", + "artifact_count": 1 + ) + end + end + end + + describe '#artifacts' do + let(:response) { { total_count: 0, artifacts: [] } } + + def expect_query_option_include(expected_params) + expect_next_instance_of(Gitlab::Harbor::Client) do |client| + expect(client).to receive(:get_artifacts) + .with(hash_including(expected_params)) + .and_return(response) + end + + query.artifacts + end + + context 'when params is {}' do + it 'fills default params' do + expect_query_option_include(page_size: 10, page: 1) + end + end + + context 'when params contains options' do + let(:params) do + { search: 'tags=1', repository_id: 'jihuprivate', sort: 'creation_time desc', limit: 20, page: 3 } + end + + it 'fills params with standard of Harbor' do + expect_query_option_include(q: 'tags=~1', sort: '-creation_time', page_size: 20, page: 3) + end + end + + context 'when params contains invalid sort option' do + let(:params) { { search: 'tags=1', repository_id: 'jihuprivate', sort: 'blabla desc', limit: 20, page: 3 } } + + it 'ignores invalid sort params' do + expect(query.valid?).to eq(false) + end + end + + context 'when client.get_artifacts returns data' do + let(:response_with_data) do + { + total_count: 1, + body: + [ + { + "digest": "sha256:14d4f50961544fdb669075c442509f194bdc4c0e344bde06e35dbd55af842a38", + "icon": "sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06", + "id": 5, + "project_id": 14, + "push_time": "2022-03-22T09:04:56.170Z", + "repository_id": 5, + "size": 774790, + "tags": [ + { + "artifact_id": 5, + "id": 7, + "immutable": false, + "name": "2", + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-03-22T09:05:04.844Z", + "repository_id": 5 + }, + { + "artifact_id": 5, + "id": 6, + "immutable": false, + "name": "1", + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-03-22T09:04:56.186Z", + "repository_id": 5 + } + ], + "type": "IMAGE" + }.with_indifferent_access + ] + } + end + + it 'returns the right artifacts data' do + expect_next_instance_of(Gitlab::Harbor::Client) do |client| + expect(client).to receive(:get_artifacts) + .with(hash_including(page_size: 10, page: 1)) + .and_return(response_with_data) + end + + artifact = query.artifacts.first + + expect(artifact).to include( + "digest": "sha256:14d4f50961544fdb669075c442509f194bdc4c0e344bde06e35dbd55af842a38", + "push_time": "2022-03-22T09:04:56.170Z" + ) + expect(artifact["tags"].size).to eq(2) + end + end + end + + describe '#tags' do + let(:response) { { total_count: 0, tags: [] } } + + def expect_query_option_include(expected_params) + expect_next_instance_of(Gitlab::Harbor::Client) do |client| + expect(client).to receive(:get_tags) + .with(hash_including(expected_params)) + .and_return(response) + end + + query.tags + end + + context 'when params is {}' do + it 'fills default params' do + expect_query_option_include(page_size: 10, page: 1) + end + end + + context 'when params contains options' do + let(:params) { { repository_id: 'jihuprivate', sort: 'creation_time desc', limit: 20, page: 3 } } + + it 'fills params with standard of Harbor' do + expect_query_option_include(sort: '-creation_time', page_size: 20, page: 3) + end + end + + context 'when params contains invalid sort option' do + let(:params) { { repository_id: 'jihuprivate', artifact_id: 'test', sort: 'blabla desc', limit: 20, page: 3 } } + + it 'ignores invalid sort params' do + expect(query.valid?).to eq(false) + end + end + + context 'when client.get_tags returns data' do + let(:response_with_data) do + { + total_count: 2, + body: + [ + { + "artifact_id": 5, + "id": 7, + "immutable": false, + "name": "2", + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-03-22T09:05:04.844Z", + "repository_id": 5 + }, + { + "artifact_id": 5, + "id": 6, + "immutable": false, + "name": "1", + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-03-22T09:04:56.186Z", + "repository_id": 5 + }.with_indifferent_access + ] + } + end + + it 'returns the right tags data' do + expect_next_instance_of(Gitlab::Harbor::Client) do |client| + expect(client).to receive(:get_tags) + .with(hash_including(page_size: 10, page: 1)) + .and_return(response_with_data) + end + + tag = query.tags.first + + expect(tag).to include( + "immutable": false, + "push_time": "2022-03-22T09:05:04.844Z" + ) + end + end + end +end diff --git a/spec/migrations/20220606082910_add_tmp_index_for_potentially_misassociated_vulnerability_occurrences_spec.rb b/spec/migrations/20220606082910_add_tmp_index_for_potentially_misassociated_vulnerability_occurrences_spec.rb new file mode 100644 index 00000000000..1450811b3b9 --- /dev/null +++ b/spec/migrations/20220606082910_add_tmp_index_for_potentially_misassociated_vulnerability_occurrences_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "spec_helper" + +require_migration! + +RSpec.describe AddTmpIndexForPotentiallyMisassociatedVulnerabilityOccurrences do + let(:async_index) { Gitlab::Database::AsyncIndexes::PostgresAsyncIndex } + let(:index_name) { described_class::INDEX_NAME } + + it "schedules the index" do + reversible_migration do |migration| + migration.before -> do + expect(async_index.where(name: index_name).count).to be(0) + end + + migration.after -> do + expect(async_index.where(name: index_name).count).to be(1) + end + end + end +end diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb index 3a4b836e453..fc5a9c879f6 100644 --- a/spec/models/ci/group_variable_spec.rb +++ b/spec/models/ci/group_variable_spec.rb @@ -56,4 +56,10 @@ RSpec.describe Ci::GroupVariable do let!(:parent) { model.group } end + + describe '#audit_details' do + it "equals to the group variable's key" do + expect(subject.audit_details).to eq(subject.key) + end + end end diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 29ca088ee04..f0af229ff2c 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -51,4 +51,10 @@ RSpec.describe Ci::Variable do let!(:model) { create(:ci_variable, project: parent) } end end + + describe '#audit_details' do + it "equals to the variable's key" do + expect(subject.audit_details).to eq(subject.key) + end + end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 91ba12a16d5..e8e805b2678 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -42,6 +42,7 @@ RSpec.describe Group do it { is_expected.to have_many(:organizations).class_name('CustomerRelations::Organization') } it { is_expected.to have_one(:crm_settings) } it { is_expected.to have_one(:group_feature) } + it { is_expected.to have_one(:harbor_integration) } describe '#members & #requesters' do let(:requester) { create(:user) } diff --git a/spec/models/integrations/datadog_spec.rb b/spec/models/integrations/datadog_spec.rb index cfc44b22a84..47f916e8457 100644 --- a/spec/models/integrations/datadog_spec.rb +++ b/spec/models/integrations/datadog_spec.rb @@ -240,4 +240,20 @@ RSpec.describe Integrations::Datadog do end end end + + describe '#fields' do + it 'includes the archive_trace_events field' do + expect(instance.fields).to include(have_attributes(name: 'archive_trace_events')) + end + + context 'when the FF :datadog_integration_logs_collection is disabled' do + before do + stub_feature_flags(datadog_integration_logs_collection: false) + end + + it 'does not include the archive_trace_events field' do + expect(instance.fields).not_to include(have_attributes(name: 'archive_trace_events')) + end + end + end end diff --git a/spec/models/integrations/harbor_spec.rb b/spec/models/integrations/harbor_spec.rb index 9e3d4b524a6..5d8597969a1 100644 --- a/spec/models/integrations/harbor_spec.rb +++ b/spec/models/integrations/harbor_spec.rb @@ -19,6 +19,14 @@ RSpec.describe Integrations::Harbor do it { is_expected.to allow_value('helloworld').for(:password) } end + describe 'url' do + subject { build(:harbor_integration) } + + it { is_expected.not_to allow_value('https://192.168.1.1').for(:url) } + it { is_expected.not_to allow_value('https://127.0.0.1').for(:url) } + it { is_expected.to allow_value('https://demo.goharbor.io').for(:url)} + end + describe '#fields' do it 'returns custom fields' do expect(harbor_integration.fields.pluck(:name)).to eq(%w[url project_name username password]) diff --git a/spec/models/integrations/youtrack_spec.rb b/spec/models/integrations/youtrack_spec.rb index f6a9dd8ef37..618ebcbb76a 100644 --- a/spec/models/integrations/youtrack_spec.rb +++ b/spec/models/integrations/youtrack_spec.rb @@ -37,4 +37,10 @@ RSpec.describe Integrations::Youtrack do expect(described_class.reference_pattern.match('yt-123')[:issue]).to eq('yt-123') end end + + describe '#fields' do + it 'only returns the project_url and issues_url fields' do + expect(subject.fields.pluck(:name)).to eq(%w[project_url issues_url]) + end + end end diff --git a/spec/requests/groups/harbor/artifacts_controller_spec.rb b/spec/requests/groups/harbor/artifacts_controller_spec.rb new file mode 100644 index 00000000000..ea9529119a6 --- /dev/null +++ b/spec/requests/groups/harbor/artifacts_controller_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Groups::Harbor::ArtifactsController do + it_behaves_like 'a harbor artifacts controller', anonymous_status_code: '404' do + let_it_be(:container) { create(:group) } + let_it_be(:harbor_integration) { create(:harbor_integration, group: container, project: nil) } + end +end diff --git a/spec/requests/groups/harbor/repositories_controller_spec.rb b/spec/requests/groups/harbor/repositories_controller_spec.rb index 3e475dc410e..b4022561f54 100644 --- a/spec/requests/groups/harbor/repositories_controller_spec.rb +++ b/spec/requests/groups/harbor/repositories_controller_spec.rb @@ -3,67 +3,8 @@ require 'spec_helper' RSpec.describe Groups::Harbor::RepositoriesController do - let_it_be(:group, reload: true) { create(:group) } - let_it_be(:user) { create(:user) } - - shared_examples 'responds with 404 status' do - it 'returns 404' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - shared_examples 'responds with 200 status' do - it 'renders the index template' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template(:index) - end - end - - before do - stub_feature_flags(harbor_registry_integration: true) - group.add_reporter(user) - login_as(user) - end - - describe 'GET #index' do - subject do - get group_harbor_registries_path(group) - response - end - - context 'with harbor registry feature flag enabled' do - it_behaves_like 'responds with 200 status' - end - - context 'with harbor registry feature flag disabled' do - before do - stub_feature_flags(harbor_registry_integration: false) - end - - it_behaves_like 'responds with 404 status' - end - end - - describe 'GET #show' do - subject do - get group_harbor_registry_path(group, 1) - response - end - - context 'with harbor registry feature flag enabled' do - it_behaves_like 'responds with 200 status' - end - - context 'with harbor registry feature flag disabled' do - before do - stub_feature_flags(harbor_registry_integration: false) - end - - it_behaves_like 'responds with 404 status' - end + it_behaves_like 'a harbor repositories controller', anonymous_status_code: '404' do + let_it_be(:container, reload: true) { create(:group) } + let_it_be(:harbor_integration) { create(:harbor_integration, group: container, project: nil) } end end diff --git a/spec/requests/groups/harbor/tags_controller_spec.rb b/spec/requests/groups/harbor/tags_controller_spec.rb new file mode 100644 index 00000000000..257d4366837 --- /dev/null +++ b/spec/requests/groups/harbor/tags_controller_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Groups::Harbor::TagsController do + it_behaves_like 'a harbor tags controller', anonymous_status_code: '404' do + let_it_be(:container) { create(:group) } + let_it_be(:harbor_integration) { create(:harbor_integration, group: container, project: nil) } + end +end diff --git a/spec/requests/projects/harbor/artifacts_controller_spec.rb b/spec/requests/projects/harbor/artifacts_controller_spec.rb new file mode 100644 index 00000000000..310fbcf0a0f --- /dev/null +++ b/spec/requests/projects/harbor/artifacts_controller_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::Harbor::ArtifactsController do + it_behaves_like 'a harbor artifacts controller', anonymous_status_code: '302' do + let_it_be(:container) { create(:project) } + let_it_be(:harbor_integration) { create(:harbor_integration, project: container) } + end +end diff --git a/spec/requests/projects/harbor/repositories_controller_spec.rb b/spec/requests/projects/harbor/repositories_controller_spec.rb index cdb5a696d7e..751becaa20a 100644 --- a/spec/requests/projects/harbor/repositories_controller_spec.rb +++ b/spec/requests/projects/harbor/repositories_controller_spec.rb @@ -3,67 +3,8 @@ require 'spec_helper' RSpec.describe Projects::Harbor::RepositoriesController do - let_it_be(:project, reload: true) { create(:project) } - let_it_be(:user) { create(:user) } - - shared_examples 'responds with 404 status' do - it 'returns 404' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - shared_examples 'responds with 200 status' do - it 'renders the index template' do - subject - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template(:index) - end - end - - before do - stub_feature_flags(harbor_registry_integration: true) - project.add_developer(user) - sign_in(user) - end - - describe 'GET #index' do - subject do - get project_harbor_registry_index_path(project) - response - end - - context 'with harbor registry feature flag enabled' do - it_behaves_like 'responds with 200 status' - end - - context 'with harbor registry feature flag disabled' do - before do - stub_feature_flags(harbor_registry_integration: false) - end - - it_behaves_like 'responds with 404 status' - end - end - - describe 'GET #show' do - subject do - get project_harbor_registry_path(project, 1) - response - end - - context 'with harbor registry feature flag enabled' do - it_behaves_like 'responds with 200 status' - end - - context 'with harbor registry feature flag disabled' do - before do - stub_feature_flags(harbor_registry_integration: false) - end - - it_behaves_like 'responds with 404 status' - end + it_behaves_like 'a harbor repositories controller', anonymous_status_code: '302' do + let_it_be(:container, reload: true) { create(:project) } + let_it_be(:harbor_integration) { create(:harbor_integration, project: container) } end end diff --git a/spec/requests/projects/harbor/tags_controller_spec.rb b/spec/requests/projects/harbor/tags_controller_spec.rb new file mode 100644 index 00000000000..119d1c746ac --- /dev/null +++ b/spec/requests/projects/harbor/tags_controller_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::Harbor::TagsController do + it_behaves_like 'a harbor tags controller', anonymous_status_code: '302' do + let_it_be(:container) { create(:project) } + let_it_be(:harbor_integration) { create(:harbor_integration, project: container) } + end +end diff --git a/spec/routing/group_routing_spec.rb b/spec/routing/group_routing_spec.rb index 5c2ef62683e..9f5f821cc61 100644 --- a/spec/routing/group_routing_spec.rb +++ b/spec/routing/group_routing_spec.rb @@ -59,6 +59,18 @@ RSpec.shared_examples 'groups routing' do expect(get('/groups/gitlabhq/-/boards')).to route_to('groups/boards#index', group_id: 'gitlabhq') end + + it 'routes to the harbor repositories controller' do + expect(get("groups/#{group_path}/-/harbor/repositories")).to route_to('groups/harbor/repositories#index', group_id: group_path) + end + + it 'routes to the harbor artifacts controller' do + expect(get("groups/#{group_path}/-/harbor/repositories/test/artifacts")).to route_to('groups/harbor/artifacts#index', group_id: group_path, repository_id: 'test') + end + + it 'routes to the harbor tags controller' do + expect(get("groups/#{group_path}/-/harbor/repositories/test/artifacts/test/tags")).to route_to('groups/harbor/tags#index', group_id: group_path, repository_id: 'test', artifact_id: 'test') + end end RSpec.describe "Groups", "routing" do diff --git a/spec/serializers/integrations/harbor_serializers/artifact_entity_spec.rb b/spec/serializers/integrations/harbor_serializers/artifact_entity_spec.rb new file mode 100644 index 00000000000..c9a95c02e19 --- /dev/null +++ b/spec/serializers/integrations/harbor_serializers/artifact_entity_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::HarborSerializers::ArtifactEntity do + let_it_be(:harbor_integration) { create(:harbor_integration) } + + let(:artifact) do + { + "digest": "sha256:14d4f50961544fdb669075c442509f194bdc4c0e344bde06e35dbd55af842a38", + "id": 5, + "project_id": 14, + "push_time": "2022-03-22T09:04:56.170Z", + "repository_id": 5, + "size": 774790, + "tags": [ + { + "artifact_id": 5, + "id": 7, + "immutable": false, + "name": "2", + "push_time": "2022-03-22T09:05:04.844Z", + "repository_id": 5, + "signed": false + }, + { + "artifact_id": 5, + "id": 6, + "immutable": false, + "name": "1", + "push_time": "2022-03-22T09:04:56.186Z", + "repository_id": 5, + "signed": false + } + ], + "type": "IMAGE" + }.deep_stringify_keys + end + + subject { described_class.new(artifact).as_json } + + it 'returns the Harbor artifact' do + expect(subject).to include({ + harbor_id: 5, + size: 774790, + push_time: "2022-03-22T09:04:56.170Z".to_datetime, + digest: "sha256:14d4f50961544fdb669075c442509f194bdc4c0e344bde06e35dbd55af842a38", + tags: %w[2 1] + }) + end +end diff --git a/spec/serializers/integrations/harbor_serializers/artifact_serializer_spec.rb b/spec/serializers/integrations/harbor_serializers/artifact_serializer_spec.rb new file mode 100644 index 00000000000..9879c0a6434 --- /dev/null +++ b/spec/serializers/integrations/harbor_serializers/artifact_serializer_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::HarborSerializers::ArtifactSerializer do + it 'represents Integrations::HarborSerializers::ArtifactEntity entities' do + expect(described_class.entity_class).to eq(Integrations::HarborSerializers::ArtifactEntity) + end +end diff --git a/spec/serializers/integrations/harbor_serializers/repository_entity_spec.rb b/spec/serializers/integrations/harbor_serializers/repository_entity_spec.rb new file mode 100644 index 00000000000..29708bd0416 --- /dev/null +++ b/spec/serializers/integrations/harbor_serializers/repository_entity_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::HarborSerializers::RepositoryEntity do + let_it_be(:harbor_integration) { create(:harbor_integration) } + + let(:repo) do + { + "artifact_count" => 1, + "creation_time" => "2022-03-13T09:36:43.240Z", + "id" => 1, + "name" => "jihuprivate/busybox", + "project_id" => 4, + "pull_count" => 0, + "update_time" => "2022-03-13T09:36:43.240Z" + }.deep_stringify_keys + end + + subject { described_class.new(repo, url: "https://demo.goharbor.io", project_name: "jihuprivate").as_json } + + context 'with normal repository data' do + it 'returns the Harbor repository' do + expect(subject).to include({ + artifact_count: 1, + creation_time: "2022-03-13T09:36:43.240Z".to_datetime, + harbor_id: 1, + name: "jihuprivate/busybox", + harbor_project_id: 4, + pull_count: 0, + update_time: "2022-03-13T09:36:43.240Z".to_datetime, + location: "https://demo.goharbor.io/harbor/projects/4/repositories/busybox" + }) + end + end + + context 'with data that may contain path traversal attacks' do + before do + repo["project_id"] = './../../../../../etc/hosts' + end + + it 'returns empty location' do + expect(subject).to include({ + artifact_count: 1, + creation_time: "2022-03-13T09:36:43.240Z".to_datetime, + harbor_id: 1, + name: "jihuprivate/busybox", + harbor_project_id: './../../../../../etc/hosts', + pull_count: 0, + update_time: "2022-03-13T09:36:43.240Z".to_datetime, + location: "https://demo.goharbor.io/" + }) + end + end +end diff --git a/spec/serializers/integrations/harbor_serializers/repository_serializer_spec.rb b/spec/serializers/integrations/harbor_serializers/repository_serializer_spec.rb new file mode 100644 index 00000000000..1a4235bea1e --- /dev/null +++ b/spec/serializers/integrations/harbor_serializers/repository_serializer_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::HarborSerializers::RepositorySerializer do + it 'represents Integrations::HarborSerializers::RepositoryEntity entities' do + expect(described_class.entity_class).to eq(Integrations::HarborSerializers::RepositoryEntity) + end +end diff --git a/spec/serializers/integrations/harbor_serializers/tag_entity_spec.rb b/spec/serializers/integrations/harbor_serializers/tag_entity_spec.rb new file mode 100644 index 00000000000..f4bc5f71d5b --- /dev/null +++ b/spec/serializers/integrations/harbor_serializers/tag_entity_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::HarborSerializers::TagEntity do + let_it_be(:harbor_integration) { create(:harbor_integration) } + + let(:push_time) { "2022-03-22T09:04:56.186Z" } + let(:pull_time) { "2022-03-23T09:04:56.186Z" } + + let(:tag) do + { + "artifact_id": 5, + "id": 6, + "immutable": false, + "name": "1", + "push_time": push_time, + "pull_time": pull_time, + "repository_id": 5, + "signed": false + }.deep_stringify_keys + end + + subject { described_class.new(tag).as_json } + + it 'returns the Harbor artifact' do + expect(subject).to include({ + harbor_repository_id: 5, + harbor_artifact_id: 5, + harbor_id: 6, + name: "1", + pull_time: pull_time.to_datetime.utc, + push_time: push_time.to_datetime.utc, + signed: false, + immutable: false + }) + end +end diff --git a/spec/serializers/integrations/harbor_serializers/tag_serializer_spec.rb b/spec/serializers/integrations/harbor_serializers/tag_serializer_spec.rb new file mode 100644 index 00000000000..45fee0b9b17 --- /dev/null +++ b/spec/serializers/integrations/harbor_serializers/tag_serializer_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::HarborSerializers::TagSerializer do + it 'represents Integrations::HarborSerializers::TagEntity entities' do + expect(described_class.entity_class).to eq(Integrations::HarborSerializers::TagEntity) + end +end diff --git a/spec/support/helpers/harbor_helper.rb b/spec/support/helpers/harbor_helper.rb new file mode 100644 index 00000000000..3f13710ede6 --- /dev/null +++ b/spec/support/helpers/harbor_helper.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module HarborHelper + def harbor_repository_url(container, *args) + if container.is_a?(Project) + project_harbor_repositories_path(container, *args) + else + group_harbor_repositories_path(container, *args) + end + end + + def harbor_artifact_url(container, *args) + if container.is_a?(Project) + project_harbor_repository_artifacts_path(container, *args) + else + group_harbor_repository_artifacts_path(container, *args) + end + end + + def harbor_tag_url(container, *args) + if container.is_a?(Project) + project_harbor_repository_artifact_tags_path(container, *args) + else + group_harbor_repository_artifact_tags_path(container, *args) + end + end +end diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb index 483bca07ba6..eec6e92c5fe 100644 --- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -31,6 +31,7 @@ RSpec.shared_context 'GroupPolicy context' do admin_milestone admin_issue_board read_container_image + read_harbor_registry read_metrics_dashboard_annotation read_prometheus read_crm_contact diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb index 3bb05d0b6a6..789b385c435 100644 --- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb +++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb @@ -30,7 +30,7 @@ RSpec.shared_context 'ProjectPolicy context' do create_snippet create_incident daily_statistics create_merge_request_in download_code download_wiki_code fork_project metrics_dashboard read_build read_commit_status read_confidential_issues read_container_image - read_deployment read_environment read_merge_request + read_harbor_registry read_deployment read_environment read_merge_request read_metrics_dashboard_annotation read_pipeline read_prometheus read_sentry_issue update_issue create_merge_request_in ] diff --git a/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb new file mode 100644 index 00000000000..85fcd426e3d --- /dev/null +++ b/spec/support/shared_examples/harbor/artifacts_controller_shared_examples.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a harbor artifacts controller' do |args| + include HarborHelper + let_it_be(:user) { create(:user) } + let_it_be(:unauthorized_user) { create(:user) } + let_it_be(:json_header) { { accept: 'application/json' } } + + let(:mock_artifacts) do + [ + { + "digest": "sha256:661e8e44e5d7290fbd42d0495ab4ff6fdf1ad251a9f358969b3264a22107c14d", + "icon": "sha256:0048162a053eef4d4ce3fe7518615bef084403614f8bca43b40ae2e762e11e06", + "id": 1, + "project_id": 1, + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-04-23T08:04:08.901Z", + "repository_id": 1, + "size": 126745886, + "tags": [ + { + "artifact_id": 1, + "id": 1, + "immutable": false, + "name": "2", + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-04-23T08:04:08.920Z", + "repository_id": 1, + "signed": false + } + ], + "type": "IMAGE" + } + ] + end + + let(:repository_id) { 'test' } + + shared_examples 'responds with 404 status' do + it 'returns 404' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + shared_examples 'responds with 200 status with json' do + it 'renders the index template' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).not_to render_template(:index) + end + end + + shared_examples 'responds with 302 status' do + it 'returns 302' do + subject + + expect(response).to redirect_to(new_user_session_path) + end + end + + shared_examples 'responds with 422 status with json' do + it 'returns 422' do + subject + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end + end + + before do + stub_request(:get, + "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts"\ + "?page=1&page_size=10&with_tag=true") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }).to_return(status: 200, body: mock_artifacts.to_json, headers: { "x-total-count": 2 }) + container.add_reporter(user) + sign_in(user) + end + + describe 'GET #index.json' do + subject do + get harbor_artifact_url(container, repository_id), headers: json_header + end + + context 'with harbor registry feature flag enabled' do + it_behaves_like 'responds with 200 status with json' + end + + context 'with harbor registry feature flag disabled' do + before do + stub_feature_flags(harbor_registry_integration: false) + end + + it_behaves_like 'responds with 404 status' + end + + context 'with anonymous user' do + before do + sign_out(user) + end + + it_behaves_like "responds with #{args[:anonymous_status_code]} status" + end + + context 'with unauthorized user' do + before do + sign_in(unauthorized_user) + end + + it_behaves_like 'responds with 404 status' + end + + context 'with valid params' do + context 'with valid repository' do + subject do + get harbor_artifact_url(container, repository_id), headers: json_header + end + + it_behaves_like 'responds with 200 status with json' + end + + context 'with valid page' do + subject do + get harbor_artifact_url(container, repository_id, page: '1'), headers: json_header + end + + it_behaves_like 'responds with 200 status with json' + end + + context 'with valid limit' do + subject do + get harbor_artifact_url(container, repository_id, limit: '10'), headers: json_header + end + + it_behaves_like 'responds with 200 status with json' + end + end + + context 'with invalid params' do + context 'with invalid page' do + subject do + get harbor_artifact_url(container, repository_id, page: 'aaa'), headers: json_header + end + + it_behaves_like 'responds with 422 status with json' + end + + context 'with invalid limit' do + subject do + get harbor_artifact_url(container, repository_id, limit: 'aaa'), headers: json_header + end + + it_behaves_like 'responds with 422 status with json' + end + end + end +end diff --git a/spec/support/shared_examples/harbor/container_shared_examples.rb b/spec/support/shared_examples/harbor/container_shared_examples.rb new file mode 100644 index 00000000000..57274e0b457 --- /dev/null +++ b/spec/support/shared_examples/harbor/container_shared_examples.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'raises NotImplementedError when calling #container' do + describe '#container' do + it 'raises NotImplementedError' do + expect { controller.send(:container) }.to raise_error(NotImplementedError) + end + end +end diff --git a/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb new file mode 100644 index 00000000000..b35595a10b2 --- /dev/null +++ b/spec/support/shared_examples/harbor/repositories_controller_shared_examples.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a harbor repositories controller' do |args| + include HarborHelper + let_it_be(:user) { create(:user) } + let_it_be(:unauthorized_user) { create(:user) } + let_it_be(:json_header) { { accept: 'application/json' } } + + let(:mock_repositories) do + [ + { + "artifact_count": 6, + "creation_time": "2022-04-24T10:59:02.719Z", + "id": 33, + "name": "test/photon", + "project_id": 3, + "pull_count": 12, + "update_time": "2022-04-24T11:06:27.678Z" + }, + { + "artifact_count": 1, + "creation_time": "2022-04-23T08:04:08.880Z", + "id": 1, + "name": "test/gemnasium", + "project_id": 3, + "pull_count": 0, + "update_time": "2022-04-23T08:04:08.880Z" + } + ] + end + + shared_examples 'responds with 404 status' do + it 'returns 404' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + shared_examples 'responds with 200 status with html' do + it 'renders the index template' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:index) + end + end + + shared_examples 'responds with 302 status' do + it 'returns 302' do + subject + + expect(response).to redirect_to(new_user_session_path) + end + end + + shared_examples 'responds with 200 status with json' do + it 'renders the index template' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).not_to render_template(:index) + end + end + + shared_examples 'responds with 422 status with json' do + it 'returns 422' do + subject + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end + end + + before do + stub_request(:get, "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories?page=1&page_size=10") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }).to_return(status: 200, body: mock_repositories.to_json, headers: { "x-total-count": 2 }) + container.add_reporter(user) + sign_in(user) + end + + describe 'GET #index.html' do + subject do + get harbor_repository_url(container) + end + + context 'with harbor registry feature flag enabled' do + it_behaves_like 'responds with 200 status with html' + end + + context 'with harbor registry feature flag disabled' do + before do + stub_feature_flags(harbor_registry_integration: false) + end + + it_behaves_like 'responds with 404 status' + end + + context 'with anonymous user' do + before do + sign_out(user) + end + + it_behaves_like "responds with #{args[:anonymous_status_code]} status" + end + + context 'with unauthorized user' do + before do + sign_in(unauthorized_user) + end + + it_behaves_like 'responds with 404 status' + end + end + + describe 'GET #index.json' do + subject do + get harbor_repository_url(container), headers: json_header + end + + context 'with harbor registry feature flag enabled' do + it_behaves_like 'responds with 200 status with json' + end + + context 'with harbor registry feature flag disabled' do + before do + stub_feature_flags(harbor_registry_integration: false) + end + + it_behaves_like 'responds with 404 status' + end + + context 'with valid params' do + context 'with valid page params' do + subject do + get harbor_repository_url(container, page: '1'), headers: json_header + end + + it_behaves_like 'responds with 200 status with json' + end + + context 'with valid limit params' do + subject do + get harbor_repository_url(container, limit: '10'), headers: json_header + end + + it_behaves_like 'responds with 200 status with json' + end + end + + context 'with invalid params' do + context 'with invalid page params' do + subject do + get harbor_repository_url(container, page: 'aaa'), headers: json_header + end + + it_behaves_like 'responds with 422 status with json' + end + + context 'with invalid limit params' do + subject do + get harbor_repository_url(container, limit: 'aaa'), headers: json_header + end + + it_behaves_like 'responds with 422 status with json' + end + end + end +end diff --git a/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb new file mode 100644 index 00000000000..46fea7fdff6 --- /dev/null +++ b/spec/support/shared_examples/harbor/tags_controller_shared_examples.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a harbor tags controller' do |args| + include HarborHelper + let_it_be(:user) { create(:user) } + let_it_be(:unauthorized_user) { create(:user) } + let_it_be(:json_header) { { accept: 'application/json' } } + + let(:mock_artifacts) do + [ + { + "artifact_id": 1, + "id": 1, + "immutable": false, + "name": "2", + "pull_time": "0001-01-01T00:00:00.000Z", + "push_time": "2022-04-23T08:04:08.920Z", + "repository_id": 1, + "signed": false + } + ] + end + + let(:repository_id) { 'test' } + let(:artifact_id) { '1' } + + shared_examples 'responds with 404 status' do + it 'returns 404' do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + shared_examples 'responds with 200 status with json' do + it 'renders the index template' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).not_to render_template(:index) + end + end + + shared_examples 'responds with 302 status' do + it 'returns 302' do + subject + + expect(response).to redirect_to(new_user_session_path) + end + end + + shared_examples 'responds with 422 status with json' do + it 'returns 422' do + subject + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end + end + + before do + stub_request(:get, + "https://demo.goharbor.io/api/v2.0/projects/testproject/repositories/test/artifacts/1/tags"\ + "?page=1&page_size=10") + .with( + headers: { + 'Authorization': 'Basic aGFyYm9ydXNlcm5hbWU6aGFyYm9ycGFzc3dvcmQ=', + 'Content-Type': 'application/json' + }).to_return(status: 200, body: mock_artifacts.to_json, headers: { "x-total-count": 2 }) + container.add_reporter(user) + sign_in(user) + end + + describe 'GET #index.json' do + subject do + get(harbor_tag_url(container, repository_id, artifact_id), + headers: json_header) + end + + context 'with harbor registry feature flag enabled' do + it_behaves_like 'responds with 200 status with json' + end + + context 'with harbor registry feature flag disabled' do + before do + stub_feature_flags(harbor_registry_integration: false) + end + + it_behaves_like 'responds with 404 status' + end + + context 'with anonymous user' do + before do + sign_out(user) + end + + it_behaves_like "responds with #{args[:anonymous_status_code]} status" + end + + context 'with unauthorized user' do + before do + sign_in(unauthorized_user) + end + + it_behaves_like 'responds with 404 status' + end + + context 'with valid params' do + context 'with valid repository' do + subject do + get harbor_tag_url(container, repository_id, artifact_id), headers: json_header + end + + it_behaves_like 'responds with 200 status with json' + end + + context 'with valid page' do + subject do + get(harbor_tag_url(container, repository_id, artifact_id, page: '1'), + headers: json_header) + end + + it_behaves_like 'responds with 200 status with json' + end + + context 'with valid limit' do + subject do + get(harbor_tag_url(container, repository_id, artifact_id, limit: '10'), + headers: json_header) + end + + it_behaves_like 'responds with 200 status with json' + end + end + + context 'with invalid params' do + context 'with invalid page' do + subject do + get(harbor_tag_url(container, repository_id, artifact_id, page: 'aaa'), + headers: json_header) + end + + it_behaves_like 'responds with 422 status with json' + end + + context 'with invalid limit' do + subject do + get(harbor_tag_url(container, repository_id, artifact_id, limit: 'aaa'), + headers: json_header) + end + + it_behaves_like 'responds with 422 status with json' + end + end + end +end |