<?php namespace League\OAuth2\Client\Test\Provider;

use InvalidArgumentException;
use League\OAuth2\Client\Tool\QueryBuilderTrait;
use Mockery as m;

class LinkedinTest extends \PHPUnit_Framework_TestCase
{
    use QueryBuilderTrait;

    protected $provider;

    protected function setUp()
    {
        $this->provider = new \League\OAuth2\Client\Provider\LinkedIn([
            'clientId' => 'mock_client_id',
            'clientSecret' => 'mock_secret',
            'redirectUri' => 'none',
        ]);
    }

    public function tearDown()
    {
        m::close();
        parent::tearDown();
    }

    public function testAuthorizationUrl()
    {
        $url = $this->provider->getAuthorizationUrl();
        $uri = parse_url($url);
        parse_str($uri['query'], $query);

        $this->assertArrayHasKey('client_id', $query);
        $this->assertArrayHasKey('redirect_uri', $query);
        $this->assertArrayHasKey('state', $query);
        $this->assertArrayHasKey('scope', $query);
        $this->assertArrayHasKey('response_type', $query);
        $this->assertArrayHasKey('approval_prompt', $query);
        $this->assertNotNull($this->provider->getState());
    }

    public function testResourceOwnerDetailsUrl()
    {
        $accessToken = m::mock('League\OAuth2\Client\Token\AccessToken');
        $expectedFields = $this->provider->getFields();
        $url = $this->provider->getResourceOwnerDetailsUrl($accessToken);
        $uri = parse_url($url);
        $path = $uri['path'];
        $query = explode('=', $uri['query']);
        $fields = $query[1];
        $actualFields = explode(',', preg_replace('/^\((.*)\)$/', '\1', $fields));
        $this->assertEquals('/v2/me', $path);
        $this->assertEquals('projection', $query[0]);
        $this->assertEquals($expectedFields, $actualFields);
    }

    public function testResourceOwnerEmailUrl()
    {
        $accessToken = m::mock('League\OAuth2\Client\Token\AccessToken');
        $expectedFields = $this->provider->getFields();
        $url = $this->provider->getResourceOwnerEmailUrl($accessToken);
        $uri = parse_url($url);
        parse_str($uri['query'], $query);
        $this->assertEquals('/v2/clientAwareMemberHandles', $uri['path']);
        $this->assertEquals('(elements*(state,primary,type,handle~))', $query['projection']);
    }

    public function testScopes()
    {
        $scopeSeparator = ' ';
        $options = ['scope' => [uniqid(), uniqid()]];
        $query = ['scope' => implode($scopeSeparator, $options['scope'])];
        $url = $this->provider->getAuthorizationUrl($options);
        $encodedScope = $this->buildQueryString($query);
        $this->assertContains($encodedScope, $url);
    }

    public function testFields()
    {
        $provider = new \League\OAuth2\Client\Provider\LinkedIn([
            'clientId' => 'mock_client_id',
            'clientSecret' => 'mock_secret',
            'redirectUri' => 'none'
        ]);

        $currentFields = $provider->getFields();
        $customFields = [uniqid(), uniqid()];

        $this->assertTrue(is_array($currentFields));
        $provider->withFields($customFields);
        $this->assertEquals($customFields, $provider->getFields());
    }

    public function testNonArrayFieldsDuringInstantiationThrowsException()
    {
        $this->setExpectedException(InvalidArgumentException::class);
        $provider = new \League\OAuth2\Client\Provider\LinkedIn([
            'clientId' => 'mock_client_id',
            'clientSecret' => 'mock_secret',
            'redirectUri' => 'none',
            'fields' => 'foo'
        ]);
    }

    public function testGetAuthorizationUrl()
    {
        $url = $this->provider->getAuthorizationUrl();
        $uri = parse_url($url);

        $this->assertEquals('/oauth/v2/authorization', $uri['path']);
    }

    public function testGetBaseAccessTokenUrl()
    {
        $params = [];

        $url = $this->provider->getBaseAccessTokenUrl($params);
        $uri = parse_url($url);

        $this->assertEquals('/oauth/v2/accessToken', $uri['path']);
    }

