File: release_spec.rb

package info (click to toggle)
ruby-puppet-forge 5.0.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,196 kB
  • sloc: ruby: 2,397; makefile: 3
file content (260 lines) | stat: -rw-r--r-- 9,631 bytes parent folder | download
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
require 'spec_helper'
require 'fileutils'

describe PuppetForge::V3::Release do
  context 'connection management' do
    before(:each) do
      PuppetForge::Connection.authorization = nil
      PuppetForge::Connection.proxy = nil
      described_class.conn = PuppetForge::V3::Base.conn(true)
    end

    after(:each) do
      PuppetForge::Connection.authorization = nil
      PuppetForge::Connection.proxy = nil
      described_class.conn = nil
    end

    describe 'setting authorization value after a connection is created' do
      it 'should reset connection' do
        old_conn = described_class.conn

        PuppetForge::Connection.authorization = 'post-init auth value'
        new_conn = described_class.conn

        expect(new_conn).to_not eq(old_conn)
        expect(new_conn.headers).to include(:authorization => 'post-init auth value')
      end
    end

    describe 'setting proxy value after a connection is created' do
      it 'should reset connection' do
        old_conn = described_class.conn

        PuppetForge::Connection.proxy = 'http://proxy.example.com:8888'
        new_conn = described_class.conn

        expect(new_conn).to_not eq(old_conn)
        expect(new_conn.proxy).to_not be_nil
        expect(new_conn.proxy.uri.to_s).to eq('http://proxy.example.com:8888')
      end
    end
  end

  context 'with stubbed connection' do
    before do
      stub_api_for(PuppetForge::V3::Base) do |stubs|
        stub_fixture(stubs, :get, '/v3/releases/puppetlabs-apache-0.0.1')
        stub_fixture(stubs, :get, '/v3/releases/absent-apache-0.0.1')
        stub_fixture(stubs, :get, '/v3/files/puppetlabs-apache-0.0.1.tar.gz')
        stub_fixture(stubs, :get, '/v3/modules/puppetlabs-apache')
        stub_fixture(stubs, :get, '/v3/releases?module=puppetlabs-apache')
      end
    end

    describe '::find' do
      let(:release) { PuppetForge::V3::Release.find('puppetlabs-apache-0.0.1') }
      let(:missing_release) { PuppetForge::V3::Release.find('absent-apache-0.0.1') }

      it 'can find releases that exist' do
        expect(release.version).to eql('0.0.1')
      end

      it 'raises Faraday::ResourceNotFound for non-existent releases' do
        expect { missing_release }.to raise_error(Faraday::ResourceNotFound)
      end
    end

    describe '#module' do
      let(:release) { PuppetForge::V3::Release.find('puppetlabs-apache-0.0.1') }

      it 'exposes the related module as a property' do
        expect(release.module).to_not be nil
      end

      it 'grants access to module attributes without an API call' do
        expect(PuppetForge::V3::Module).not_to receive(:request)
        expect(release.module.name).to eql('apache')
      end

      it 'transparently makes API calls for other attributes' do
        expect(PuppetForge::V3::Module).to receive(:request).once.and_call_original
        expect(release.module.created_at).to_not be nil
      end
    end

    describe '#download_url' do
      let(:release) { PuppetForge::V3::Release.find('puppetlabs-apache-0.0.1') }

      it 'handles an API response that does not include a scheme and host' do
        release.file_uri = '/v3/files/puppetlabs-apache-0.0.1.tar.gz'
        uri_with_host = URI.join(PuppetForge.host, '/v3/files/puppetlabs-apache-0.0.1.tar.gz').to_s
        expect(release.download_url).to eql(uri_with_host)
      end

      it 'handles an API response that includes a scheme and host' do
        release.file_uri = 'https://example.com/v3/files/puppetlabs-apache-0.0.1.tar.gz'
        expect(release.download_url).to eql('https://example.com/v3/files/puppetlabs-apache-0.0.1.tar.gz')
      end

      context 'when PuppetForge.host has a path prefix' do
        around(:each) do |spec|
          old_host = PuppetForge.host
          PuppetForge.host = 'http://example.com/forge/api/'

          spec.run

          PuppetForge.host = old_host
        end

        it 'includes path prefix in download url' do
          expect(release.download_url).to eql('http://example.com/forge/api/v3/files/puppetlabs-apache-0.0.1.tar.gz')
        end
      end
    end

    describe '#download' do
      let(:release) { PuppetForge::V3::Release.find('puppetlabs-apache-0.0.1') }
      let(:tarball) { "#{PROJECT_ROOT}/spec/tmp/module.tgz" }

      before { FileUtils.rm tarball rescue nil }
      after  { FileUtils.rm tarball rescue nil }

      it 'downloads the file to the specified location' do
        expect(File.exist?(tarball)).to be false
        release.download(Pathname.new(tarball))
        expect(File.exist?(tarball)).to be true
      end

      context 'when response is 403' do
        it "raises PuppetForge::ReleaseForbidden" do
          mock_conn = instance_double("PuppetForge::V3::Connection", :url_prefix => PuppetForge.host)
          allow(described_class).to receive(:conn).and_return(mock_conn)

          expect(mock_conn).to receive(:get).and_raise(Faraday::ClientError.new("403", {:status => 403, :body => ({:message => "Forbidden"}.to_json)}))

          expect { release.download(Pathname.new(tarball)) }.to raise_error(PuppetForge::ReleaseForbidden)
        end
      end

      context 'when connection fails' do
        it "re-raises original error" do
          mock_conn = instance_double("PuppetForge::V3::Connection", :url_prefix => PuppetForge.host)
          allow(described_class).to receive(:conn).and_return(mock_conn)

          expect(mock_conn).to receive(:get).and_raise(Faraday::ConnectionFailed.new("connection failed"))

          expect { release.download(Pathname.new(tarball)) }.to raise_error(Faraday::ConnectionFailed, /connection failed/)
        end
      end
    end

    describe '#verify' do
      let(:release) { PuppetForge::V3::Release.find('puppetlabs-apache-0.0.1') }
      let(:tarball) { "#{PROJECT_ROOT}/spec/tmp/module.tgz" }
      let(:allow_md5) { true }

      before(:each) do
        FileUtils.rm tarball rescue nil
        release.download(Pathname.new(tarball))
      end

      after(:each) { FileUtils.rm tarball rescue nil }

      context 'file_sha256 is available' do
        before(:each) do
          allow(release).to receive(:file_sha256).and_return("810ff2fb242a5dee4220f2cb0e6a519891fb67f2f828a6cab4ef8894633b1f50")
        end

        let(:mock_sha256) { double(Digest::SHA256, hexdigest: release.file_sha256) }

        it 'only verifies sha-256 checksum' do
          expect(Digest::SHA256).to receive(:file).and_return(mock_sha256)
          expect(Digest::MD5).not_to receive(:file)

          release.verify(tarball, allow_md5)
        end
      end

      context 'file_sha256 is not available' do
        let(:mock_md5) { double(Digest::MD5, hexdigest: release.file_md5) }

        it 'only verfies the md5 checksum' do
          expect(Digest::SHA256).not_to receive(:file)
          expect(Digest::MD5).to receive(:file).and_return(mock_md5)

          release.verify(tarball, allow_md5)
        end
      end

      context 'when allow_md5=false' do
        let(:allow_md5) { false }

        context 'file_sha256 is not available' do
          it 'raises an appropriate error' do
            expect(Digest::SHA256).not_to receive(:file)
            expect(Digest::MD5).not_to receive(:file)

            expect { release.verify(tarball, allow_md5) }.to raise_error(PuppetForge::Error, /cannot verify module release.*md5.*forbidden/i)
          end
        end
      end
    end

    describe '#metadata' do
      let(:release) { PuppetForge::V3::Release.find('puppetlabs-apache-0.0.1') }

      it 'is lazy and repeatable' do
        3.times do
          expect(release.module.releases.last.metadata).to_not be_nil
        end
      end
    end

    describe 'instance properies' do
      let(:release) { PuppetForge::V3::Release.find('puppetlabs-apache-0.0.1') }

      example 'are easily accessible' do
        expect(release.created_at).to_not be nil
      end
    end

    describe '#upload' do
      let(:tarball) { "#{PROJECT_ROOT}/spec/tmp/module.tgz" }
      let(:file_object) { double('file', read: 'file contents') }

      let(:release) { PuppetForge::V3::Release.upload(tarball) }
      let(:mock_conn) { instance_double('PuppetForge::V3::Connection', url_prefix: PuppetForge.host) }

      context 'when there is no auth token provided' do
        it 'raises PuppetForge::ReleaseForbidden' do
          allow(File).to receive(:file?).and_return(true)
          allow(File).to receive(:open).and_return(file_object)
          allow(described_class).to receive(:conn).and_return(mock_conn)

          response = { status: 403, body: { 'message' => 'Forbidden' }.to_json }
          expect(mock_conn).to receive(:post).and_raise(Faraday::ClientError.new('Forbidden', response))
          expect { release }.to raise_error(PuppetForge::ReleaseForbidden)
        end
      end

      context 'when the module is not valid' do
        it 'raises PuppetForge::ReleaseBadRequest' do
          allow(File).to receive(:file?).and_return(true)
          allow(File).to receive(:open).and_return(file_object)
          allow(described_class).to receive(:conn).and_return(mock_conn)

          response = { status: 400, body: { message: 'Bad Content' }.to_json }
          expect(mock_conn).to receive(:post).and_raise(Faraday::ClientError.new('400', response))
          expect { release }.to raise_error(PuppetForge::ReleaseBadContent)
        end
      end

      context 'when the tarball does not exist' do
        it 'raises PuppetForge::FileNotFound' do
          expect { PuppetForge::V3::Release.upload(tarball) }.to raise_error(PuppetForge::FileNotFound)
        end
      end
    end
  end
end