|
11 | 11 | let(:developer_headers) { headers_for(developer, user_name: user_name) } |
12 | 12 | let(:user_name) { 'sundance kid' } |
13 | 13 |
|
| 14 | + let(:guid) { droplet_model.guid } |
| 15 | + let(:package_model) { VCAP::CloudController::PackageModel.make(app_guid: app_model.guid) } |
| 16 | + let(:app_guid) { droplet_model.app_guid } |
| 17 | + |
14 | 18 | let(:parsed_response) { MultiJson.load(last_response.body) } |
15 | 19 |
|
16 | 20 | describe 'POST /v3/droplets' do |
|
189 | 193 | end |
190 | 194 |
|
191 | 195 | describe 'GET /v3/droplets/:guid' do |
192 | | - let(:guid) { droplet_model.guid } |
193 | | - let(:package_model) { VCAP::CloudController::PackageModel.make(app_guid: app_model.guid) } |
194 | | - let(:app_guid) { droplet_model.app_guid } |
195 | | - |
196 | 196 | context 'when the droplet has a buildpack lifecycle' do |
197 | 197 | let!(:droplet_model) do |
198 | 198 | VCAP::CloudController::DropletModel.make( |
|
239 | 239 | 'self' => { 'href' => "#{link_prefix}/v3/droplets/#{guid}" }, |
240 | 240 | 'package' => { 'href' => "#{link_prefix}/v3/packages/#{package_model.guid}" }, |
241 | 241 | 'app' => { 'href' => "#{link_prefix}/v3/apps/#{app_guid}" }, |
| 242 | + 'download' => { 'href' => "#{link_prefix}/v3/droplets/#{guid}/download", 'experimental' => true }, |
242 | 243 | 'assign_current_droplet' => { 'href' => "#{link_prefix}/v3/apps/#{app_guid}/relationships/current_droplet", 'method' => 'PATCH' }, |
243 | 244 | }, |
244 | 245 | 'metadata' => { |
|
304 | 305 | 'self' => { 'href' => "#{link_prefix}/v3/droplets/#{guid}" }, |
305 | 306 | 'package' => { 'href' => "#{link_prefix}/v3/packages/#{package_model.guid}" }, |
306 | 307 | 'app' => { 'href' => "#{link_prefix}/v3/apps/#{app_guid}" }, |
| 308 | + 'download' => { 'href' => "#{link_prefix}/v3/droplets/#{guid}/download", 'experimental' => true }, |
307 | 309 | 'assign_current_droplet' => { 'href' => "#{link_prefix}/v3/apps/#{app_guid}/relationships/current_droplet", 'method' => 'PATCH' }, |
308 | 310 | }, |
309 | 311 | 'metadata' => { |
|
315 | 317 | end |
316 | 318 | end |
317 | 319 |
|
| 320 | + describe 'GET /v3/droplets/:guid/download' do |
| 321 | + let(:worlds_smallest_tgz_file) { "\x1f\x8b\x08\x00\x5e\xc2\xc6\x5e\x00\x03\x63\x60\x18\x05\xa3\x60\x14\x8c\x54\x00\x00\x2e\xaf\xb5\xef\x00\x04\x00\x00" } |
| 322 | + let!(:droplet_model) do |
| 323 | + VCAP::CloudController::DropletModel.make( |
| 324 | + state: VCAP::CloudController::DropletModel::AWAITING_UPLOAD_STATE, |
| 325 | + app_guid: app_model.guid, |
| 326 | + package_guid: package_model.guid, |
| 327 | + buildpack_receipt_buildpack: 'http://buildpack.git.url.com', |
| 328 | + error_description: 'example error', |
| 329 | + execution_metadata: 'some-data', |
| 330 | + droplet_hash: Digest::SHA1.hexdigest(worlds_smallest_tgz_file), |
| 331 | + sha256_checksum: 'some-sha-256', |
| 332 | + process_types: { 'web' => 'start-command' }, |
| 333 | + ) |
| 334 | + end |
| 335 | + |
| 336 | + let(:droplet_file) do |
| 337 | + File.join(Dir.mktmpdir(nil, '/tmp'), 'droplet.tgz') |
| 338 | + end |
| 339 | + let(:upload_body) do |
| 340 | + { |
| 341 | + bits_name: 'droplet.tgz', |
| 342 | + bits_path: droplet_file, |
| 343 | + } |
| 344 | + end |
| 345 | + let(:bits_download_url) { CloudController::DependencyLocator.instance.blobstore_url_generator.droplet_download_url(droplet_model) } |
| 346 | + |
| 347 | + context 'when the droplet is uploaded' do |
| 348 | + let(:api_call) { lambda { |user_headers| get "/v3/droplets/#{guid}/download", nil, user_headers } } |
| 349 | + let(:expected_codes_and_responses) do |
| 350 | + h = Hash.new( |
| 351 | + code: 302 |
| 352 | + ) |
| 353 | + h['org_auditor'] = { |
| 354 | + code: 404 |
| 355 | + } |
| 356 | + h['org_billing_manager'] = { |
| 357 | + code: 404 |
| 358 | + } |
| 359 | + h['no_role'] = { |
| 360 | + code: 404 |
| 361 | + } |
| 362 | + h.freeze |
| 363 | + end |
| 364 | + |
| 365 | + before do |
| 366 | + File.write(droplet_file, worlds_smallest_tgz_file) |
| 367 | + post "/v3/droplets/#{guid}/upload", upload_body.to_json, developer_headers |
| 368 | + expect(last_response).to have_status_code(202) |
| 369 | + successes, failures = Delayed::Worker.new.work_off |
| 370 | + expect(successes).to eq(1) |
| 371 | + expect(failures).to eq(0) |
| 372 | + end |
| 373 | + |
| 374 | + it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS |
| 375 | + |
| 376 | + it 'downloads the bit(s) for a droplet' do |
| 377 | + get "/v3/droplets/#{guid}/download", nil, developer_headers |
| 378 | + |
| 379 | + expect(last_response.status).to eq(302) |
| 380 | + expect(last_response.headers['Location']).to eq(bits_download_url) |
| 381 | + |
| 382 | + expected_metadata = { droplet_guid: droplet_model.guid }.to_json |
| 383 | + |
| 384 | + event = VCAP::CloudController::Event.last |
| 385 | + expect(event.values).to include({ |
| 386 | + type: 'audit.app.droplet.download', |
| 387 | + actor_username: user_name, |
| 388 | + metadata: expected_metadata, |
| 389 | + space_guid: space.guid, |
| 390 | + organization_guid: space.organization.guid |
| 391 | + }) |
| 392 | + end |
| 393 | + end |
| 394 | + |
| 395 | + context 'when the droplet cannot be found' do |
| 396 | + it 'returns 404 for the droplet' do |
| 397 | + get '/v3/droplets/some-bogus-guid/download', nil, developer_headers |
| 398 | + expect(last_response.status).to eq(404) |
| 399 | + expect(last_response.body).to include('Droplet not found') |
| 400 | + end |
| 401 | + end |
| 402 | + |
| 403 | + context "when the droplet hasn't finished uploading/processing" do |
| 404 | + it 'returns a 422 with a helpful error message' do |
| 405 | + get "/v3/droplets/#{guid}/download", nil, developer_headers |
| 406 | + expect(last_response.status).to eq(422) |
| 407 | + expect(last_response.body).to include('Only staged droplets can be downloaded.') |
| 408 | + end |
| 409 | + end |
| 410 | + end |
| 411 | + |
318 | 412 | describe 'GET /v3/droplets' do |
319 | 413 | let(:buildpack) { VCAP::CloudController::Buildpack.make } |
320 | 414 | let(:package_model) do |
|
449 | 543 | 'self' => { 'href' => "#{link_prefix}/v3/droplets/#{droplet2.guid}" }, |
450 | 544 | 'package' => { 'href' => "#{link_prefix}/v3/packages/#{package_model.guid}" }, |
451 | 545 | 'app' => { 'href' => "#{link_prefix}/v3/apps/#{app_model.guid}" }, |
| 546 | + 'download' => { 'href' => "#{link_prefix}/v3/droplets/#{droplet2.guid}/download", 'experimental' => true }, |
452 | 547 | 'assign_current_droplet' => { 'href' => "#{link_prefix}/v3/apps/#{app_model.guid}/relationships/current_droplet", 'method' => 'PATCH' }, |
453 | 548 | }, |
454 | 549 | 'metadata' => { |
|
845 | 940 | 'self' => { 'href' => "#{link_prefix}/v3/droplets/#{droplet2.guid}" }, |
846 | 941 | 'package' => { 'href' => "#{link_prefix}/v3/packages/#{package_model.guid}" }, |
847 | 942 | 'app' => { 'href' => "#{link_prefix}/v3/apps/#{app_model.guid}" }, |
| 943 | + 'download' => { 'href' => "#{link_prefix}/v3/droplets/#{droplet2.guid}/download", 'experimental' => true }, |
848 | 944 | 'assign_current_droplet' => { 'href' => "#{link_prefix}/v3/apps/#{app_model.guid}/relationships/current_droplet", 'method' => 'PATCH' }, |
849 | 945 | }, |
850 | 946 | 'metadata' => { |
|
1032 | 1128 | 'self' => { 'href' => "#{link_prefix}/v3/droplets/#{droplet2.guid}" }, |
1033 | 1129 | 'package' => { 'href' => "#{link_prefix}/v3/packages/#{package_model.guid}" }, |
1034 | 1130 | 'app' => { 'href' => "#{link_prefix}/v3/apps/#{app_model.guid}" }, |
| 1131 | + 'download' => { 'href' => "#{link_prefix}/v3/droplets/#{droplet2.guid}/download", 'experimental' => true }, |
1035 | 1132 | 'assign_current_droplet' => { 'href' => "#{link_prefix}/v3/apps/#{app_model.guid}/relationships/current_droplet", 'method' => 'PATCH' }, |
1036 | 1133 | }, |
1037 | 1134 | 'metadata' => { |
|
0 commit comments