    public function testGetAccessToken()
    {
        $response = m::mock('Psr\Http\Message\ResponseInterface');
        $response->shouldReceive('getBody')->andReturn('{"access_token": "mock_access_token", "expires_in": 3600, "refresh_token": "mock_refresh_token", "refresh_token_expires_in": 7200}');
        $response->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $response->shouldReceive('getStatusCode')->andReturn(200);

        $client = m::mock('GuzzleHttp\ClientInterface');
        $client->shouldReceive('send')->times(1)->andReturn($response);
        $this->provider->setHttpClient($client);

        $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);

        $this->assertEquals('mock_access_token', $token->getToken());
        $this->assertLessThanOrEqual(time() + 3600, $token->getExpires());
        $this->assertGreaterThanOrEqual(time(), $token->getExpires());
        $this->assertEquals('mock_refresh_token', $token->getRefreshToken());
        $this->assertLessThanOrEqual(time() + 7200, $token->getRefreshTokenExpires());
        $this->assertGreaterThanOrEqual(time(), $token->getRefreshTokenExpires());
        $this->assertNull($token->getResourceOwnerId());
    }

    public function testUserData()
    {
        $apiProfileResponse = json_decode(file_get_contents(__DIR__.'/../../api_responses/me.json'), true);
        $apiEmailResponse = json_decode(file_get_contents(__DIR__.'/../../api_responses/email.json'), true);
        $somethingExtra = ['more' => uniqid()];
        $apiProfileResponse['somethingExtra'] = $somethingExtra;

        $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $postResponse->shouldReceive('getBody')->andReturn('{"access_token": "mock_access_token", "expires_in": 3600}');
        $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $postResponse->shouldReceive('getStatusCode')->andReturn(200);

        $userResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $userResponse->shouldReceive('getBody')->andReturn(json_encode($apiProfileResponse));
        $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $userResponse->shouldReceive('getStatusCode')->andReturn(200);

        $emailResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $emailResponse->shouldReceive('getBody')->andReturn(json_encode($apiEmailResponse));
        $emailResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $emailResponse->shouldReceive('getStatusCode')->andReturn(200);

        $client = m::mock('GuzzleHttp\ClientInterface');
        $client->shouldReceive('send')
            ->times(3)
            ->andReturn($postResponse, $userResponse, $emailResponse);
        $this->provider->setHttpClient($client);

        $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
        $user = $this->provider->getResourceOwner($token);

        $this->assertEquals('abcdef1234', $user->getId());
        $this->assertEquals('abcdef1234', $user->toArray()['id']);
        $this->assertEquals('John', $user->getFirstName());
        $this->assertEquals('John', $user->toArray()['localizedFirstName']);
        $this->assertEquals('Doe', $user->getLastName());
        $this->assertEquals('Doe', $user->toArray()['localizedLastName']);
        $this->assertEquals('http://example.com/avatar_800_800.jpeg', $user->getImageUrl());
        $this->assertEquals('https://www.linkedin.com/in/john-doe', $user->getUrl());
        $this->assertEquals('resource-owner@example.com', $user->getEmail());
        $this->assertEquals($somethingExtra, $user->getAttribute('somethingExtra'));
        $this->assertEquals($somethingExtra, $user->toArray()['somethingExtra']);
        $this->assertEquals($somethingExtra['more'], $user->getAttribute('somethingExtra.more'));
        $this->assertEquals([100, 200, 400, 800], $user->getImageSizes());
        $this->assertTrue(is_array($user->getImageBySize(100)));
        $this->assertNull($user->getImageBySize(300));
    }

    public function testMissingUserData()
    {
        $userId = rand(1000,9999);
        $firstName = uniqid();
        $lastName = uniqid();
        $apiProfileResponse = json_decode(file_get_contents(__DIR__.'/../../api_responses/me.json'), true);
        $apiEmailResponse = json_decode(file_get_contents(__DIR__.'/../../api_responses/email.json'), true);
        $apiProfileResponse['id'] = $userId;
        $apiProfileResponse['localizedFirstName'] = $firstName;
        $apiProfileResponse['localizedLastName'] = $lastName;
        unset($apiProfileResponse['profilePicture']);
        unset($apiProfileResponse['vanityName']);

        $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $postResponse->shouldReceive('getBody')->andReturn('{"access_token": "mock_access_token", "expires_in": 3600}');
        $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $postResponse->shouldReceive('getStatusCode')->andReturn(200);

        $userResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $userResponse->shouldReceive('getBody')->andReturn(json_encode($apiProfileResponse));
        $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $userResponse->shouldReceive('getStatusCode')->andReturn(200);

        $emailResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $emailResponse->shouldReceive('getBody')->andReturn(json_encode($apiEmailResponse));
        $emailResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $emailResponse->shouldReceive('getStatusCode')->andReturn(200);

        $client = m::mock('GuzzleHttp\ClientInterface');
        $client->shouldReceive('send')
            ->times(3)
            ->andReturn($postResponse, $userResponse, $emailResponse);
        $this->provider->setHttpClient($client);

        $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
        $user = $this->provider->getResourceOwner($token);

        $this->assertEquals($userId, $user->getId());
        $this->assertEquals($userId, $user->toArray()['id']);
        $this->assertEquals($firstName, $user->getFirstName());
        $this->assertEquals($firstName, $user->toArray()['localizedFirstName']);
        $this->assertEquals($lastName, $user->GeTlAsTnAmE()); // https://github.com/thephpleague/oauth2-linkedin/issues/4
        $this->assertEquals($lastName, $user->toArray()['localizedLastName']);
        $this->assertEquals(null, $user->getImageurl());
        $this->assertEquals(null, $user->getUrl());
    }

    public function testUserEmail()
    {
        $apiEmailResponse = json_decode(file_get_contents(__DIR__.'/../../api_responses/email.json'), true);

        $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $postResponse->shouldReceive('getBody')->andReturn('{"access_token": "mock_access_token", "expires_in": 3600}');
        $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $postResponse->shouldReceive('getStatusCode')->andReturn(200);

        $userResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $userResponse->shouldReceive('getBody')->andReturn(json_encode($apiEmailResponse));
        $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $userResponse->shouldReceive('getStatusCode')->andReturn(200);

        $client = m::mock('GuzzleHttp\ClientInterface');
        $client->shouldReceive('send')
            ->times(2)
            ->andReturn($postResponse, $userResponse);
        $this->provider->setHttpClient($client);

        $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
        $email = $this->provider->getResourceOwnerEmail($token);

        $this->assertEquals('resource-owner@example.com', $email);
    }

    public function testUserEmailNullIfApiResponseInvalid()
    {
        foreach ([null, []] as $apiEmailResponse) {
            $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
            $postResponse->shouldReceive('getBody')->andReturn('{"access_token": "mock_access_token", "expires_in": 3600}');
            $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
            $postResponse->shouldReceive('getStatusCode')->andReturn(200);

            $emailResponse = m::mock('Psr\Http\Message\ResponseInterface');
            $emailResponse->shouldReceive('getBody')->andReturn(json_encode($apiEmailResponse));
            $emailResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
            $emailResponse->shouldReceive('getStatusCode')->andReturn(200);

            $client = m::mock('GuzzleHttp\ClientInterface');
            $client->shouldReceive('send')
                ->times(2)
                ->andReturn($postResponse, $emailResponse);
            $this->provider->setHttpClient($client);

            $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
            $email = $this->provider->getResourceOwnerEmail($token);

            $this->assertNull($email);
        }
    }

    public function testResourceOwnerEmailNullWhenNotAuthorized()
    {
        $apiProfileResponse = json_decode(file_get_contents(__DIR__.'/../../api_responses/me.json'), true);

        $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $postResponse->shouldReceive('getBody')->andReturn('{"access_token": "mock_access_token", "expires_in": 3600}');
        $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $postResponse->shouldReceive('getStatusCode')->andReturn(200);

        $userResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $userResponse->shouldReceive('getBody')->andReturn(json_encode($apiProfileResponse));
        $userResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $userResponse->shouldReceive('getStatusCode')->andReturn(200);

        $emailResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $emailResponse->shouldReceive('getBody')->andReturn('{"message": "Not enough permissions to access: GET-members /clientAwareMemberHandles","status":403,"serviceErrorCode":100}');
        $emailResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $emailResponse->shouldReceive('getStatusCode')->andReturn(403);

        $client = m::mock('GuzzleHttp\ClientInterface');
        $client->shouldReceive('send')
            ->times(3)
            ->andReturn($postResponse, $userResponse, $emailResponse);
        $this->provider->setHttpClient($client);

        $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);

        $user = $this->provider->getResourceOwner($token);

        $this->assertNull($user->getEmail());

        $this->assertEquals('abcdef1234', $user->getId());
        $this->assertEquals('John', $user->getFirstName());
        $this->assertEquals('Doe', $user->getLastName());
        $this->assertEquals('http://example.com/avatar_800_800.jpeg', $user->getImageUrl());
        $this->assertEquals('https://www.linkedin.com/in/john-doe', $user->getUrl());
    }

    public function testExceptionThrownWhenEmailIsNotAuthorizedButRequestedFromAdapter()
    {
        $errorMessage = 'Not enough permissions to access: GET-members /clientAwareMemberHandles';
        $errorStatus = 403;
        $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $postResponse->shouldReceive('getBody')->andReturn('{"access_token": "mock_access_token", "expires_in": 3600}');
        $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $postResponse->shouldReceive('getStatusCode')->andReturn(200);

        $emailResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $emailResponse->shouldReceive('getBody')->andReturn('{"message": "'.$errorMessage.'","status":'.$errorStatus.',"serviceErrorCode":100}');
        $emailResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $emailResponse->shouldReceive('getStatusCode')->andReturn(403);

        $client = m::mock('GuzzleHttp\ClientInterface');
        $client->shouldReceive('send')
            ->times(2)
            ->andReturn($postResponse, $emailResponse);
        $this->provider->setHttpClient($client);

        $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);

        try {
            $this->provider->getResourceOwnerEmail($token);
        } catch (\Exception $exception) {
            $this->assertInstanceOf(
                'League\OAuth2\Client\Provider\Exception\LinkedInAccessDeniedException',
                $exception,
                'An invalid exception was thrown: '.get_class($exception)
            );
            $this->assertEquals($exception->getMessage(), $errorMessage);
            $this->assertEquals($exception->getCode(), $errorStatus);
            return;
        }
        $this->fail('No exception was thrown');
    }

    public function testExceptionThrownWhenErrorObjectReceived()
    {
        $errorMessage = uniqid();
        $errorStatus = rand(400,600);
        $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $postResponse->shouldReceive('getBody')->andReturn('{"message": "'.$errorMessage.'","status": '.$errorStatus.', "serviceErrorCode": 100}');
        $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $postResponse->shouldReceive('getStatusCode')->andReturn($errorStatus);

        $client = m::mock('GuzzleHttp\ClientInterface');
        $client->shouldReceive('send')
            ->times(1)
            ->andReturn($postResponse);
        $this->provider->setHttpClient($client);

        try {
            $token = $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
        } catch (\Exception $exception) {
            $this->assertInstanceOf(
                'League\OAuth2\Client\Provider\Exception\IdentityProviderException',
                $exception,
                'An invalid exception was thrown: '.get_class($exception)
            );
            $this->assertEquals($exception->getMessage(), $errorMessage);
            $this->assertEquals($exception->getCode(), $errorStatus);
            return;
        }
        $this->fail('No exception was thrown');
    }

    public function testProviderExceptionThrownWhenErrorObjectReceivedWithoutMessage()
    {
        $statusCode = rand(400,600);
        $postResponse = m::mock('Psr\Http\Message\ResponseInterface');
        $postResponse->shouldReceive('getBody')->andReturn('{"serviceErrorCode": 100}');
        $postResponse->shouldReceive('getHeader')->andReturn(['content-type' => 'json']);
        $postResponse->shouldReceive('getStatusCode')->andReturn($statusCode);
        $postResponse->shouldReceive('getReasonPhrase')->andReturn('mock reason phrase');

        $client = m::mock('GuzzleHttp\ClientInterface');
        $client->shouldReceive('send')
            ->times(1)
            ->andReturn($postResponse);
        $this->provider->setHttpClient($client);

        try {
            $this->provider->getAccessToken('authorization_code', ['code' => 'mock_authorization_code']);
        } catch (\Exception $e) {

            $this->assertInstanceOf(
                'League\OAuth2\Client\Provider\Exception\IdentityProviderException',
                $e,
                'Unexpected exception thrown'
            );

            $this->assertEquals($e->getMessage(), 'mock reason phrase');
            $this->assertEquals($e->getCode(), $statusCode);

            return;
        }

        $this->fail('No exception was thrown');
    }
}
