require 'spec_helper'
require 'isolation_segment_assign'
require 'request_spec_shared_examples'

RSpec.describe 'IsolationSegmentModels' do
  let(:user) { VCAP::CloudController::User.make }
  let(:user_header) { admin_headers_for(user) }
  let(:admin_header) { admin_headers_for(user) }
  let(:space) { VCAP::CloudController::Space.make }
  let(:assigner) { VCAP::CloudController::IsolationSegmentAssign.new }

  describe 'POST /v3/isolation_segments' do
    it 'creates an isolation segment' do
      create_request = {
        name: 'my_segment',
        metadata: {
          labels: { release: 'stable' },
          annotations: { note: 'this info' }
        }
      }

      post '/v3/isolation_segments', create_request.to_json, user_header

      parsed_response = Oj.load(last_response.body)
      expect(last_response.status).to eq(201)

      created_isolation_segment = VCAP::CloudController::IsolationSegmentModel.last
      expected_response = {
        'name' => 'my_segment',
        'guid' => created_isolation_segment.guid,
        'created_at' => iso8601,
        'updated_at' => iso8601,
        'links' => {
          'self' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{created_isolation_segment.guid}" },
          'organizations' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{created_isolation_segment.guid}/organizations" }
        },
        'metadata' => {
          'annotations' => { 'note' => 'this info' },
          'labels' => { 'release' => 'stable' }
        }
      }

      expect(parsed_response).to be_a_response_like(expected_response)
    end
  end

  describe 'GET /v3/isolation_segments/:guid/relationships/organizations' do
    let(:org1) { VCAP::CloudController::Organization.make }
    let(:org2) { VCAP::CloudController::Organization.make }
    let(:isolation_segment_model) { VCAP::CloudController::IsolationSegmentModel.make }

    before do
      assigner.assign(isolation_segment_model, [org1, org2])
    end

    it 'returns the organization guids assigned' do
      get "/v3/isolation_segments/#{isolation_segment_model.guid}/relationships/organizations", nil, user_header

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

      expected_response = {
        'data' => [
          { 'guid' => org1.guid },
          { 'guid' => org2.guid }
        ],
        'links' => {
          'self' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{isolation_segment_model.guid}/relationships/organizations" },
          'related' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{isolation_segment_model.guid}/organizations" }
        }
      }

      expect(parsed_response['data'].length).to eq 2
      expect(parsed_response['data']).to include(expected_response['data'][0])
      expect(parsed_response['data']).to include(expected_response['data'][1])
      expect(parsed_response.except('data')).to be_a_response_like(expected_response.except('data'))
    end

    context 'permissions' do
      before do
        assigner.assign(isolation_segment_model, [space.organization])
        space.update(isolation_segment_guid: isolation_segment_model.guid)
      end

      it_behaves_like 'permissions for list endpoint', ALL_PERMISSIONS do
        let(:api_call) { ->(user_headers) { get "/v3/isolation_segments/#{isolation_segment_model.guid}/relationships/organizations", nil, user_headers } }
        let(:org) { space.organization }
        let(:user) { VCAP::CloudController::User.make }

        let(:expected_codes_and_responses) do
          h = Hash.new({ code: 200, response_guids: [org1.guid, org2.guid, space.organization.guid] }.freeze)
          h['org_auditor'] = { code: 200, response_guids: [space.organization.guid] }
          h['org_billing_manager'] = { code: 200, response_guids: [space.organization.guid] }
          h['org_manager'] = { code: 200, response_guids: [space.organization.guid] }
          h['space_auditor'] = { code: 200, response_guids: [space.organization.guid] }
          h['space_developer'] = { code: 200, response_guids: [space.organization.guid] }
          h['space_manager'] = { code: 200, response_guids: [space.organization.guid] }
          h['space_supporter'] = { code: 200, response_guids: [space.organization.guid] }
          h['no_role'] = { code: 404 }
          h
        end
      end
    end
  end

  describe 'GET /v3/isolation_segments/:guid/relationships/spaces' do
    let(:space1) { VCAP::CloudController::Space.make }
    let(:space2) { VCAP::CloudController::Space.make }
    let(:isolation_segment_model) { VCAP::CloudController::IsolationSegmentModel.make }

    before do
      assigner.assign(isolation_segment_model, [space1.organization, space2.organization])
      isolation_segment_model.add_space(space1)
      isolation_segment_model.add_space(space2)
    end

    it 'returns the guids of the associated spaces' do
      get "/v3/isolation_segments/#{isolation_segment_model.guid}/relationships/spaces", nil, user_header

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

      expect(parsed_response['data'].length).to eq 2
      expect(parsed_response['data']).to include({ 'guid' => space1.guid })
      expect(parsed_response['data']).to include({ 'guid' => space2.guid })
      expect(parsed_response['links']).to eq({
                                               'self' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{isolation_segment_model.guid}/relationships/spaces" }
                                             })
    end

    context 'permissions' do
      it_behaves_like 'permissions for list endpoint', ALL_PERMISSIONS do
        let(:api_call) { ->(user_headers) { get "/v3/isolation_segments/#{isolation_segment_model.guid}/relationships/spaces", nil, user_headers } }
        let(:org) { space1.organization }
        let(:space) { space1 }
        let(:user) { VCAP::CloudController::User.make }

        let(:expected_codes_and_responses) do
          h = Hash.new({ code: 200, response_guids: [space1.guid, space2.guid] }.freeze)
          h['org_auditor'] = { code: 200, response_guids: [] }
          h['org_billing_manager'] = { code: 200, response_guids: [] }
          h['org_manager'] = { code: 200, response_guids: [space1.guid] }
          h['space_auditor'] = { code: 200, response_guids: [space1.guid] }
          h['space_developer'] = { code: 200, response_guids: [space1.guid] }
          h['space_manager'] = { code: 200, response_guids: [space1.guid] }
          h['space_supporter'] = { code: 200, response_guids: [space1.guid] }
          h['no_role'] = { code: 404 }
          h
        end
      end
    end
  end

  describe 'POST /v3/isolation_segments/:guid/relationships/organizations' do
    let(:org1) { VCAP::CloudController::Organization.make }
    let(:org2) { VCAP::CloudController::Organization.make }
    let(:isolation_segment) { VCAP::CloudController::IsolationSegmentModel.make }

    it 'assigns the isolation segment to the organization' do
      assign_request = {
        data: [
          { guid: org1.guid }
        ]
      }

      post "/v3/isolation_segments/#{isolation_segment.guid}/relationships/organizations", assign_request.to_json, user_header

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

      expected_response = {
        'data' => [
          { 'guid' => org1.guid }
        ],
        'links' => {
          'self' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{isolation_segment.guid}/relationships/organizations" },
          'related' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{isolation_segment.guid}/organizations" }
        }
      }

      expect(parsed_response).to be_a_response_like(expected_response)
    end
  end

  describe 'DELETE /v3/isolation_segments/:guid/relationships/organizations/:org_guid' do
    let(:org1) { VCAP::CloudController::Organization.make }
    let(:org2) { VCAP::CloudController::Organization.make }
    let(:isolation_segment) { VCAP::CloudController::IsolationSegmentModel.make }

    before do
      assigner.assign(isolation_segment, [org1, org2])
    end

    it 'removes the organization from the isolation segment' do
      delete "/v3/isolation_segments/#{isolation_segment.guid}/relationships/organizations/#{org1.guid}", nil, user_header

      expect(last_response.status).to eq(204)
      isolation_segment.reload
      expect(isolation_segment.organizations).to include(org2)
      expect(isolation_segment.organizations).not_to include(org1)
    end
  end

  describe 'GET /v3/isolation_segments/:guid' do
    let(:isolation_segment_model) { VCAP::CloudController::IsolationSegmentModel.make }
    let(:expected_response_object) do
      {
        'name' => isolation_segment_model.name,
        'guid' => isolation_segment_model.guid,
        'created_at' => iso8601,
        'updated_at' => iso8601,
        'links' => {
          'self' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{isolation_segment_model.guid}" },
          'organizations' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{isolation_segment_model.guid}/organizations" }
        },
        'metadata' => {
          'annotations' => {},
          'labels' => {}
        }
      }
    end

    context 'permissions for org-scoped isolation segments' do
      before do
        assigner.assign(isolation_segment_model, [space.organization])
      end

      it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS do
        let(:api_call) { ->(user_headers) { get "/v3/isolation_segments/#{isolation_segment_model.guid}", nil, user_headers } }
        let(:org) { space.organization }
        let(:user) { VCAP::CloudController::User.make }
        let(:expected_codes_and_responses) do
          h = Hash.new({ code: 200, response_object: expected_response_object }.freeze)
          h['no_role'] = { code: 404 }
          h
        end
      end
    end

    context 'permissions for unscoped isolation segments' do
      it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS do
        let(:api_call) { ->(user_headers) { get "/v3/isolation_segments/#{isolation_segment_model.guid}", nil, user_headers } }
        let(:org) { space.organization }
        let(:user) { VCAP::CloudController::User.make }
        let(:expected_codes_and_responses) do
          h = Hash.new({ code: 404 }.freeze)
          h['admin'] = { code: 200, response_object: expected_response_object }
          h['admin_read_only'] = { code: 200, response_object: expected_response_object }
          h['global_auditor'] = { code: 200, response_object: expected_response_object }
          h
        end
      end
    end
  end

  describe 'GET /v3/isolation_segments' do
    let(:org1) { VCAP::CloudController::Organization.make }
    let(:org2) { VCAP::CloudController::Organization.make }

    it_behaves_like 'list query endpoint' do
      let(:message) { VCAP::CloudController::IsolationSegmentsListMessage }
      let(:request) { '/v3/isolation_segments' }

      let(:params) do
        {
          names: %w[foo bar],
          guids: %w[foo bar],
          organization_guids: %w[foo bar],
          page: '2',
          per_page: '10',
          order_by: 'updated_at',
          label_selector: 'foo,bar',
          created_ats: "#{Time.now.utc.iso8601},#{Time.now.utc.iso8601}",
          updated_ats: { gt: Time.now.utc.iso8601 }
        }
      end
    end

    it 'returns the seeded isolation segment' do
      get '/v3/isolation_segments', nil, user_header

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

      shared_guid = VCAP::CloudController::IsolationSegmentModel::SHARED_ISOLATION_SEGMENT_GUID

      expected_response = {
        'pagination' => {
          'total_results' => 1,
          'total_pages' => 1,
          'first' => { 'href' => "#{link_prefix}/v3/isolation_segments?page=1&per_page=50" },
          'last' => { 'href' => "#{link_prefix}/v3/isolation_segments?page=1&per_page=50" },
          'next' => nil,
          'previous' => nil
        },
        'resources' => [
          {
            'name' => 'shared',
            'guid' => shared_guid,
            'created_at' => iso8601,
            'updated_at' => iso8601,
            'links' => {
              'self' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{shared_guid}" },
              'organizations' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{shared_guid}/organizations" }
            },
            'metadata' => {
              'annotations' => {},
              'labels' => {}
            }
          }
        ]
      }

      expect(parsed_response).to be_a_response_like(expected_response)
    end

    context 'when there are multiple isolation segments' do
      let!(:models) do
        [
          VCAP::CloudController::IsolationSegmentModel.make(name: 'segment1'),
          VCAP::CloudController::IsolationSegmentModel.make(name: 'segment2'),
          VCAP::CloudController::IsolationSegmentModel.make(name: 'segment3'),
          VCAP::CloudController::IsolationSegmentModel.make(name: 'segment4'),
          VCAP::CloudController::IsolationSegmentModel.make(name: 'segment5'),
          VCAP::CloudController::IsolationSegmentModel.make(name: 'segment6')
        ]
      end

      # We do not account for the first isolation segment as it is a seed in the database.
      it 'returns a paginated list of the isolation segments' do
        get '/v3/isolation_segments?per_page=2&page=2', nil, user_header

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

        expected_response = {
          'pagination' => {
            'total_results' => 7,
            'total_pages' => 4,
            'first' => { 'href' => "#{link_prefix}/v3/isolation_segments?page=1&per_page=2" },
            'last' => { 'href' => "#{link_prefix}/v3/isolation_segments?page=4&per_page=2" },
            'next' => { 'href' => "#{link_prefix}/v3/isolation_segments?page=3&per_page=2" },
            'previous' => { 'href' => "#{link_prefix}/v3/isolation_segments?page=1&per_page=2" }
          },
          'resources' => [
            {
              'guid' => models[1].guid.to_s,
              'name' => models[1].name.to_s,
              'created_at' => iso8601,
              'updated_at' => iso8601,
              'links' => {
                'self' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{models[1].guid}" },
                'organizations' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{models[1].guid}/organizations" }
              },
              'metadata' => {
                'annotations' => {},
                'labels' => {}
              }
            },
            {
              'guid' => models[2].guid.to_s,
              'name' => models[2].name.to_s,
              'created_at' => iso8601,
              'updated_at' => iso8601,
              'links' => {
                'self' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{models[2].guid}" },
                'organizations' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{models[2].guid}/organizations" }
              },
              'metadata' => {
                'annotations' => {},
                'labels' => {}
              }
            }
          ]
        }

        expect(parsed_response).to be_a_response_like(expected_response)
      end

      it 'filters by isolation segment names' do
        get "/v3/isolation_segments?names=#{models[2].name}%2C#{models[4].name}", nil, user_header

        expected_pagination = {
          'total_results' => 2,
          'total_pages' => 1,
          'first' => { 'href' => "#{link_prefix}/v3/isolation_segments?names=#{models[2].name}%2C#{models[4].name}&page=1&per_page=50" },
          'last' => { 'href' => "#{link_prefix}/v3/isolation_segments?names=#{models[2].name}%2C#{models[4].name}&page=1&per_page=50" },
          'next' => nil,
          'previous' => nil
        }

        parsed_response = Oj.load(last_response.body)

        expect(last_response.status).to eq(200)
        expect(parsed_response['resources'].pluck('name')).to eq([models[2].name, models[4].name])
        expect(parsed_response['pagination']).to eq(expected_pagination)
      end

      it 'filters by isolation segment guids' do
        get "/v3/isolation_segments?guids=#{models[3].guid}%2C#{models[5].guid}", nil, user_header

        expected_pagination = {
          'total_results' => 2,
          'total_pages' => 1,
          'first' => { 'href' => "#{link_prefix}/v3/isolation_segments?guids=#{models[3].guid}%2C#{models[5].guid}&page=1&per_page=50" },
          'last' => { 'href' => "#{link_prefix}/v3/isolation_segments?guids=#{models[3].guid}%2C#{models[5].guid}&page=1&per_page=50" },
          'next' => nil,
          'previous' => nil
        }

        parsed_response = Oj.load(last_response.body)

        expect(last_response.status).to eq(200)
        expect(parsed_response['resources'].pluck('name')).to eq([models[3].name, models[5].name])
        expect(parsed_response['pagination']).to eq(expected_pagination)
      end

      context 'and isolation segments are assigned to orgs' do
        before do
          assigner.assign(models[1], [org1])
          assigner.assign(models[2], [org2])
        end

        it 'filters by organization guids' do
          get "/v3/isolation_segments?organization_guids=#{org1.guid}%2C#{org2.guid}", nil, user_header

          expected_pagination = {
            'total_results' => 2,
            'total_pages' => 1,
            'first' => { 'href' => "#{link_prefix}/v3/isolation_segments?organization_guids=#{org1.guid}%2C#{org2.guid}&page=1&per_page=50" },
            'last' => { 'href' => "#{link_prefix}/v3/isolation_segments?organization_guids=#{org1.guid}%2C#{org2.guid}&page=1&per_page=50" },
            'next' => nil,
            'previous' => nil
          }

          parsed_response = Oj.load(last_response.body)

          expect(last_response.status).to eq(200)
          expect(parsed_response['resources'].pluck('name')).to eq([models[1].name, models[2].name])
          expect(parsed_response['pagination']).to eq(expected_pagination)
        end
      end
    end

    it_behaves_like 'list_endpoint_with_common_filters' do
      let(:resource_klass) { VCAP::CloudController::IsolationSegmentModel }
      let(:api_call) do
        ->(headers, filters) { get "/v3/isolation_segments?#{filters}", nil, headers }
      end
      let(:headers) { admin_header }
    end

    context 'label_selector' do
      let!(:iso_segA) { VCAP::CloudController::IsolationSegmentModel.make(name: 'segmentA') }
      let!(:iso_segB) { VCAP::CloudController::IsolationSegmentModel.make(name: 'segmentB') }
      let!(:iso_segC) { VCAP::CloudController::IsolationSegmentModel.make(name: 'segmentC') }

      let!(:isoAFruit) { VCAP::CloudController::IsolationSegmentLabelModel.make(key_name: 'fruit', value: 'strawberry', resource_guid: iso_segA.guid) }
      let!(:isoAAnimal) { VCAP::CloudController::IsolationSegmentLabelModel.make(key_name: 'animal', value: 'horse', resource_guid: iso_segA.guid) }

      let!(:isoBEnv) { VCAP::CloudController::IsolationSegmentLabelModel.make(key_name: 'env', value: 'prod', resource_guid: iso_segB.guid) }
      let!(:isoBAnimal) { VCAP::CloudController::IsolationSegmentLabelModel.make(key_name: 'animal', value: 'dog', resource_guid: iso_segB.guid) }

      let!(:isoCEnv) { VCAP::CloudController::IsolationSegmentLabelModel.make(key_name: 'env', value: 'prod', resource_guid: iso_segC.guid) }
      let!(:isoCAnimal) { VCAP::CloudController::IsolationSegmentLabelModel.make(key_name: 'animal', value: 'horse', resource_guid: iso_segC.guid) }

      it 'returns the matching iso segs' do
        get '/v3/isolation_segments?label_selector=!fruit,env=prod,animal in (dog,horse)', nil, admin_headers
        expect(last_response.status).to eq(200)

        parsed_response = Oj.load(last_response.body)
        expect(parsed_response['resources'].pluck('guid')).to contain_exactly(iso_segB.guid, iso_segC.guid)
      end
    end

    context 'permissions' do
      let(:iso_seg1) { VCAP::CloudController::IsolationSegmentModel.make }

      before do
        assigner.assign(iso_seg1, [space.organization])
      end

      it_behaves_like 'permissions for list endpoint', ALL_PERMISSIONS do
        let(:api_call) { ->(user_headers) { get '/v3/isolation_segments', nil, user_headers } }
        let(:org) { space.organization }
        let(:user) { VCAP::CloudController::User.make }
        let(:expected_codes_and_responses) do
          h = Hash.new({ code: 200, response_guids: [iso_seg1.guid] }.freeze)
          h['admin'] = { code: 200, response_guids: [iso_seg1.guid, VCAP::CloudController::IsolationSegmentModel::SHARED_ISOLATION_SEGMENT_GUID] }
          h['admin_read_only'] = { code: 200, response_guids: [iso_seg1.guid, VCAP::CloudController::IsolationSegmentModel::SHARED_ISOLATION_SEGMENT_GUID] }
          h['global_auditor'] = { code: 200, response_guids: [iso_seg1.guid, VCAP::CloudController::IsolationSegmentModel::SHARED_ISOLATION_SEGMENT_GUID] }
          h['no_role'] = { code: 200, response_guids: [] }
          h
        end
      end
    end
  end

  describe 'PATCH /v3/isolation_segments/:guid' do
    it 'updates the specified isolation segment' do
      isolation_segment_model = VCAP::CloudController::IsolationSegmentModel.make(name: 'my_segment')

      update_request = {
        name: 'your_segment',
        metadata: {
          labels: {
            one: 'two'
          },
          annotations: {
            three: 'four'
          }
        }
      }

      expected_response = {
        'name' => 'your_segment',
        'guid' => isolation_segment_model.guid,
        'created_at' => iso8601,
        'updated_at' => iso8601,
        'links' => {
          'self' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{isolation_segment_model.guid}" },
          'organizations' => { 'href' => "#{link_prefix}/v3/isolation_segments/#{isolation_segment_model.guid}/organizations" }
        },
        'metadata' => {
          'labels' => { 'one' => 'two' },
          'annotations' => { 'three' => 'four' }
        }
      }

      patch "/v3/isolation_segments/#{isolation_segment_model.guid}", update_request.to_json, user_header

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

  describe 'DELETE /v3/isolation_segments/:guid' do
    let(:isolation_segment_model) { VCAP::CloudController::IsolationSegmentModel.make(name: 'my_segment') }

    it 'deletes the specified isolation segment' do
      delete "/v3/isolation_segments/#{isolation_segment_model.guid}", nil, user_header
      expect(last_response.status).to eq(204)
    end

    context 'deleting metadata' do
      it_behaves_like 'resource with metadata' do
        let(:resource) { isolation_segment_model }
        let(:api_call) do
          -> { delete "/v3/isolation_segments/#{isolation_segment_model.guid}", nil, user_header }
        end
      end
    end
  end
end
