RSpec.describe Flipper::UI::Actions::ActorsGate do
  let(:token) do
    if Rack::Protection::AuthenticityToken.respond_to?(:random_token)
      Rack::Protection::AuthenticityToken.random_token
    else
      'a'
    end
  end
  let(:session) do
    { :csrf => token, 'csrf' => token, '_csrf_token' => token }
  end

  describe 'GET /features/:feature/actors' do
    before do
      get 'features/search/actors'
    end

    it 'responds with success' do
      expect(last_response.status).to be(200)
    end

    it 'renders add new actor form' do
      form = '<form action="/features/search/actors" method="post" class="row">'
      expect(last_response.body).to include(form)
    end
  end

  describe 'GET /features/:feature/actors with slash in feature name' do
    before do
      get 'features/a/b/actors'
    end

    it 'responds with success' do
      expect(last_response.status).to be(200)
    end

    it 'renders add new actor form' do
      form = '<form action="/features/a%2Fb/actors" method="post" class="row">'
      expect(last_response.body).to include(form)
    end
  end

  describe 'POST /features/:feature/actors' do
    context 'enabling an actor' do
      let(:value) { 'User;6' }
      let(:multi_value) { 'User;5, User;7, User;9, User;12' }

      before do
        post 'features/search/actors',
             { 'value' => value, 'operation' => 'enable', 'authenticity_token' => token },
             'rack.session' => session
      end

      it 'adds item to members' do
        expect(flipper[:search].actors_value).to include(value)
      end

      it 'adds item to multiple members' do
        post 'features/search/actors',
             { 'value' => multi_value, 'operation' => 'enable', 'authenticity_token' => token },
             'rack.session' => session

        expect(flipper[:search].actors_value).to include('User;5')
        expect(flipper[:search].actors_value).to include('User;7')
        expect(flipper[:search].actors_value).to include('User;9')
        expect(flipper[:search].actors_value).to include('User;12')
      end

      it 'redirects back to feature' do
        expect(last_response.status).to be(302)
        expect(last_response.headers['location']).to eq('/features/search')
      end

      context "when feature name contains space" do
        before do
          post 'features/sp+ace/actors',
               { 'value' => value, 'operation' => 'enable', 'authenticity_token' => token },
               'rack.session' => session
        end

        it 'adds item to members' do
          expect(flipper["sp ace"].actors_value).to include('User;6')
        end

        it "redirects back to feature" do
          expect(last_response.status).to be(302)
          expect(last_response.headers['location']).to eq('/features/sp+ace')
        end
      end

      context 'value contains whitespace' do
        let(:value) { '  User;6  ' }
        let(:multi_value) { '  User;5  ,  User;7   ,  User;9 ,  User;12 ' }

        it 'adds item without whitespace' do
          expect(flipper[:search].actors_value).to include('User;6')
        end

        it 'adds item to multi members without whitespace' do
          post 'features/search/actors',
             { 'value' => multi_value, 'operation' => 'enable', 'authenticity_token' => token },
             'rack.session' => session

          expect(flipper[:search].actors_value).to include('User;5')
          expect(flipper[:search].actors_value).to include('User;7')
          expect(flipper[:search].actors_value).to include('User;9')
          expect(flipper[:search].actors_value).to include('User;12')
        end
      end

      context 'for an invalid actor value' do
        context 'empty value' do
          let(:value) { '' }

          it 'redirects back to feature' do
            expect(last_response.status).to be(302)
            expect(last_response.headers['location']).to eq('/features/search/actors?error=%22%22+is+not+a+valid+actor+value.')
          end
        end

        context 'nil value' do
          let(:value) { nil }

          it 'redirects back to feature' do
            expect(last_response.status).to be(302)
            expect(last_response.headers['location']).to eq('/features/search/actors?error=%22%22+is+not+a+valid+actor+value.')
          end
        end
      end
    end

    context "when a readonly adapter is configured" do
      let(:value) { 'User;6' }

      before do
        allow(flipper).to receive(:read_only?) { true }
      end

      it "does not allow an actor to be added" do
        post 'features/search/actors',
           { 'value' => value, 'operation' => 'enable', 'authenticity_token' => token },
           'rack.session' => session

        expect(flipper[:search].actors_value).not_to include('User;6')
        expect(last_response.body).to include("The UI is currently in read only mode.")
      end
    end

    context 'disabling an actor' do
      let(:value) { 'User;6' }
      let(:multi_value) { 'User;5, User;7, User;9, User;12' }

      before do
        flipper[:search].enable_actor Flipper::Actor.new(value)
        post 'features/search/actors',
             { 'value' => value, 'operation' => 'disable', 'authenticity_token' => token },
             'rack.session' => session
      end

      it 'removes item from members' do
        expect(flipper[:search].actors_value).not_to include(value)
      end

      it 'removes item from multi members' do
        multi_value.split(',').map(&:strip).each do |value|
          flipper[:search].enable_actor Flipper::Actor.new(value)
        end

        post 'features/search/actors',
             { 'value' => multi_value, 'operation' => 'disable', 'authenticity_token' => token },
             'rack.session' => session

        expect(flipper[:search].actors_value).not_to eq(Set.new(multi_value.split(',').map(&:strip)))
      end

      it 'redirects back to feature' do
        expect(last_response.status).to be(302)
        expect(last_response.headers['location']).to eq('/features/search')
      end

      context 'value contains whitespace' do
        let(:value) { '  User;6  ' }
        let(:multi_value) { '  User;5  ,  User;7   ,  User;9 ,  User;12 ' }

        it 'removes item without whitespace' do
          expect(flipper[:search].actors_value).not_to include('User;6')
        end

        it 'removes item without whitespace' do
          multi_value.split(',').map(&:strip).each do |value|
            flipper[:search].enable_actor Flipper::Actor.new(value)
          end
          post 'features/search/actors',
              { 'value' => multi_value, 'operation' => 'disable', 'authenticity_token' => token },
              'rack.session' => session
          expect(flipper[:search].actors_value).not_to eq(Set.new(multi_value.split(',').map(&:strip)))
        end
      end
    end
  end
end
