cloudfoundry/cloud_controller_ng

View on GitHub
spec/request/sidecars_spec.rb

Summary

Maintainability
C
1 day
Test Coverage
require 'spec_helper'
require 'request_spec_shared_examples'

RSpec.describe 'Sidecars' do
  let(:app_model) { VCAP::CloudController::AppModel.make }
  let(:user) { VCAP::CloudController::User.make }
  let(:user_header) { headers_for(user) }

  describe 'POST /v3/apps/:guid/sidecars' do
    before do
      app_model.space.organization.add_user(user)
      app_model.space.add_developer(user)
    end

    let(:sidecar_params) do
      {
        name: 'sidecar_one',
        command: 'bundle exec rackup',
        process_types: %w[web other_worker],
        memory_in_mb: 300
      }
    end

    it 'creates a sidecar for an app' do
      expect do
        post "/v3/apps/#{app_model.guid}/sidecars", sidecar_params.to_json, user_header
      end.to change(VCAP::CloudController::SidecarModel, :count).by(1)

      expect(last_response.status).to eq(201), last_response.body
      sidecar = VCAP::CloudController::SidecarModel.last

      expected_response = {
        'guid' => sidecar.guid,
        'name' => 'sidecar_one',
        'command' => 'bundle exec rackup',
        'process_types' => %w[other_worker web],
        'memory_in_mb' => 300,
        'origin' => 'user',
        'created_at' => iso8601,
        'updated_at' => iso8601,
        'relationships' => {
          'app' => {
            'data' => {
              'guid' => app_model.guid
            }
          }
        }
      }

      parsed_response = Oj.load(last_response.body)
      expect(parsed_response).to be_a_response_like(expected_response)
    end

    it 'logs sidecar create params to telemetry' do
      Timecop.freeze do
        expected_json = {
          'telemetry-source' => 'cloud_controller_ng',
          'telemetry-time' => Time.now.to_datetime.rfc3339,
          'create-sidecar' => {
            'api-version' => 'v3',
            'origin' => 'user',
            'memory-in-mb' => 300,
            'process-types' => %w[other_worker web],
            'app-id' => OpenSSL::Digest::SHA256.hexdigest(app_model.guid),
            'user-id' => OpenSSL::Digest::SHA256.hexdigest(user.guid)
          }
        }
        expect_any_instance_of(ActiveSupport::Logger).to receive(:info).with(Oj.dump(expected_json))
        post "/v3/apps/#{app_model.guid}/sidecars", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(201), last_response.body
      end
    end

    describe 'deleting an app with a sidecar' do
      it 'deletes the sidecar' do
        post "/v3/apps/#{app_model.guid}/sidecars", sidecar_params.to_json, user_header
        delete "/v3/apps/#{app_model.guid}", nil, user_header
        expect(last_response.status).to eq(202)
      end
    end

    describe 'long name' do
      let(:sidecar_params) do
        {
          name: 'a' * 256,
          command: 'bundle exec rackup',
          process_types: %w[web other_worker]
        }
      end

      it 'returns an error' do
        post "/v3/apps/#{app_model.guid}/sidecars", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(422)
        expect(parsed_response['errors'][0]['detail']).to eq 'Name is too long (maximum is 255 characters)'
      end
    end

    describe 'empty process_types' do
      let(:sidecar_params) do
        {
          name: 'my_sidecar',
          command: 'bundle exec rackup',
          process_types: []
        }
      end

      it 'returns an error' do
        post "/v3/apps/#{app_model.guid}/sidecars", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(422)
        expect(parsed_response['errors'][0]['detail']).to eq 'Process types must have at least 1 process_type'
      end
    end

    describe 'validates sidecar memory' do
      let!(:process) { VCAP::CloudController::ProcessModel.make(app_guid: app_model.guid, memory: 100, type: 'other_worker') }
      let(:sidecar_params) do
        {
          name: 'sidecar_one',
          command: 'bundle exec rackup',
          process_types: %w[web other_worker],
          memory_in_mb: 300
        }
      end

      it 'returns an error if the sidecar memory exceeds the process memory' do
        post "/v3/apps/#{app_model.guid}/sidecars", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(422)
        expect(parsed_response['errors'][0]['detail']).to eq 'The memory allocation defined is too large to run with the dependent "other_worker" process'
      end
    end

    context 'permissions' do
      let(:org) { app_model.space.organization }
      let(:space) { app_model.space }
      let(:api_call) { ->(user_headers) { post "/v3/apps/#{app_model.guid}/sidecars", sidecar_params.to_json, user_headers } }

      before do
        space.remove_developer(user)
        org.remove_user(user)
      end

      let(:expected_codes_and_responses) do
        h = Hash.new(code: 403, errors: CF_NOT_AUTHORIZED)
        %w[no_role org_auditor org_billing_manager].each { |r| h[r] = { code: 404 } }
        %w[admin space_developer].each { |r| h[r] = { code: 201 } }
        h
      end

      it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS

      context 'when organization is suspended' do
        let(:expected_codes_and_responses) do
          h = super()
          h['space_developer'] = { code: 403, errors: CF_ORG_SUSPENDED }
          h
        end

        before do
          org.update(status: VCAP::CloudController::Organization::SUSPENDED)
        end

        it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
      end
    end
  end

  describe 'PATCH /v3/apps/:guid/sidecars' do
    before do
      app_model.space.organization.add_user(user)
      app_model.space.add_developer(user)
    end

    let!(:sidecar) { VCAP::CloudController::SidecarModel.make(name: 'My sidecar', command: 'rackdown', app: app_model, memory: 400) }
    let!(:sidecar_process_type) do
      VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar, type: 'other_worker', app_guid: app_model.guid)
    end

    let(:sidecar_params) do
      {
        name: 'my_sidecar_2',
        command: 'rackup',
        process_types: ['sidecar_process'],
        memory_in_mb: 300
      }
    end

    it 'updates sidecar' do
      expected_response = {
        'guid' => sidecar.guid,
        'name' => 'my_sidecar_2',
        'command' => 'rackup',
        'process_types' => ['sidecar_process'],
        'memory_in_mb' => 300,
        'origin' => 'user',
        'created_at' => iso8601,
        'updated_at' => iso8601,
        'relationships' => {
          'app' => {
            'data' => {
              'guid' => app_model.guid
            }
          }
        }
      }
      patch "/v3/sidecars/#{sidecar.guid}", sidecar_params.to_json, user_header

      expect(last_response.status).to eq(200)
      parsed_response = Oj.load(last_response.body)
      expect(parsed_response).to be_a_response_like(expected_response)
    end

    describe 'partial updates' do
      let(:sidecar_params) do
        { command: 'bundle exec rackup' }
      end

      it 'partially updates the sidecar' do
        expected_response = {
          'guid' => sidecar.guid,
          'name' => 'My sidecar',
          'command' => 'bundle exec rackup',
          'process_types' => ['other_worker'],
          'memory_in_mb' => 400,
          'origin' => 'user',
          'created_at' => iso8601,
          'updated_at' => iso8601,
          'relationships' => {
            'app' => {
              'data' => {
                'guid' => app_model.guid
              }
            }
          }
        }

        patch "/v3/sidecars/#{sidecar.guid}", sidecar_params.to_json, user_header

        expect(last_response.status).to eq(200)
        parsed_response = Oj.load(last_response.body)
        expect(parsed_response).to be_a_response_like(expected_response)
      end
    end

    describe 'duplicate name' do
      let!(:other_sidecar) { VCAP::CloudController::SidecarModel.make(name: 'other sidecar', command: 'rackdown', app: app_model) }

      let(:sidecar_params) do
        { name: 'My sidecar' }
      end

      it 'returns an error' do
        patch "/v3/sidecars/#{other_sidecar.guid}", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(422)
        expect(parsed_response['errors'][0]['detail']).to eq "Sidecar with name 'My sidecar' already exists for given app"
      end
    end

    describe 'long commands' do
      let(:sidecar_params) do
        { command: 'b' * 4097 }
      end

      it 'returns an error' do
        patch "/v3/sidecars/#{sidecar.guid}", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(422)
        expect(parsed_response['errors'][0]['detail']).to eq 'Command is too long (maximum is 4096 characters)'
      end
    end

    describe 'long name' do
      let(:sidecar_params) do
        { name: 'b' * 256 }
      end

      it 'returns an error' do
        patch "/v3/sidecars/#{sidecar.guid}", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(422)
        expect(parsed_response['errors'][0]['detail']).to eq 'Name is too long (maximum is 255 characters)'
      end
    end

    describe 'long process types' do
      let(:sidecar_params) do
        { process_types: ['b' * 256] }
      end

      it 'returns an error' do
        patch "/v3/sidecars/#{sidecar.guid}", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(422)
        expect(parsed_response['errors'][0]['detail']).to eq 'Process type is too long (maximum is 255 characters)'
      end
    end

    describe 'empty process_types' do
      let(:sidecar_params) do
        { process_types: [] }
      end

      it 'returns an error' do
        patch "/v3/sidecars/#{sidecar.guid}", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(422)
        expect(parsed_response['errors'][0]['detail']).to eq 'Process types must have at least 1 process_type'
      end
    end

    describe 'when the sidecar is not found' do
      it 'returns 404' do
        patch '/v3/sidecars/doesntexist', sidecar_params.to_json, user_header
        expect(last_response.status).to eq(404)
      end
    end

    describe 'validates sidecar memory' do
      let!(:process) { VCAP::CloudController::ProcessModel.make(app_guid: app_model.guid, memory: 500, type: 'other_worker') }
      let(:sidecar_params) do
        {
          name: 'sidecar_one',
          command: 'bundle exec rackup',
          process_types: %w[web other_worker],
          memory_in_mb: 600
        }
      end

      it 'returns an error if the sidecar memory exceeds the process memory' do
        patch "/v3/sidecars/#{sidecar.guid}", sidecar_params.to_json, user_header
        expect(last_response.status).to eq(422)
        expect(parsed_response['errors'][0]['detail']).to eq 'The memory allocation defined is too large to run with the dependent "other_worker" process'
      end
    end

    context 'permissions' do
      let(:org) { app_model.space.organization }
      let(:space) { app_model.space }
      let(:api_call) { ->(user_headers) { patch "/v3/sidecars/#{sidecar.guid}", sidecar_params.to_json, user_headers } }

      before do
        space.remove_developer(user)
        org.remove_user(user)
      end

      let(:expected_codes_and_responses) do
        h = Hash.new(code: 403, errors: CF_NOT_AUTHORIZED)
        %w[no_role org_auditor org_billing_manager].each { |r| h[r] = { code: 404 } }
        %w[admin space_developer].each { |r| h[r] = { code: 200 } }
        h
      end

      it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS

      context 'when organization is suspended' do
        let(:expected_codes_and_responses) do
          h = super()
          h['space_developer'] = { code: 403, errors: CF_ORG_SUSPENDED }
          h
        end

        before do
          org.update(status: VCAP::CloudController::Organization::SUSPENDED)
        end

        it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
      end
    end
  end

  describe 'GET /v3/sidecars/:guid' do
    let(:sidecar) { VCAP::CloudController::SidecarModel.make(app: app_model, name: 'sidecar', command: 'smarch', memory: 300) }
    let!(:sidecar_spider) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar, type: 'spider') }
    let!(:sidecar_web) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar, type: 'web') }

    context 'as a permitted user' do
      before do
        app_model.space.organization.add_user(user)
        app_model.space.add_developer(user)
      end

      it 'gets the sidecar in the expected format' do
        get "/v3/sidecars/#{sidecar.guid}", nil, user_header

        expected_response = {
          'guid' => sidecar.guid,
          'name' => 'sidecar',
          'command' => 'smarch',
          'process_types' => %w[spider web],
          'memory_in_mb' => 300,
          'origin' => 'user',
          'created_at' => iso8601,
          'updated_at' => iso8601,
          'relationships' => {
            'app' => {
              'data' => {
                'guid' => app_model.guid
              }
            }
          }
        }

        expect(last_response.status).to eq(200), last_response.body
        parsed_response = Oj.load(last_response.body)
        expect(parsed_response).to be_a_response_like(expected_response)
      end
    end

    context 'permissions' do
      let(:api_call) { ->(user_headers) { get "/v3/sidecars/#{sidecar.guid}", nil, user_headers } }
      let(:org) { app_model.organization }
      let(:space) { app_model.space }

      let(:expected_codes_and_responses) do
        h = Hash.new(code: 200, response_guid: sidecar.guid)
        h['no_role'] = {
          code: 404
        }
        h['org_auditor'] = {
          code: 404
        }
        h['org_billing_manager'] = {
          code: 404
        }
        h
      end

      it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
    end
  end

  describe 'GET /v3/processes/:process_guid/sidecars' do
    let!(:sidecar1a) { VCAP::CloudController::SidecarModel.make(app: app_model, name: 'sidecar1a', command: 'missile1a') }
    let!(:sidecar_worker1a) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar1a, type: 'worker') }
    let!(:sidecar_web1a) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar1a, type: 'web') }

    let!(:sidecar1b) { VCAP::CloudController::SidecarModel.make(app: app_model, name: 'sidecar1b', command: 'missile1b') }
    let!(:sidecar_worker1b) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar1b, type: 'worker') }
    let!(:sidecar_web1b) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar1b, type: 'web') }

    let!(:sidecar1c) { VCAP::CloudController::SidecarModel.make(app: app_model, name: 'sidecar1c', command: 'missile1c') }
    let!(:sidecar_worker1c) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar1c, type: 'worker') }
    let!(:sidecar_web1c) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar1c, type: 'web') }

    let!(:sidecar1d) { VCAP::CloudController::SidecarModel.make(app: app_model, name: 'sidecar1d', command: 'missile1d') }
    let!(:sidecar_worker1d) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar1d, type: 'fish') }
    let!(:sidecar_web1d) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar1d, type: 'cows') }

    let!(:process1) do
      VCAP::CloudController::ProcessModel.make(
        :process,
        app: app_model,
        type: 'web',
        command: 'rackup'
      )
    end

    let!(:app_model2) { VCAP::CloudController::AppModel.make(space: app_model.space, name: 'app2') }
    let!(:sidecar_for_app2) { VCAP::CloudController::SidecarModel.make(app: app_model2, name: 'sidecar2', command: 'missile2') }
    let!(:sidecar_worker2) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar_for_app2, type: 'worker') }
    let!(:sidecar_web2) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar_for_app2, type: 'web') }
    let!(:process2) do
      VCAP::CloudController::ProcessModel.make(
        :process,
        app: app_model2,
        type: 'web',
        command: 'rackup'
      )
    end

    context 'as a space developer' do
      before do
        app_model.space.organization.add_user(user)
        app_model.space.add_developer(user)
      end

      it_behaves_like 'list query endpoint' do
        let(:request) { "/v3/processes/#{process1.guid}/sidecars" }
        let(:message) { VCAP::CloudController::SidecarsListMessage }

        let(:params) do
          {
            page: '2',
            per_page: '10',
            order_by: 'updated_at',
            guids: "#{process1.guid},bogus",
            created_ats: "#{Time.now.utc.iso8601},#{Time.now.utc.iso8601}",
            updated_ats: { gt: Time.now.utc.iso8601 }
          }
        end
      end

      it "retrieves the process' sidecars" do
        get "/v3/processes/#{process1.guid}/sidecars?per_page=2", nil, user_header

        expected_response = {
          'pagination' => {
            'total_results' => 3,
            'total_pages' => 2,
            'first' => { 'href' => "#{link_prefix}/v3/processes/#{process1.guid}/sidecars?page=1&per_page=2" },
            'last' => { 'href' => "#{link_prefix}/v3/processes/#{process1.guid}/sidecars?page=2&per_page=2" },
            'next' => { 'href' => "#{link_prefix}/v3/processes/#{process1.guid}/sidecars?page=2&per_page=2" },
            'previous' => nil
          },
          'resources' => [
            {
              'guid' => sidecar1a.guid,
              'name' => 'sidecar1a',
              'command' => 'missile1a',
              'process_types' => %w[web worker],
              'memory_in_mb' => nil,
              'origin' => 'user',
              'relationships' => {
                'app' => {
                  'data' => {
                    'guid' => app_model.guid
                  }
                }
              },
              'created_at' => iso8601,
              'updated_at' => iso8601
            },
            {
              'guid' => sidecar1b.guid,
              'name' => 'sidecar1b',
              'command' => 'missile1b',
              'process_types' => %w[web worker],
              'memory_in_mb' => nil,
              'origin' => 'user',
              'relationships' => {
                'app' => {
                  'data' => {
                    'guid' => app_model.guid
                  }
                }
              },
              'created_at' => iso8601,
              'updated_at' => iso8601
            }
          ]
        }

        expect(last_response.status).to eq(200), last_response.body
        parsed_response = Oj.load(last_response.body)
        expect(parsed_response).to be_a_response_like(expected_response)
      end

      context 'when the process does not exist' do
        it 'returns a 404 error' do
          get '/v3/processes/fake-process-guid/sidecars?per_page=2', nil, user_header
          expect(last_response.status).to eq(404), last_response.body
        end
      end

      context 'filtering on created_ats and updated_ats' do
        let(:app_model3) { VCAP::CloudController::AppModel.make }
        let!(:process3) do
          VCAP::CloudController::ProcessModel.make(
            :process,
            app: app_model3,
            type: 'web',
            command: 'rackup'
          )
        end

        it_behaves_like 'list_endpoint_with_common_filters' do
          let(:resource_klass) { VCAP::CloudController::SidecarModel }
          let(:additional_resource_params) { { app: app_model3 } }
          let(:headers) { admin_headers }
          let(:api_call) do
            app_model3.sidecars_dataset.each do |sidecar|
              VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar, type: 'web')
            end
            ->(headers, filters) { get "/v3/processes/#{process3.guid}/sidecars?#{filters}", nil, headers }
          end
        end
      end
    end

    describe 'permissions' do
      let(:api_call) { ->(user_headers) { get "/v3/processes/#{process1.guid}/sidecars", nil, user_headers } }
      let(:org) { app_model.organization }
      let(:space) { app_model.space }

      let(:expected_codes_and_responses) do
        h = Hash.new(code: 404)
        h['admin'] = {
          code: 200
        }
        h['admin_read_only'] = {
          code: 200
        }
        h['space_developer'] = {
          code: 200
        }
        h['space_supporter'] = {
          code: 200
        }
        h['global_auditor'] = {
          code: 200
        }
        h['space_manager'] = {
          code: 200
        }
        h['space_auditor'] = {
          code: 200
        }
        h['org_manager'] = {
          code: 200
        }
        h
      end

      it_behaves_like 'permissions for list endpoint', ALL_PERMISSIONS
    end
  end

  describe 'GET /v3/apps/:app_guid/sidecars' do
    let!(:sidecar1) { VCAP::CloudController::SidecarModel.make(name: 'sidecar1', app: app_model) }
    let!(:sidecar1_processes) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar1, type: 'one') }
    let!(:sidecar2) { VCAP::CloudController::SidecarModel.make(name: 'sidecar2', app: app_model) }
    let!(:sidecar2_processes) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar2, type: 'two') }
    let!(:sidecar3) { VCAP::CloudController::SidecarModel.make(name: 'sidecar3', app: app_model) }
    let!(:sidecar3_processes) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar3, type: 'three') }

    context 'with a user in the space' do
      before do
        app_model.space.organization.add_user(user)
        app_model.space.add_developer(user)
      end

      it 'lists the sidecars for an app' do
        get "/v3/apps/#{app_model.guid}/sidecars?per_page=2", nil, user_header
        expect(last_response.status).to eq(200), last_response.body

        expect(parsed_response).to be_a_response_like(
          {
            'pagination' => {
              'total_results' => 3,
              'total_pages' => 2,
              'first' => {
                'href' => "#{link_prefix}/v3/apps/#{app_model.guid}/sidecars?page=1&per_page=2"
              },
              'last' => {
                'href' => "#{link_prefix}/v3/apps/#{app_model.guid}/sidecars?page=2&per_page=2"
              },
              'next' => {
                'href' => "#{link_prefix}/v3/apps/#{app_model.guid}/sidecars?page=2&per_page=2"
              },
              'previous' => nil
            },
            'resources' => [
              {
                'guid' => sidecar1.guid,
                'name' => 'sidecar1',
                'command' => 'bundle exec rackup',
                'process_types' => ['one'],
                'memory_in_mb' => nil,
                'origin' => 'user',
                'created_at' => iso8601,
                'updated_at' => iso8601,
                'relationships' => {
                  'app' => {
                    'data' => {
                      'guid' => app_model.guid
                    }
                  }
                }
              },
              {
                'guid' => sidecar2.guid,
                'name' => 'sidecar2',
                'command' => 'bundle exec rackup',
                'process_types' => ['two'],
                'memory_in_mb' => nil,
                'origin' => 'user',
                'created_at' => iso8601,
                'updated_at' => iso8601,
                'relationships' => {
                  'app' => {
                    'data' => {
                      'guid' => app_model.guid
                    }
                  }
                }
              }
            ]
          }
        )
      end

      context 'when the app does not exist' do
        it 'returns a 404 error' do
          get '/v3/apps/fake-app-guid/sidecars?per_page=2', nil, user_header
          expect(last_response.status).to eq(404), last_response.body
        end
      end

      it_behaves_like 'list_endpoint_with_common_filters' do
        let(:resource_klass) { VCAP::CloudController::SidecarModel }
        let(:app_model2) { VCAP::CloudController::AppModel.make }
        let(:additional_resource_params) { { app: app_model2 } }
        let(:headers) { admin_headers }
        let(:api_call) do
          ->(headers, filters) { get "/v3/apps/#{app_model2.guid}/sidecars?#{filters}", nil, headers }
        end
      end
    end

    context 'permissions' do
      let(:api_call) { ->(user_headers) { get "/v3/apps/#{app_model.guid}/sidecars", nil, user_headers } }
      let(:org) { app_model.organization }
      let(:space) { app_model.space }

      let(:expected_codes_and_responses) do
        h = Hash.new(code: 404)
        h['admin'] = {
          code: 200
        }
        h['admin_read_only'] = {
          code: 200
        }
        h['space_developer'] = {
          code: 200
        }
        h['space_supporter'] = {
          code: 200
        }
        h['global_auditor'] = {
          code: 200
        }
        h['space_manager'] = {
          code: 200
        }
        h['space_auditor'] = {
          code: 200
        }
        h['org_manager'] = {
          code: 200
        }
        h
      end

      it_behaves_like 'permissions for list endpoint', ALL_PERMISSIONS
    end
  end

  describe 'DELETE /v3/sidecars/:guid' do
    let!(:sidecar) { VCAP::CloudController::SidecarModel.make(app: app_model, name: 'sidecar', command: 'smarch') }
    let!(:sidecar_spider) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar, type: 'spider') }
    let!(:sidecar_web) { VCAP::CloudController::SidecarProcessTypeModel.make(sidecar: sidecar, type: 'web') }

    before do
      app_model.space.organization.add_user(user)
      app_model.space.add_developer(user)
    end

    it 'deletes the sidecar' do
      delete "/v3/sidecars/#{sidecar.guid}", nil, user_header
      expect(last_response.status).to eq(204), last_response.body
      expect(app_model.reload.sidecars.size).to eq(0)
    end

    context 'permissions' do
      let(:org) { app_model.space.organization }
      let(:space) { app_model.space }
      let(:api_call) { ->(user_headers) { delete "/v3/sidecars/#{sidecar.guid}", nil, user_headers } }

      before do
        space.remove_developer(user)
        org.remove_user(user)
      end

      let(:expected_codes_and_responses) do
        h = Hash.new(code: 403, errors: CF_NOT_AUTHORIZED)
        %w[no_role org_auditor org_billing_manager].each { |r| h[r] = { code: 404 } }
        %w[admin space_developer].each { |r| h[r] = { code: 204 } }
        h
      end

      it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS

      context 'when organization is suspended' do
        let(:expected_codes_and_responses) do
          h = super()
          h['space_developer'] = { code: 403, errors: CF_ORG_SUSPENDED }
          h
        end

        before do
          org.update(status: VCAP::CloudController::Organization::SUSPENDED)
        end

        it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
      end
    end
  end
end