summaryrefslogtreecommitdiff
path: root/spec/services/projects/container_repository/delete_tags_service_spec.rb
blob: c3ae26b1f05b380da6cd7120a80a395c328d053f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Projects::ContainerRepository::DeleteTagsService do
  include_context 'container repository delete tags service shared context'

  let(:service) { described_class.new(project, user, params) }

  let_it_be(:available_service_classes) do
    [
      ::Projects::ContainerRepository::Gitlab::DeleteTagsService,
      ::Projects::ContainerRepository::ThirdParty::DeleteTagsService
    ]
  end

  RSpec.shared_examples 'logging a success response' do
    it 'logs an info message' do
      expect(service).to receive(:log_info).with(
        service_class: 'Projects::ContainerRepository::DeleteTagsService',
        message: 'deleted tags',
        container_repository_id: repository.id,
        deleted_tags_count: tags.size
      )

      subject
    end
  end

  RSpec.shared_examples 'logging an error response' do |message: 'could not delete tags'|
    it 'logs an error message' do
      expect(service).to receive(:log_error).with(
        service_class: 'Projects::ContainerRepository::DeleteTagsService',
        message: message,
        container_repository_id: repository.id
      )

      subject
    end
  end

  RSpec.shared_examples 'calling the correct delete tags service' do |expected_service_class|
    let(:service_response) { { status: :success, deleted: tags } }
    let(:excluded_service_class) { available_service_classes.excluding(expected_service_class).first }

    before do
      service_double = double
      expect(expected_service_class).to receive(:new).with(repository, tags).and_return(service_double)
      expect(excluded_service_class).not_to receive(:new)
      expect(service_double).to receive(:execute).and_return(service_response)
    end

    it { is_expected.to include(status: :success) }

    it_behaves_like 'logging a success response'

    context 'with an error service response' do
      let(:service_response) { { status: :error, message: 'could not delete tags' } }

      it { is_expected.to include(status: :error) }

      it_behaves_like 'logging an error response'
    end
  end

  RSpec.shared_examples 'handling invalid params' do
    context 'with invalid params' do
      before do
        expect(::Projects::ContainerRepository::Gitlab::DeleteTagsService).not_to receive(:new)
        expect(::Projects::ContainerRepository::ThirdParty::DeleteTagsService).not_to receive(:new)
        expect_any_instance_of(ContainerRegistry::Client).not_to receive(:delete_repository_tag_by_name)
      end

      context 'when no params are specified' do
        let_it_be(:params) { {} }

        it { is_expected.to include(status: :error) }
      end

      context 'with empty tags' do
        let_it_be(:tags) { [] }

        it { is_expected.to include(status: :error) }
      end
    end
  end

  RSpec.shared_examples 'supporting fast delete' do
    context 'when the registry supports fast delete' do
      before do
        allow(repository.client).to receive(:supports_tag_delete?).and_return(true)
      end

      it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::Gitlab::DeleteTagsService

      it_behaves_like 'handling invalid params'

      context 'with the real service' do
        before do
          stub_delete_reference_requests(tags)
          expect_delete_tag_by_names(tags)
        end

        it { is_expected.to include(status: :success) }

        it_behaves_like 'logging a success response'
      end

      context 'with a timeout error' do
        before do
          expect_next_instance_of(::Projects::ContainerRepository::Gitlab::DeleteTagsService) do |delete_service|
            expect(delete_service).to receive(:delete_tags).and_raise(::Projects::ContainerRepository::Gitlab::DeleteTagsService::TimeoutError)
          end
        end

        it { is_expected.to include(status: :error, message: 'timeout while deleting tags') }

        it_behaves_like 'logging an error response', message: 'timeout while deleting tags'
      end
    end
  end

  describe '#execute' do
    let(:tags) { %w[A Ba] }

    subject { service.execute(repository) }

    before do
      stub_feature_flags(container_registry_expiration_policies_throttling: false)
    end

    context 'without permissions' do
      it { is_expected.to include(status: :error) }
    end

    context 'with permissions' do
      before do
        project.add_developer(user)
      end

      it_behaves_like 'supporting fast delete'

      context 'when the registry does not support fast delete' do
        before do
          allow(repository.client).to receive(:supports_tag_delete?).and_return(false)
        end

        it_behaves_like 'calling the correct delete tags service', ::Projects::ContainerRepository::ThirdParty::DeleteTagsService

        it_behaves_like 'handling invalid params'
      end
    end

    context 'without user' do
      let_it_be(:user) { nil }

      context 'when not run by a cleanup policy' do
        it { is_expected.to include(status: :error) }
      end

      context 'when run by a cleanup policy' do
        let(:params) { { tags: tags, container_expiration_policy: true } }

        it_behaves_like 'supporting fast delete'
      end
    end
  end
end