diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-09 12:14:13 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-09 12:14:13 +0300 |
commit | 6021fa2fc624b7d6902273bae55c5b8b2b2b3fff (patch) | |
tree | 54d044a94c33f737d46ecb4829930868fd79105e /spec | |
parent | 377c02f959f45d07a6d1f3c4d7f5afc6ad5162ff (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
22 files changed, 823 insertions, 391 deletions
diff --git a/spec/factories/packages/package_files.rb b/spec/factories/packages/package_files.rb index 5eac0036b91..7d3dd274777 100644 --- a/spec/factories/packages/package_files.rb +++ b/spec/factories/packages/package_files.rb @@ -337,6 +337,14 @@ FactoryBot.define do size { 3989.bytes } end + trait(:rpm) do + package + file_fixture { 'spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm' } + file_name { 'hello-0.0.1-1.fc29.x86_64.rpm' } + file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' } + size { 115.kilobytes } + end + trait(:object_storage) do file_store { Packages::PackageFileUploader::Store::REMOTE } end diff --git a/spec/factories/packages/packages.rb b/spec/factories/packages/packages.rb index 3d5bdc95722..8a3fe4bd948 100644 --- a/spec/factories/packages/packages.rb +++ b/spec/factories/packages/packages.rb @@ -55,6 +55,12 @@ FactoryBot.define do end end + factory :rpm_package do + sequence(:name) { |n| "package-#{n}" } + sequence(:version) { |n| "v1.0.#{n}" } + package_type { :rpm } + end + factory :debian_package do sequence(:name) { |n| "package-#{n}" } sequence(:version) { |n| "1.0-#{n}" } diff --git a/spec/factories/packages/rpm/metadata.rb b/spec/factories/packages/rpm/metadata.rb new file mode 100644 index 00000000000..96c785bd1ae --- /dev/null +++ b/spec/factories/packages/rpm/metadata.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :rpm_metadatum, class: 'Packages::Rpm::Metadatum' do + package { association(:rpm_package) } + release { "#{rand(10)}.#{rand(10)}" } + summary { FFaker::Lorem.sentences(2).join } + description { FFaker::Lorem.sentences(4).join } + arch { FFaker::Lorem.word } + end +end diff --git a/spec/features/projects/settings/registry_settings_spec.rb b/spec/features/projects/settings/registry_settings_spec.rb index 5d252a86de7..1ed9ff83cdd 100644 --- a/spec/features/projects/settings/registry_settings_spec.rb +++ b/spec/features/projects/settings/registry_settings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Project > Settings > Packages and registries > Container registry tag expiration policy', :js do +RSpec.describe 'Project > Settings > Packages and registries > Container registry tag expiration policy' do let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) } @@ -19,7 +19,7 @@ RSpec.describe 'Project > Settings > Packages and registries > Container registr stub_container_registry_config(enabled: container_registry_enabled) end - context 'as owner' do + context 'as owner', :js do it 'shows available section' do subject @@ -27,40 +27,14 @@ RSpec.describe 'Project > Settings > Packages and registries > Container registr expect(settings_block).to have_text 'Clean up image tags' end - it 'saves cleanup policy submit the form' do + it 'contains link to clean up image tags page' do subject - within '[data-testid="container-expiration-policy-project-settings"]' do - select('Every day', from: 'Run cleanup') - select('50 tags per image name', from: 'Keep the most recent:') - fill_in('Keep tags matching:', with: 'stable') - select('7 days', from: 'Remove tags older than:') - fill_in('Remove tags matching:', with: '.*-production') - - submit_button = find('[data-testid="save-button"') - expect(submit_button).not_to be_disabled - submit_button.click - end - - expect(find('.gl-toast')).to have_content('Cleanup policy successfully saved.') - end - - it 'does not save cleanup policy submit form with invalid regex' do - subject - - within '[data-testid="container-expiration-policy-project-settings"]' do - fill_in('Remove tags matching:', with: '*-production') - - submit_button = find('[data-testid="save-button"') - expect(submit_button).not_to be_disabled - submit_button.click - end - - expect(find('.gl-toast')).to have_content('Something went wrong while updating the cleanup policy.') + expect(page).to have_link('Edit cleanup rules', href: cleanup_image_tags_project_settings_packages_and_registries_path(project)) end end - context 'with a project without expiration policy' do + context 'with a project without expiration policy', :js do before do project.container_expiration_policy.destroy! end @@ -74,7 +48,7 @@ RSpec.describe 'Project > Settings > Packages and registries > Container registr subject within '[data-testid="container-expiration-policy-project-settings"]' do - expect(find('[data-testid="enable-toggle"]')).to have_content('Disabled - Tags will not be automatically deleted.') + expect(page).to have_link('Set cleanup rules', href: cleanup_image_tags_project_settings_packages_and_registries_path(project)) end end end diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb index 89f6b4237a4..5056e245fed 100644 --- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb +++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb @@ -288,6 +288,17 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do end end + it 'no Auto DevOps button if builds feature is disabled' do + project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED) + + visit project_path(project) + + page.within('.project-buttons') do + expect(page).not_to have_link('Enable Auto DevOps') + expect(page).not_to have_link('Auto DevOps enabled') + end + end + it 'no "Enable Auto DevOps" button when .gitlab-ci.yml already exists' do Files::CreateService.new( project, diff --git a/spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm b/spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm Binary files differnew file mode 100644 index 00000000000..bff3193a9e8 --- /dev/null +++ b/spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js index d83c717da6a..827cd05390a 100644 --- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js +++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js @@ -1,12 +1,14 @@ -import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { GlAlert, GlSprintf, GlLink, GlCard } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import component from '~/packages_and_registries/settings/project/components/container_expiration_policy.vue'; -import ContainerExpirationPolicyForm from '~/packages_and_registries/settings/project/components/container_expiration_policy_form.vue'; import { + CONTAINER_CLEANUP_POLICY_EDIT_RULES, + CONTAINER_CLEANUP_POLICY_SET_RULES, + CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION, FETCH_SETTINGS_ERROR_MESSAGE, UNAVAILABLE_FEATURE_INTRO_TEXT, UNAVAILABLE_USER_FEATURE_TEXT, @@ -14,11 +16,7 @@ import { import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql'; import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; -import { - expirationPolicyPayload, - emptyExpirationPolicyPayload, - containerExpirationPolicyData, -} from '../mock_data'; +import { expirationPolicyPayload, emptyExpirationPolicyPayload } from '../mock_data'; describe('Container expiration policy project settings', () => { let wrapper; @@ -28,17 +26,19 @@ describe('Container expiration policy project settings', () => { projectPath: 'path', isAdmin: false, adminSettingsPath: 'settingsPath', + cleanupSettingsPath: 'cleanupSettingsPath', enableHistoricEntries: false, helpPagePath: 'helpPagePath', - showCleanupPolicyLink: false, }; - const findFormComponent = () => wrapper.find(ContainerExpirationPolicyForm); - const findAlert = () => wrapper.find(GlAlert); + const findFormComponent = () => wrapper.findComponent(GlCard); + const findDescription = () => wrapper.findByTestId('description'); + const findButton = () => wrapper.findByTestId('rules-button'); + const findAlert = () => wrapper.findComponent(GlAlert); const findSettingsBlock = () => wrapper.find(SettingsBlock); const mountComponent = (provide = defaultProvidedValues, config) => { - wrapper = shallowMount(component, { + wrapper = shallowMountExtended(component, { stubs: { GlSprintf, SettingsBlock, @@ -63,37 +63,19 @@ describe('Container expiration policy project settings', () => { wrapper.destroy(); }); - describe('isEdited status', () => { - it.each` - description | apiResponse | workingCopy | result - ${'empty response and no changes from user'} | ${emptyExpirationPolicyPayload()} | ${{}} | ${false} - ${'empty response and changes from user'} | ${emptyExpirationPolicyPayload()} | ${{ enabled: true }} | ${true} - ${'response and no changes'} | ${expirationPolicyPayload()} | ${containerExpirationPolicyData()} | ${false} - ${'response and changes'} | ${expirationPolicyPayload()} | ${{ ...containerExpirationPolicyData(), nameRegex: '12345' }} | ${true} - ${'response and empty'} | ${expirationPolicyPayload()} | ${{}} | ${true} - `('$description', async ({ apiResponse, workingCopy, result }) => { - mountComponentWithApollo({ - provide: { ...defaultProvidedValues, enableHistoricEntries: true }, - resolver: jest.fn().mockResolvedValue(apiResponse), - }); - await waitForPromises(); - - findFormComponent().vm.$emit('input', workingCopy); - - await waitForPromises(); - - expect(findFormComponent().props('isEdited')).toBe(result); - }); - }); - it('renders the setting form', async () => { mountComponentWithApollo({ resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()), }); await waitForPromises(); - expect(findFormComponent().exists()).toBe(true); expect(findSettingsBlock().exists()).toBe(true); + expect(findFormComponent().exists()).toBe(true); + expect(findDescription().text()).toMatchInterpolatedText( + CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION, + ); + expect(findButton().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_EDIT_RULES); + expect(findButton().attributes('href')).toBe(defaultProvidedValues.cleanupSettingsPath); }); describe('the form is disabled', () => { @@ -157,6 +139,10 @@ describe('Container expiration policy project settings', () => { await waitForPromises(); expect(findFormComponent().exists()).toBe(isShown); + if (isShown) { + expect(findButton().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_SET_RULES); + expect(findButton().attributes('href')).toBe(defaultProvidedValues.cleanupSettingsPath); + } }); }); }); diff --git a/spec/graphql/types/packages/package_type_enum_spec.rb b/spec/graphql/types/packages/package_type_enum_spec.rb index 9d5a7716a61..fb93b1c8c8a 100644 --- a/spec/graphql/types/packages/package_type_enum_spec.rb +++ b/spec/graphql/types/packages/package_type_enum_spec.rb @@ -4,6 +4,6 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['PackageTypeEnum'] do it 'exposes all package types' do - expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM TERRAFORM_MODULE]) + expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM TERRAFORM_MODULE RPM]) end end diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb index 190ddef0cd5..cb5c6a60e1d 100644 --- a/spec/lib/container_registry/tag_spec.rb +++ b/spec/lib/container_registry/tag_spec.rb @@ -240,6 +240,31 @@ RSpec.describe ContainerRegistry::Tag do it_behaves_like 'setting and caching the created_at value' end end + + describe 'updated_at=' do + subject do + tag.updated_at = input + tag.updated_at + end + + context 'with a valid input' do + let(:input) { 2.days.ago.iso8601 } + + it { is_expected.to eq(DateTime.iso8601(input)) } + end + + context 'with a nil input' do + let(:input) { nil } + + it { is_expected.to eq(nil) } + end + + context 'with an invalid input' do + let(:input) { 'not a timestamp' } + + it { is_expected.to eq(nil) } + end + end end end end diff --git a/spec/models/incident_management/timeline_event_spec.rb b/spec/models/incident_management/timeline_event_spec.rb index 9f4011fe6a7..fea391acda3 100644 --- a/spec/models/incident_management/timeline_event_spec.rb +++ b/spec/models/incident_management/timeline_event_spec.rb @@ -29,7 +29,7 @@ RSpec.describe IncidentManagement::TimelineEvent do it { is_expected.to validate_length_of(:action).is_at_most(128) } end - describe '.order_occurred_at_asc' do + describe '.order_occurred_at_asc_id_asc' do let_it_be(:occurred_3mins_ago) do create(:incident_management_timeline_event, project: project, occurred_at: 3.minutes.ago) end @@ -38,11 +38,23 @@ RSpec.describe IncidentManagement::TimelineEvent do create(:incident_management_timeline_event, project: project, occurred_at: 2.minutes.ago) end - subject(:order) { described_class.order_occurred_at_asc } + subject(:order) { described_class.order_occurred_at_asc_id_asc } it 'sorts timeline events by occurred_at' do is_expected.to eq([occurred_3mins_ago, occurred_2mins_ago, timeline_event]) end + + context 'when two events occured at the same time' do + let_it_be(:also_occurred_2mins_ago) do + create(:incident_management_timeline_event, project: project, occurred_at: occurred_2mins_ago.occurred_at) + end + + it 'sorts timeline events by occurred_at then sorts by id' do + occurred_2mins_ago.touch # Interact with record of earlier id to switch default DB ordering + + is_expected.to eq([occurred_3mins_ago, occurred_2mins_ago, also_occurred_2mins_ago, timeline_event]) + end + end end describe '#cache_markdown_field' do diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb index 526c57d08b0..e44883e7b0e 100644 --- a/spec/models/packages/package_spec.rb +++ b/spec/models/packages/package_spec.rb @@ -21,6 +21,7 @@ RSpec.describe Packages::Package, type: :model do it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) } it { is_expected.to have_one(:rubygems_metadatum).inverse_of(:package) } it { is_expected.to have_one(:npm_metadatum).inverse_of(:package) } + it { is_expected.to have_one(:rpm_metadatum).inverse_of(:package) } end describe '.with_debian_codename' do diff --git a/spec/models/packages/rpm/metadatum_spec.rb b/spec/models/packages/rpm/metadatum_spec.rb new file mode 100644 index 00000000000..53a40b4a4b3 --- /dev/null +++ b/spec/models/packages/rpm/metadatum_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Packages::Rpm::Metadatum, type: :model do + describe 'relationships' do + it { is_expected.to belong_to(:package) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:package) } + it { is_expected.to validate_presence_of(:release) } + it { is_expected.to validate_presence_of(:summary) } + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_presence_of(:arch) } + it { is_expected.to validate_length_of(:release).is_at_most(128) } + it { is_expected.to validate_length_of(:summary).is_at_most(1000) } + it { is_expected.to validate_length_of(:description).is_at_most(5000) } + it { is_expected.to validate_length_of(:arch).is_at_most(255) } + it { is_expected.to validate_length_of(:license).is_at_most(1000) } + it { is_expected.to validate_length_of(:url).is_at_most(1000) } + + describe '#rpm_package_type' do + it 'will not allow a package with a different package_type' do + package = build('conan_package') + rpm_metadatum = build('rpm_metadatum', package: package) + + expect(rpm_metadatum).not_to be_valid + expect(rpm_metadatum.errors.to_a).to include('Package type must be RPM') + end + end + end +end diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb index f6bc3048092..7ff19b1b770 100644 --- a/spec/presenters/project_presenter_spec.rb +++ b/spec/presenters/project_presenter_spec.rb @@ -484,6 +484,12 @@ RSpec.describe ProjectPresenter do end describe '#autodevops_anchor_data' do + it 'returns nil if builds feature is not available' do + allow(project).to receive(:feature_available?).with(:builds, user).and_return(false) + + expect(presenter.autodevops_anchor_data).to be_nil + end + context 'when Auto Devops is enabled' do it 'returns anchor data' do allow(project).to receive(:auto_devops_enabled?).and_return(true) diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 362826eb10a..8a08d5203fd 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -7,10 +7,11 @@ RSpec.describe API::Commits do include ProjectForksHelper include SessionHelpers - let(:user) { create(:user) } - let(:guest) { create(:user).tap { |u| project.add_guest(u) } } - let(:developer) { create(:user).tap { |u| project.add_developer(u) } } - let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository, creator: user, path: 'my.project') } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let(:branch_with_dot) { project.repository.find_branch('ends-with.json') } let(:branch_with_slash) { project.repository.find_branch('improve/awesome') } let(:project_id) { project.id } @@ -46,7 +47,7 @@ RSpec.describe API::Commits do end context 'when unauthenticated', 'and project is public' do - let(:project) { create(:project, :public, :repository) } + let_it_be(:project) { create(:project, :public, :repository) } it_behaves_like 'project commits' end @@ -413,6 +414,9 @@ RSpec.describe API::Commits do end describe 'create' do + let_it_be(:sequencer) { FactoryBot::Sequence.new(:new_file_path) { |n| "files/test/#{n}.rb" } } + + let(:new_file_path) { sequencer.next } let(:message) { 'Created a new file with a very very looooooooooooooooooooooooooooooooooooooooooooooong commit message' } let(:invalid_c_params) do { @@ -435,7 +439,7 @@ RSpec.describe API::Commits do actions: [ { action: 'create', - file_path: 'foo/bar/baz.txt', + file_path: new_file_path, content: 'puts 8' } ] @@ -449,7 +453,7 @@ RSpec.describe API::Commits do actions: [ { action: 'create', - file_path: 'foo/bar/baz.txt', + file_path: new_file_path, content: 'puts 🦊' } ] @@ -959,6 +963,7 @@ RSpec.describe API::Commits do end describe 'multiple operations' do + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } let(:message) { 'Multiple actions' } let(:invalid_mo_params) do { @@ -1028,17 +1033,11 @@ RSpec.describe API::Commits do } end - it 'are committed as one in project repo' do + it 'is committed as one in project repo and includes stats' do post api(url, user), params: valid_mo_params expect(response).to have_gitlab_http_status(:created) expect(json_response['title']).to eq(message) - end - - it 'includes the commit stats' do - post api(url, user), params: valid_mo_params - - expect(response).to have_gitlab_http_status(:created) expect(json_response).to include 'stats' end @@ -1124,7 +1123,8 @@ RSpec.describe API::Commits do end describe 'GET /projects/:id/repository/commits/:sha/refs' do - let(:project) { create(:project, :public, :repository) } + let_it_be(:project) { create(:project, :public, :repository) } + let(:tag) { project.repository.find_tag('v1.1.0') } let(:commit_id) { tag.dereferenced_target.id } let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/refs" } @@ -1139,6 +1139,8 @@ RSpec.describe API::Commits do end context 'when repository is disabled' do + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + include_context 'disabled repository' it_behaves_like '404 response' do @@ -1228,6 +1230,8 @@ RSpec.describe API::Commits do end context 'when repository is disabled' do + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + include_context 'disabled repository' it_behaves_like '404 response' do @@ -1269,8 +1273,14 @@ RSpec.describe API::Commits do end shared_examples_for 'ref with unaccessible pipeline' do - let!(:pipeline) do - create(:ci_empty_pipeline, project: project, status: :created, source: :push, ref: 'master', sha: commit.sha, protected: false) + let(:pipeline) do + create(:ci_empty_pipeline, + project: project, + status: :created, + source: :push, + ref: 'master', + sha: commit.sha, + protected: false) end it 'does not include last_pipeline' do @@ -1308,7 +1318,7 @@ RSpec.describe API::Commits do end context 'when unauthenticated', 'and project is public' do - let(:project) { create(:project, :public, :repository) } + let_it_be_with_reload(:project) { create(:project, :public, :repository) } it_behaves_like 'ref commit' it_behaves_like 'ref with pipeline' @@ -1338,6 +1348,7 @@ RSpec.describe API::Commits do context 'when builds are disabled' do before do project + .reload .project_feature .update!(builds_access_level: ProjectFeature::DISABLED) end @@ -1389,7 +1400,7 @@ RSpec.describe API::Commits do context 'with private builds' do before do - project.project_feature.update!(builds_access_level: ProjectFeature::PRIVATE) + project.reload.project_feature.update!(builds_access_level: ProjectFeature::PRIVATE) end it_behaves_like 'ref with pipeline' @@ -1415,8 +1426,8 @@ RSpec.describe API::Commits do end context 'when authenticated', 'as non_member and project is public' do - let(:current_user) { create(:user) } - let(:project) { create(:project, :public, :repository) } + let_it_be(:current_user) { create(:user) } + let_it_be_with_reload(:project) { create(:project, :public, :repository) } it_behaves_like 'ref with pipeline' @@ -1469,6 +1480,8 @@ RSpec.describe API::Commits do end context 'when repository is disabled' do + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + include_context 'disabled repository' it_behaves_like '404 response' do @@ -1478,7 +1491,7 @@ RSpec.describe API::Commits do end context 'when unauthenticated', 'and project is public' do - let(:project) { create(:project, :public, :repository) } + let_it_be(:project) { create(:project, :public, :repository) } it_behaves_like 'ref diff' end @@ -1568,6 +1581,8 @@ RSpec.describe API::Commits do end context 'when repository is disabled' do + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + include_context 'disabled repository' it_behaves_like '404 response' do @@ -1577,7 +1592,7 @@ RSpec.describe API::Commits do end context 'when unauthenticated', 'and project is public' do - let(:project) { create(:project, :public, :repository) } + let_it_be(:project) { create(:project, :public, :repository) } it_behaves_like 'ref comments' end @@ -1666,6 +1681,7 @@ RSpec.describe API::Commits do end describe 'POST :id/repository/commits/:sha/cherry_pick' do + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } let(:commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } let(:commit_id) { commit.id } let(:branch) { 'master' } @@ -1703,6 +1719,8 @@ RSpec.describe API::Commits do end context 'when repository is disabled' do + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + include_context 'disabled repository' it_behaves_like '404 response' do @@ -1712,7 +1730,7 @@ RSpec.describe API::Commits do end context 'when unauthenticated', 'and project is public' do - let(:project) { create(:project, :public, :repository) } + let_it_be(:project) { create(:project, :public, :repository) } it_behaves_like '403 response' do let(:request) { post api(route), params: { branch: 'master' } } @@ -1851,6 +1869,7 @@ RSpec.describe API::Commits do end describe 'POST :id/repository/commits/:sha/revert' do + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } let(:commit_id) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' } let(:commit) { project.commit(commit_id) } let(:branch) { 'master' } @@ -1891,7 +1910,7 @@ RSpec.describe API::Commits do end context 'when unauthenticated', 'and project is public' do - let(:project) { create(:project, :public, :repository) } + let_it_be(:project) { create(:project, :public, :repository) } it_behaves_like '403 response' do let(:request) { post api(route), params: { branch: branch } } @@ -1998,6 +2017,7 @@ RSpec.describe API::Commits do end describe 'POST /projects/:id/repository/commits/:sha/comments' do + let(:project) { create(:project, :repository, :private) } let(:commit) { project.repository.commit } let(:commit_id) { commit.id } let(:note) { 'My comment' } @@ -2018,6 +2038,8 @@ RSpec.describe API::Commits do end context 'when repository is disabled' do + let(:project) { create(:project, :repository, creator: user, path: 'my.project') } + include_context 'disabled repository' it_behaves_like '404 response' do @@ -2027,7 +2049,7 @@ RSpec.describe API::Commits do end context 'when unauthenticated', 'and project is public' do - let(:project) { create(:project, :public, :repository) } + let_it_be(:project) { create(:project, :public, :repository) } it_behaves_like '400 response' do let(:request) { post api(route), params: { note: 'My comment' } } @@ -2047,12 +2069,13 @@ RSpec.describe API::Commits do it_behaves_like 'ref new comment' it 'returns the inline comment' do - post api(route, current_user), params: { note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' } + path = project.repository.commit.raw_diffs.first.new_path + post api(route, current_user), params: { note: 'My comment', path: path, line: 1, line_type: 'new' } expect(response).to have_gitlab_http_status(:created) expect(response).to match_response_schema('public_api/v4/commit_note') expect(json_response['note']).to eq('My comment') - expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path) + expect(json_response['path']).to eq(path) expect(json_response['line']).to eq(1) expect(json_response['line_type']).to eq('new') end @@ -2127,7 +2150,8 @@ RSpec.describe API::Commits do end describe 'GET /projects/:id/repository/commits/:sha/merge_requests' do - let(:project) { create(:project, :repository, :private) } + let_it_be(:project) { create(:project, :repository, :private) } + let(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') } let(:commit) { merged_mr.merge_request_diff.commits.last } @@ -2159,7 +2183,8 @@ RSpec.describe API::Commits do end context 'public project' do - let(:project) { create(:project, :repository, :public, :merge_requests_private) } + let_it_be(:project) { create(:project, :repository, :public, :merge_requests_private) } + let(:non_member) { create(:user) } it 'responds 403 when only members are allowed to read merge requests' do diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb index 2aab402a1a0..3054b866812 100644 --- a/spec/requests/api/graphql/ci/runners_spec.rb +++ b/spec/requests/api/graphql/ci/runners_spec.rb @@ -69,15 +69,6 @@ RSpec.describe 'Query.runners' do it_behaves_like 'a working graphql query returning expected runner' end - - context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do - let(:runner_type) { 'PROJECT_TYPE' } - let(:status) { 'NEVER_CONTACTED' } - - let!(:expected_runner) { project_runner } - - it_behaves_like 'a working graphql query returning expected runner' - end end describe 'pagination' do diff --git a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb index 9ca39d4d32e..54d1cacc068 100644 --- a/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb +++ b/spec/services/ci/job_artifacts/destroy_batch_service_spec.rb @@ -221,6 +221,15 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do end context 'with update_stats: false' do + let_it_be(:extra_artifact_with_file) do + create(:ci_job_artifact, :zip, project: artifact_with_file.project) + end + + let(:artifacts) do + Ci::JobArtifact.where(id: [artifact_with_file.id, extra_artifact_with_file.id, + artifact_without_file.id, trace_artifact.id]) + end + it 'does not update project statistics' do expect(ProjectStatistics).not_to receive(:increment_statistic) @@ -230,7 +239,7 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do it 'returns size statistics' do expected_updates = { statistics_updates: { - artifact_with_file.project => -artifact_with_file.file.size, + artifact_with_file.project => -(artifact_with_file.file.size + extra_artifact_with_file.file.size), artifact_without_file.project => 0 } } diff --git a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb index 102c5e72c2c..4b753315bf7 100644 --- a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb +++ b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb @@ -5,6 +5,8 @@ require 'spec_helper' RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_redis_cache do using RSpec::Parameterized::TableSyntax + include_context 'for a cleanup tags service' + let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, :private) } @@ -39,268 +41,141 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_ describe '#execute' do subject { service.execute } - context 'when no params are specified' do - let(:params) { {} } - - it 'does not remove anything' do - expect_any_instance_of(Projects::ContainerRepository::DeleteTagsService) - .not_to receive(:execute) - expect_no_caching - - is_expected.to eq(expected_service_response(before_truncate_size: 0, after_truncate_size: 0, before_delete_size: 0)) - end - end - - context 'when regex matching everything is specified' do - shared_examples 'removes all matches' do - it 'does remove all tags except latest' do - expect_no_caching - - expect_delete(%w(A Ba Bb C D E)) - - is_expected.to eq(expected_service_response(deleted: %w(A Ba Bb C D E))) - end - end - - let(:params) do - { 'name_regex_delete' => '.*' } - end - - it_behaves_like 'removes all matches' - - context 'with deprecated name_regex param' do - let(:params) do - { 'name_regex' => '.*' } - end - - it_behaves_like 'removes all matches' - end - end - - context 'with invalid regular expressions' do - shared_examples 'handling an invalid regex' do - it 'keeps all tags' do - expect_no_caching - - expect(Projects::ContainerRepository::DeleteTagsService) - .not_to receive(:new) - - subject - end - - it { is_expected.to eq(status: :error, message: 'invalid regex') } - - it 'calls error tracking service' do - expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original - - subject - end - end - - context 'when name_regex_delete is invalid' do - let(:params) { { 'name_regex_delete' => '*test*' } } - - it_behaves_like 'handling an invalid regex' - end - - context 'when name_regex is invalid' do - let(:params) { { 'name_regex' => '*test*' } } - - it_behaves_like 'handling an invalid regex' - end - - context 'when name_regex_keep is invalid' do - let(:params) { { 'name_regex_keep' => '*test*' } } - - it_behaves_like 'handling an invalid regex' - end - end - - context 'when delete regex matching specific tags is used' do - let(:params) do - { 'name_regex_delete' => 'C|D' } - end - - it 'does remove C and D' do - expect_delete(%w(C D)) - - expect_no_caching - - is_expected.to eq(expected_service_response(deleted: %w(C D), before_truncate_size: 2, after_truncate_size: 2, before_delete_size: 2)) - end - - context 'with overriding allow regex' do - let(:params) do - { 'name_regex_delete' => 'C|D', - 'name_regex_keep' => 'C' } - end - - it 'does not remove C' do - expect_delete(%w(D)) - - expect_no_caching - - is_expected.to eq(expected_service_response(deleted: %w(D), before_truncate_size: 1, after_truncate_size: 1, before_delete_size: 1)) - end - end - - context 'with name_regex_delete overriding deprecated name_regex' do - let(:params) do - { 'name_regex' => 'C|D', - 'name_regex_delete' => 'D' } - end - - it 'does not remove C' do - expect_delete(%w(D)) - - expect_no_caching - - is_expected.to eq(expected_service_response(deleted: %w(D), before_truncate_size: 1, after_truncate_size: 1, before_delete_size: 1)) - end - end - end - - context 'with allow regex value' do - let(:params) do - { 'name_regex_delete' => '.*', - 'name_regex_keep' => 'B.*' } - end - - it 'does not remove B*' do - expect_delete(%w(A C D E)) - - expect_no_caching - - is_expected.to eq(expected_service_response(deleted: %w(A C D E), before_truncate_size: 4, after_truncate_size: 4, before_delete_size: 4)) - end - end - - context 'when keeping only N tags' do - let(:params) do - { 'name_regex' => 'A|B.*|C', - 'keep_n' => 1 } - end - - it 'sorts tags by date' do - expect_delete(%w(Bb Ba C)) - - expect_no_caching - - expect(service).to receive(:order_by_date).and_call_original - - is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_truncate_size: 4, after_truncate_size: 4, before_delete_size: 3)) - end - end - - context 'when not keeping N tags' do - let(:params) do - { 'name_regex' => 'A|B.*|C' } - end - - it 'does not sort tags by date' do - expect_delete(%w(A Ba Bb C)) - - expect_no_caching - - expect(service).not_to receive(:order_by_date) - - is_expected.to eq(expected_service_response(deleted: %w(A Ba Bb C), before_truncate_size: 4, after_truncate_size: 4, before_delete_size: 4)) - end - end - - context 'when removing keeping only 3' do - let(:params) do - { 'name_regex_delete' => '.*', - 'keep_n' => 3 } - end - - it 'does remove B* and C as they are the oldest' do - expect_delete(%w(Bb Ba C)) - - expect_no_caching - - is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3)) - end - end - - context 'when removing older than 1 day' do - let(:params) do - { 'name_regex_delete' => '.*', - 'older_than' => '1 day' } - end - - it 'does remove B* and C as they are older than 1 day' do - expect_delete(%w(Ba Bb C)) - - expect_no_caching - - is_expected.to eq(expected_service_response(deleted: %w(Ba Bb C), before_delete_size: 3)) - end - end - - context 'when combining all parameters' do + it_behaves_like 'handling invalid params', + service_response_extra: { + before_truncate_size: 0, + after_truncate_size: 0, + before_delete_size: 0, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'when regex matching everything is specified', + delete_expectations: [%w(A Ba Bb C D E)], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 6, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'when delete regex matching specific tags is used', + service_response_extra: { + before_truncate_size: 2, + after_truncate_size: 2, + before_delete_size: 2, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex', + service_response_extra: { + before_truncate_size: 1, + after_truncate_size: 1, + before_delete_size: 1, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'with allow regex value', + delete_expectations: [%w(A C D E)], + service_response_extra: { + before_truncate_size: 4, + after_truncate_size: 4, + before_delete_size: 4, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'when keeping only N tags', + delete_expectations: [%w(Bb Ba C)], + service_response_extra: { + before_truncate_size: 4, + after_truncate_size: 4, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'when not keeping N tags', + delete_expectations: [%w(A Ba Bb C)], + service_response_extra: { + before_truncate_size: 4, + after_truncate_size: 4, + before_delete_size: 4, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'when removing keeping only 3', + delete_expectations: [%w(Bb Ba C)], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'when removing older than 1 day', + delete_expectations: [%w(Ba Bb C)], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'when combining all parameters', + delete_expectations: [%w(Bb Ba C)], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true + + it_behaves_like 'when running a container_expiration_policy', + delete_expectations: [%w(Bb Ba C)], + service_response_extra: { + before_truncate_size: 6, + after_truncate_size: 6, + before_delete_size: 3, + cached_tags_count: 0 + }, + supports_caching: true + + context 'when running a container_expiration_policy with caching' do + let(:user) { nil } let(:params) do - { 'name_regex_delete' => '.*', + { + 'name_regex_delete' => '.*', 'keep_n' => 1, - 'older_than' => '1 day' } + 'older_than' => '1 day', + 'container_expiration_policy' => true + } end - it 'does remove B* and C' do - expect_delete(%w(Bb Ba C)) - - expect_no_caching + it 'expects caching to be used' do + expect_delete(%w(Bb Ba C), container_expiration_policy: true) + expect_caching - is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3)) + subject end - end - - context 'when running a container_expiration_policy' do - let(:user) { nil } - - context 'with valid container_expiration_policy param' do - let(:params) do - { 'name_regex_delete' => '.*', - 'keep_n' => 1, - 'older_than' => '1 day', - 'container_expiration_policy' => true } - end + context 'when setting set to false' do before do - expect_delete(%w(Bb Ba C), container_expiration_policy: true) + stub_application_setting(container_registry_expiration_policies_caching: false) end - it { is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3)) } - - context 'caching' do - it 'expects caching to be used' do - expect_caching - - subject - end - - context 'when setting set to false' do - before do - stub_application_setting(container_registry_expiration_policies_caching: false) - end - - it 'does not use caching' do - expect_no_caching - - subject - end - end - end - end - - context 'without container_expiration_policy param' do - let(:params) do - { 'name_regex_delete' => '.*', - 'keep_n' => 1, - 'older_than' => '1 day' } - end + it 'does not use caching' do + expect_delete(%w(Bb Ba C), container_expiration_policy: true) + expect_no_caching - it 'fails' do - is_expected.to eq(status: :error, message: 'access denied') + subject end end end @@ -322,10 +197,12 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_ service_response = expected_service_response( status: status, original_size: original_size, + deleted: nil + ).merge( before_truncate_size: before_truncate_size, after_truncate_size: after_truncate_size, before_delete_size: before_delete_size, - deleted: nil + cached_tags_count: 0 ) expect(result).to eq(service_response) @@ -483,34 +360,6 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_ end end - def expect_delete(tags, container_expiration_policy: nil) - expect(Projects::ContainerRepository::DeleteTagsService) - .to receive(:new) - .with(repository.project, user, tags: tags, container_expiration_policy: container_expiration_policy) - .and_call_original - - expect_any_instance_of(Projects::ContainerRepository::DeleteTagsService) - .to receive(:execute) - .with(repository) { { status: :success, deleted: tags } } - end - - # all those -1 because the default tags on L13 have a "latest" that will be filtered out - def expected_service_response(status: :success, deleted: [], original_size: tags.size, before_truncate_size: tags.size - 1, after_truncate_size: tags.size - 1, before_delete_size: tags.size - 1) - { - status: status, - deleted: deleted, - original_size: original_size, - before_truncate_size: before_truncate_size, - after_truncate_size: after_truncate_size, - before_delete_size: before_delete_size, - cached_tags_count: 0 - }.compact.merge(deleted_size: deleted&.size) - end - - def expect_no_caching - expect(::Gitlab::Redis::Cache).not_to receive(:with) - end - def expect_caching ::Gitlab::Redis::Cache.with do |redis| expect(redis).to receive(:mget).and_call_original diff --git a/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb new file mode 100644 index 00000000000..609257ee779 --- /dev/null +++ b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do + using RSpec::Parameterized::TableSyntax + + include_context 'for a cleanup tags service' + + let_it_be(:user) { create(:user) } + let_it_be(:user) { create(:user) } + let_it_be(:project, reload: true) { create(:project, :private) } + + let(:repository) { create(:container_repository, :root, :import_done, project: project) } + let(:service) { described_class.new(repository, user, params) } + let(:tags) { %w[latest A Ba Bb C D E] } + + before do + project.add_maintainer(user) if user + + stub_container_registry_config(enabled: true) + + stub_const("#{described_class}::TAGS_PAGE_SIZE", tags_page_size) + + one_hour_ago = 1.hour.ago + five_days_ago = 5.days.ago + six_days_ago = 6.days.ago + one_month_ago = 1.month.ago + + stub_tags( + { + 'latest' => one_hour_ago, + 'A' => one_hour_ago, + 'Ba' => five_days_ago, + 'Bb' => six_days_ago, + 'C' => one_month_ago, + 'D' => nil, + 'E' => nil + } + ) + end + + describe '#execute' do + subject { service.execute } + + context 'with several tags pages' do + let(:tags_page_size) { 2 } + + it_behaves_like 'handling invalid params' + + it_behaves_like 'when regex matching everything is specified', + delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]] + + it_behaves_like 'when delete regex matching specific tags is used' + + it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex' + + it_behaves_like 'with allow regex value', + delete_expectations: [%w[A], %w[C D], %w[E]] + + it_behaves_like 'when keeping only N tags', + delete_expectations: [%w[Bb]] + + it_behaves_like 'when not keeping N tags', + delete_expectations: [%w[A], %w[Ba Bb], %w[C]] + + context 'when removing keeping only 3' do + let(:params) do + { + 'name_regex_delete' => '.*', + 'keep_n' => 3 + } + end + + it_behaves_like 'not removing anything' + end + + it_behaves_like 'when removing older than 1 day', + delete_expectations: [%w[Ba Bb], %w[C]] + + it_behaves_like 'when combining all parameters', + delete_expectations: [%w[Bb], %w[C]] + + it_behaves_like 'when running a container_expiration_policy', + delete_expectations: [%w[Bb], %w[C]] + + context 'with a timeout' do + let(:params) do + { 'name_regex_delete' => '.*' } + end + + it 'removes the first few pages' do + expect(service).to receive(:timeout?).and_return(false, true) + + expect_delete(%w[A]) + expect_delete(%w[Ba Bb]) + + response = expected_service_response(status: :error, deleted: %w[A Ba Bb], original_size: 4) + + is_expected.to eq(response) + end + end + end + + context 'with a single tags page' do + let(:tags_page_size) { 1000 } + + it_behaves_like 'handling invalid params' + + it_behaves_like 'when regex matching everything is specified', + delete_expectations: [%w[A Ba Bb C D E]] + + it_behaves_like 'when delete regex matching specific tags is used' + + it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex' + + it_behaves_like 'with allow regex value', + delete_expectations: [%w[A C D E]] + + it_behaves_like 'when keeping only N tags', + delete_expectations: [%w[Ba Bb C]] + + it_behaves_like 'when not keeping N tags', + delete_expectations: [%w[A Ba Bb C]] + + it_behaves_like 'when removing keeping only 3', + delete_expectations: [%w[Ba Bb C]] + + it_behaves_like 'when removing older than 1 day', + delete_expectations: [%w[Ba Bb C]] + + it_behaves_like 'when combining all parameters', + delete_expectations: [%w[Ba Bb C]] + + it_behaves_like 'when running a container_expiration_policy', + delete_expectations: [%w[Ba Bb C]] + end + end + + private + + def stub_tags(tags) + chunked = tags_page_size < tags.size + previous_last = nil + max_chunk_index = tags.size / tags_page_size + + tags.keys.in_groups_of(tags_page_size, false).each_with_index do |chunked_tag_names, index| + last = index == max_chunk_index + pagination_needed = chunked && !last + + response = { + pagination: pagination_needed ? pagination_with(last: chunked_tag_names.last) : {}, + response_body: chunked_tag_names.map do |name| + tag_raw_response(name, tags[name]) + end + } + + allow(repository.gitlab_api_client) + .to receive(:tags) + .with(repository.path, page_size: described_class::TAGS_PAGE_SIZE, last: previous_last) + .and_return(response) + previous_last = chunked_tag_names.last + end + end + + def pagination_with(last:) + { + next: { + uri: URI("http://test.org?last=#{last}") + } + } + end + + def tag_raw_response(name, timestamp) + timestamp_field = name.start_with?('B') ? 'updated_at' : 'created_at' + { + 'name' => name, + 'digest' => 'sha256:1234567890', + 'media_type' => 'application/vnd.oci.image.manifest.v1+json', + timestamp_field => timestamp&.iso8601 + } + end +end diff --git a/spec/support/shared_contexts/projects/container_repository/cleanup_tags_service_shared_context.rb b/spec/support/shared_contexts/projects/container_repository/cleanup_tags_service_shared_context.rb new file mode 100644 index 00000000000..c976bbe9212 --- /dev/null +++ b/spec/support/shared_contexts/projects/container_repository/cleanup_tags_service_shared_context.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +RSpec.shared_context 'for a cleanup tags service' do + def expected_service_response(status: :success, deleted: [], original_size: tags.size) + { + status: status, + deleted: deleted, + original_size: original_size, + before_delete_size: deleted&.size + }.compact.merge(deleted_size: deleted&.size) + end + + def expect_delete(tags, container_expiration_policy: nil) + service = instance_double('Projects::ContainerRepository::DeleteTagsService') + + expect(Projects::ContainerRepository::DeleteTagsService) + .to receive(:new) + .with(repository.project, user, tags: tags, container_expiration_policy: container_expiration_policy) + .and_return(service) + + expect(service).to receive(:execute) + .with(repository) { { status: :success, deleted: tags } } + end + + def expect_no_caching + expect(::Gitlab::Redis::Cache).not_to receive(:with) + end +end diff --git a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb new file mode 100644 index 00000000000..9c2d30a9c8c --- /dev/null +++ b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'handling invalid params' do |service_response_extra: {}, supports_caching: false| + context 'when no params are specified' do + let(:params) { {} } + + it_behaves_like 'not removing anything', + service_response_extra: service_response_extra, + supports_caching: supports_caching + end + + context 'with invalid regular expressions' do + shared_examples 'handling an invalid regex' do + it 'keeps all tags' do + expect(Projects::ContainerRepository::DeleteTagsService) + .not_to receive(:new) + expect_no_caching unless supports_caching + + subject + end + + it { is_expected.to eq(status: :error, message: 'invalid regex') } + + it 'calls error tracking service' do + expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original + + subject + end + end + + context 'when name_regex_delete is invalid' do + let(:params) { { 'name_regex_delete' => '*test*' } } + + it_behaves_like 'handling an invalid regex' + end + + context 'when name_regex is invalid' do + let(:params) { { 'name_regex' => '*test*' } } + + it_behaves_like 'handling an invalid regex' + end + + context 'when name_regex_keep is invalid' do + let(:params) { { 'name_regex_keep' => '*test*' } } + + it_behaves_like 'handling an invalid regex' + end + end +end + +RSpec.shared_examples 'when regex matching everything is specified' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { 'name_regex_delete' => '.*' } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations + + context 'with deprecated name_regex param' do + let(:params) do + { 'name_regex' => '.*' } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations + end +end + +RSpec.shared_examples 'when delete regex matching specific tags is used' do + |service_response_extra: {}, supports_caching: false| + let(:params) do + { 'name_regex_delete' => 'C|D' } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: [%w[C D]] +end + +RSpec.shared_examples 'when delete regex matching specific tags is used with overriding allow regex' do + |service_response_extra: {}, supports_caching: false| + let(:params) do + { + 'name_regex_delete' => 'C|D', + 'name_regex_keep' => 'C' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: [%w[D]] + + context 'with name_regex_delete overriding deprecated name_regex' do + let(:params) do + { + 'name_regex' => 'C|D', + 'name_regex_delete' => 'D' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: [%w[D]] + end +end + +RSpec.shared_examples 'with allow regex value' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { + 'name_regex_delete' => '.*', + 'name_regex_keep' => 'B.*' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations +end + +RSpec.shared_examples 'when keeping only N tags' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { + 'name_regex' => 'A|B.*|C', + 'keep_n' => 1 + } + end + + it 'sorts tags by date' do + delete_expectations.each { |expectation| expect_delete(expectation) } + expect_no_caching unless supports_caching + + expect(service).to receive(:order_by_date_desc).at_least(:once).and_call_original + + is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra)) + end +end + +RSpec.shared_examples 'when not keeping N tags' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { 'name_regex' => 'A|B.*|C' } + end + + it 'does not sort tags by date' do + delete_expectations.each { |expectation| expect_delete(expectation) } + expect_no_caching unless supports_caching + + expect(service).not_to receive(:order_by_date_desc) + + is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra)) + end +end + +RSpec.shared_examples 'when removing keeping only 3' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { 'name_regex_delete' => '.*', + 'keep_n' => 3 } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations +end + +RSpec.shared_examples 'when removing older than 1 day' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { + 'name_regex_delete' => '.*', + 'older_than' => '1 day' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations +end + +RSpec.shared_examples 'when combining all parameters' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { + 'name_regex_delete' => '.*', + 'keep_n' => 1, + 'older_than' => '1 day' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations +end + +RSpec.shared_examples 'when running a container_expiration_policy' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:user) { nil } + + context 'with valid container_expiration_policy param' do + let(:params) do + { + 'name_regex_delete' => '.*', + 'keep_n' => 1, + 'older_than' => '1 day', + 'container_expiration_policy' => true + } + end + + it 'removes the expected tags' do + delete_expectations.each { |expectation| expect_delete(expectation, container_expiration_policy: true) } + expect_no_caching unless supports_caching + + is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra)) + end + end + + context 'without container_expiration_policy param' do + let(:params) do + { + 'name_regex_delete' => '.*', + 'keep_n' => 1, + 'older_than' => '1 day' + } + end + + it 'fails' do + is_expected.to eq(status: :error, message: 'access denied') + end + end +end + +RSpec.shared_examples 'not removing anything' do |service_response_extra: {}, supports_caching: false| + it 'does not remove anything' do + expect(Projects::ContainerRepository::DeleteTagsService).not_to receive(:new) + expect_no_caching unless supports_caching + + is_expected.to eq(expected_service_response(deleted: []).merge(service_response_extra)) + end +end + +RSpec.shared_examples 'removing the expected tags' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + it 'removes the expected tags' do + delete_expectations.each { |expectation| expect_delete(expectation) } + expect_no_caching unless supports_caching + + is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra)) + end +end diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index 704a4bbe0b8..ca4dea90c55 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -227,6 +227,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false| let_it_be(:package10) { create(:rubygems_package, project: project) } let_it_be(:package11) { create(:helm_package, project: project) } let_it_be(:package12) { create(:terraform_module_package, project: project) } + let_it_be(:package13) { create(:rpm_package, project: project) } Packages::Package.package_types.keys.each do |package_type| context "for package type #{package_type}" do diff --git a/spec/views/groups/new.html.haml_spec.rb b/spec/views/groups/new.html.haml_spec.rb index 8b12cc42a88..5c7378e8dc7 100644 --- a/spec/views/groups/new.html.haml_spec.rb +++ b/spec/views/groups/new.html.haml_spec.rb @@ -25,4 +25,15 @@ RSpec.describe 'groups/new.html.haml' do expect(rendered).not_to have_checked_field('Just me') end end + + context 'when a subgroup' do + let_it_be(:group) { create(:group, :nested) } + + it 'renders the visibility level section' do + expect(rendered).to have_content('Visibility level') + expect(rendered).to have_field('Private') + expect(rendered).to have_field('Internal') + expect(rendered).to have_field('Public') + end + end end |