<?php
namespace ShlinkioTest\Shlink\Rest\Middleware;

use PHPUnit_Framework_TestCase as TestCase;
use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Entity\RestToken;
use Shlinkio\Shlink\Rest\Middleware\CheckAuthenticationMiddleware;
use Shlinkio\Shlink\Rest\Service\RestTokenService;
use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequestFactory;
use Zend\Expressive\Router\RouteResult;
use Zend\I18n\Translator\Translator;

class CheckAuthenticationMiddlewareTest extends TestCase
{
    /**
     * @var CheckAuthenticationMiddleware
     */
    protected $middleware;
    /**
     * @var ObjectProphecy
     */
    protected $tokenService;

    public function setUp()
    {
        $this->tokenService = $this->prophesize(RestTokenService::class);
        $this->middleware = new CheckAuthenticationMiddleware($this->tokenService->reveal(), Translator::factory([]));
    }

    /**
     * @test
     */
    public function someWhitelistedSituationsFallbackToNextMiddleware()
    {
        $request = ServerRequestFactory::fromGlobals();
        $response = new Response();
        $isCalled = false;
        $this->assertFalse($isCalled);
        $this->middleware->__invoke($request, $response, function ($req, $resp) use (&$isCalled) {
            $isCalled = true;
        });
        $this->assertTrue($isCalled);

        $request = ServerRequestFactory::fromGlobals()->withAttribute(
            RouteResult::class,
            RouteResult::fromRouteFailure(['GET'])
        );
        $response = new Response();
        $isCalled = false;
        $this->assertFalse($isCalled);
        $this->middleware->__invoke($request, $response, function ($req, $resp) use (&$isCalled) {
            $isCalled = true;
        });
        $this->assertTrue($isCalled);

        $request = ServerRequestFactory::fromGlobals()->withAttribute(
            RouteResult::class,
            RouteResult::fromRouteMatch('rest-authenticate', 'foo', [])
        );
        $response = new Response();
        $isCalled = false;
        $this->assertFalse($isCalled);
        $this->middleware->__invoke($request, $response, function ($req, $resp) use (&$isCalled) {
            $isCalled = true;
        });
        $this->assertTrue($isCalled);

        $request = ServerRequestFactory::fromGlobals()->withAttribute(
            RouteResult::class,
            RouteResult::fromRouteMatch('bar', 'foo', [])
        )->withMethod('OPTIONS');
        $response = new Response();
        $isCalled = false;
        $this->assertFalse($isCalled);
        $this->middleware->__invoke($request, $response, function ($req, $resp) use (&$isCalled) {
            $isCalled = true;
        });
        $this->assertTrue($isCalled);
    }

    /**
     * @test
     */
    public function noHeaderReturnsError()
    {
        $request = ServerRequestFactory::fromGlobals()->withAttribute(
            RouteResult::class,
            RouteResult::fromRouteMatch('bar', 'foo', [])
        );
        $response = $this->middleware->__invoke($request, new Response());
        $this->assertEquals(401, $response->getStatusCode());
    }

    /**
     * @test
     */
    public function provideAnExpiredTokenReturnsError()
    {
        $authToken = 'ABC-abc';
        $request = ServerRequestFactory::fromGlobals()->withAttribute(
            RouteResult::class,
            RouteResult::fromRouteMatch('bar', 'foo', [])
        )->withHeader(CheckAuthenticationMiddleware::AUTH_TOKEN_HEADER, $authToken);
        $this->tokenService->getByToken($authToken)->willReturn(
            (new RestToken())->setExpirationDate((new \DateTime())->sub(new \DateInterval('P1D')))
        )->shouldBeCalledTimes(1);

        $response = $this->middleware->__invoke($request, new Response());
        $this->assertEquals(401, $response->getStatusCode());
    }

    /**
     * @test
     */
    public function provideCorrectTokenUpdatesExpirationAndFallbacksToNextMiddleware()
    {
        $authToken = 'ABC-abc';
        $restToken = (new RestToken())->setExpirationDate((new \DateTime())->add(new \DateInterval('P1D')));
        $request = ServerRequestFactory::fromGlobals()->withAttribute(
            RouteResult::class,
            RouteResult::fromRouteMatch('bar', 'foo', [])
        )->withHeader(CheckAuthenticationMiddleware::AUTH_TOKEN_HEADER, $authToken);
        $this->tokenService->getByToken($authToken)->willReturn($restToken)->shouldBeCalledTimes(1);
        $this->tokenService->updateExpiration($restToken)->shouldBeCalledTimes(1);

        $isCalled = false;
        $this->assertFalse($isCalled);
        $this->middleware->__invoke($request, new Response(), function ($req, $resp) use (&$isCalled) {
            $isCalled = true;
        });
        $this->assertTrue($isCalled);
    }
}