require 'spec_helper.rb'

describe Rack::OAuth2::Client do
  let :client do
    Rack::OAuth2::Client.new(
      identifier: 'client_id',
      secret: 'client_secret',
      host: 'server.example.com',
      redirect_uri: 'https://client.example.com/callback'
    )
  end
  subject { client }

  its(:identifier) { should == 'client_id' }
  its(:secret)     { should == 'client_secret' }
  its(:authorization_endpoint) { should == '/oauth2/authorize' }
  its(:token_endpoint)         { should == '/oauth2/token' }

  context 'when identifier is missing' do
    it do
      expect { Rack::OAuth2::Client.new }.to raise_error AttrRequired::AttrMissing
    end
  end

  describe '#authorization_uri' do
    subject { client.authorization_uri }
    it { should include 'https://server.example.com/oauth2/authorize' }
    it { should include 'client_id=client_id' }
    it { should include 'redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback' }
    it { should include 'response_type=code' }

    context 'when endpoints are absolute URIs' do
      before do
        client.authorization_endpoint = 'https://server2.example.com/oauth/authorize'
        client.token_endpoint = 'https://server2.example.com/oauth/token'
      end
      it { should include 'https://server2.example.com/oauth/authorize' }
    end

    context 'when scheme is specified' do
      before { client.scheme = 'http' }
      it { should include 'http://server.example.com/oauth2/authorize' }
    end

    context 'when response_type is token' do
      subject { client.authorization_uri(response_type: :token) }
      it { should include 'response_type=token' }
    end

    context 'when response_type is an Array' do
      subject { client.authorization_uri(response_type: [:token, :code]) }
      it { should include 'response_type=token+code' }
    end

    context 'when scope is given' do
      subject { client.authorization_uri(scope: [:scope1, :scope2]) }
      it { should include 'scope=scope1+scope2' }
    end
  end

  describe '#authorization_code=' do
    before  { client.authorization_code = 'code' }
    subject { client.instance_variable_get('@grant') }
    it { should be_instance_of Rack::OAuth2::Client::Grant::AuthorizationCode }
  end

  describe '#resource_owner_credentials=' do
    before  { client.resource_owner_credentials = 'username', 'password' }
    subject { client.instance_variable_get('@grant') }
    it { should be_instance_of Rack::OAuth2::Client::Grant::Password }
  end

  describe '#refresh_token=' do
    before  { client.refresh_token = 'refresh_token' }
    subject { client.instance_variable_get('@grant') }
    it { should be_instance_of Rack::OAuth2::Client::Grant::RefreshToken }
  end

  describe '#access_token!' do
    subject { client.access_token! }

      context 'when *args given' do

      describe 'client authentication method' do
        before do
          client.authorization_code = 'code'
        end

        it 'should be Basic auth as default' do
          mock_response(
            :post,
            'https://server.example.com/oauth2/token',
            'tokens/bearer.json',
            request_header: {
              'Authorization' => 'Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ='
            }
          )
          client.access_token!
        end

        context 'when other auth method specified' do
          it 'should be body params' do
            mock_response(
              :post,
              'https://server.example.com/oauth2/token',
              'tokens/bearer.json',
              params: {
                client_id: 'client_id',
                client_secret: 'client_secret',
                code: 'code',
                grant_type: 'authorization_code',
                redirect_uri: 'https://client.example.com/callback'
              }
            )
            client.access_token! :client_auth_body
          end
        end

        context 'when auth method is specified as Hash' do
          it 'should be removed before sending request' do
            mock_response(
              :post,
              'https://server.example.com/oauth2/token',
              'tokens/bearer.json',
              params: {
                client_id: 'client_id',
                client_secret: 'client_secret',
                code: 'code',
                grant_type: 'authorization_code',
                redirect_uri: 'https://client.example.com/callback'
              }
            )
            client.access_token! client_auth_method: :body
          end
        end
      end

      describe 'scopes' do
        context 'when scope option given' do
          it 'should specify given scope' do
            mock_response(
              :post,
              'https://server.example.com/oauth2/token',
              'tokens/bearer.json',
              params: {
                grant_type: 'client_credentials',
                scope: 'a b'
              }
            )
            client.access_token! scope: [:a, :b]
          end
        end
      end

      describe 'unknown params' do
        it 'should be included in body params' do
          mock_response(
            :post,
            'https://server.example.com/oauth2/token',
            'tokens/bearer.json',
            params: {
              grant_type: 'client_credentials',
              resource: 'something'
            }
          )
          client.access_token! resource: :something
        end
      end
    end

    context 'when bearer token is given' do
      before  do
        client.authorization_code = 'code'
        mock_response(
          :post,
          'https://server.example.com/oauth2/token',
          'tokens/bearer.json'
        )
      end
      it { should be_instance_of Rack::OAuth2::AccessToken::Bearer }
      its(:token_type) { should == :bearer }
      its(:access_token) { should == 'access_token' }
      its(:refresh_token) { should == 'refresh_token' }
      its(:expires_in) { should == 3600 }

      context 'when token type is "Bearer", not "bearer"' do
        before  do
          client.authorization_code = 'code'
          mock_response(
            :post,
            'https://server.example.com/oauth2/token',
            'tokens/_Bearer.json'
          )
        end
        it { should be_instance_of Rack::OAuth2::AccessToken::Bearer }
        its(:token_type) { should == :bearer }
      end
    end

    context 'when mac token is given' do
      before  do
        client.authorization_code = 'code'
        mock_response(
          :post,
          'https://server.example.com/oauth2/token',
          'tokens/mac.json'
        )
      end
      it { should be_instance_of Rack::OAuth2::AccessToken::MAC }
      its(:token_type) { should == :mac }
      its(:access_token) { should == 'access_token' }
      its(:refresh_token) { should == 'refresh_token' }
      its(:expires_in) { should == 3600 }
    end

    context 'when no-type token is given (JSON)' do
      before  do
        client.authorization_code = 'code'
        mock_response(
          :post,
          'https://server.example.com/oauth2/token',
          'tokens/legacy.json'
        )
      end
      it { should be_instance_of Rack::OAuth2::AccessToken::Legacy }
      its(:token_type) { should == :legacy }
      its(:access_token) { should == 'access_token' }
      its(:refresh_token) { should == 'refresh_token' }
      its(:expires_in) { should == 3600 }
    end

    context 'when no-type token is given (key-value)' do
      before do
        mock_response(
          :post,
          'https://server.example.com/oauth2/token',
          'tokens/legacy.txt'
        )
      end
      it { should be_instance_of Rack::OAuth2::AccessToken::Legacy }
      its(:token_type) { should == :legacy }
      its(:access_token) { should == 'access_token' }
      its(:expires_in) { should == 3600 }

      context 'when expires_in is not given' do
        before do
          mock_response(
            :post,
            'https://server.example.com/oauth2/token',
            'tokens/legacy_without_expires_in.txt'
          )
        end
        its(:expires_in) { should be_nil }
      end
    end

    context 'when unknown-type token is given' do
      before  do
        client.authorization_code = 'code'
        mock_response(
          :post,
          'https://server.example.com/oauth2/token',
          'tokens/unknown.json'
        )
      end
      it do
        expect { client.access_token! }.to raise_error(StandardError, 'Unknown Token Type')
      end
    end

    context 'when error response is given' do
      before do
        mock_response(
          :post,
          'https://server.example.com/oauth2/token',
          'errors/invalid_request.json',
          status: 400
        )
      end
      it do
        expect { client.access_token! }.to raise_error Rack::OAuth2::Client::Error
      end
    end

    context 'when no body given' do
      context 'when error given' do
        before do
          mock_response(
            :post,
            'https://server.example.com/oauth2/token',
            'blank',
            status: 400
          )
        end
        it do
          expect { client.access_token! }.to raise_error Rack::OAuth2::Client::Error
        end
      end
    end
  end

  context 'when no host info' do
    let :client do
      Rack::OAuth2::Client.new(
        identifier: 'client_id',
        secret: 'client_secret',
        redirect_uri: 'https://client.example.com/callback'
      )
    end

    describe '#authorization_uri' do
      it do
        expect { client.authorization_uri }.to raise_error 'No Host Info'
      end
    end

    describe '#access_token!' do
      it do
        expect { client.access_token! }.to raise_error 'No Host Info'
      end
    end
  end
end
