<?php
namespace Backend\BaseBundle\Tests\EventListener;

use Backend\BaseBundle\Tests\Fixture\BaseTestCase;
use Backend\BaseBundle\EventListener\ControllerActionListener;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Backend\BaseBundle\Model\SiteUser;
use Backend\BaseBundle\Controller\Backend as BackendController;

/**
 * @group unit
 */
class ControllerActionListenerTest extends BaseTestCase
{
    public function test_extractAction_with_action()
    {
        //arrange
        $listener = new ControllerActionListener();
        $actionName = 'indexAction';
        $action = 'index';

        //act
        $result = $this->callObjectMethod($listener, 'extractAction', $actionName);

        //assert
        $this->assertEquals($action, $result);
    }

    public function test_extractAction()
    {
        //arrange
        $listener = new ControllerActionListener();
        $actionName = 'Index';
        $action = 'index';

        //act
        $result = $this->callObjectMethod($listener, 'extractAction', $actionName);

        //assert
        $this->assertEquals($action, $result);
    }

    public function test_hasAction_no_route()
    {
        //arrange
        $listener = new ControllerActionListener();
        $config = array();
        $controllerName = BackendController\SiteUserController::class;
        $action = 'index';

        //act
        $result = $this->callObjectMethod($listener, 'hasAction', $config, $controllerName, $action);

        //assert
        $this->assertFalse($result);
    }

    public function test_hasAction_no_action()
    {
        //arrange
        $listener = new ControllerActionListener();
        $config = array(
            'route' => array(),
        );
        $controllerName = BackendController\SiteUserController::class;
        $action = 'index';

        //act
        $result = $this->callObjectMethod($listener, 'hasAction', $config, $controllerName, $action);

        //assert
        $this->assertFalse($result);
    }

    public function test_hasAction_routename_not_match()
    {
        //arrange
        $listener = new ControllerActionListener();
        $config = array(
            'route' => array(
                'index' => 'not_match_route',
            ),
        );
        $controllerName = BackendController\SiteUserController::class;
        $action = 'index';

        //act
        $result = $this->callObjectMethod($listener, 'hasAction', $config, $controllerName, $action);

        //assert
        $this->assertFalse($result);
    }

    public function test_hasAction_all_match()
    {
        //arrange
        $listener = new ControllerActionListener();
        $config = array(
            'route' => array(
                'index' => 'backend_base_backend_siteuser_index',
            ),
        );
        $controllerName = BackendController\SiteUserController::class;
        $action = 'index';

        //act
        $result = $this->callObjectMethod($listener, 'hasAction', $config, $controllerName, $action);

        //assert
        $this->assertTrue($result);
    }

    /**
     * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    public function test_checkActionExist_not_functionEnabled()
    {
        //arrange
        $config = array(
            'route' => array(
                'index' => 'backend_base_backend_siteuser_index',
            ),
        );
        $requestController = BackendController\SiteUserController::class;
        $requestAction = 'testAction';
        $listener = $this->getMock(ControllerActionListener::class, array('hasAction', 'extractAction', 'isFunctionEnabled'));
        $listener->expects($this->once())
            ->method('extractAction')
            ->willReturnCallback(function($action){
                $this->assertEquals('testAction', $action);
                return 'index';
            });
        $listener->expects($this->once())
            ->method('isFunctionEnabled')
            ->willReturn(false);
        $listener->expects($this->never())
            ->method('hasAction')
            ->willReturnCallback(function($resultConfig, $controllerName, $action) use($config){
                $this->assertEquals($config, $resultConfig);
                $this->assertEquals('index', $action);
                $this->assertEquals('Backend\\BaseBundle\\Controller\\SiteUserController', $controllerName);
                return true;
            });
        //act
        $this->callObjectMethod($listener, 'checkActionExist', $config, $requestController, $requestAction);

        //arrange
        //nothing happend
    }

    public function test_checkActionExist_has_action()
    {
        //arrange
        $config = array(
            'route' => array(
                'index' => 'backend_base_backend_siteuser_index',
            ),
        );
        $requestController = BackendController\SiteUserController::class;
        $requestAction = 'testAction';
        $listener = $this->getMock(ControllerActionListener::class, array('hasAction', 'extractAction', 'isFunctionEnabled'));
        $listener->expects($this->once())
            ->method('extractAction')
            ->willReturnCallback(function($action){
                $this->assertEquals('testAction', $action);
                return 'index';
            });
        $listener->expects($this->once())
            ->method('isFunctionEnabled')
            ->willReturn(true);
        $listener->expects($this->once())
            ->method('hasAction')
            ->willReturnCallback(function($resultConfig, $controllerName, $action) use($config){
                $this->assertEquals($config, $resultConfig);
                $this->assertEquals('index', $action);
                $this->assertEquals(BackendController\SiteUserController::class, $controllerName);
                return true;
            });
        //act
        $this->callObjectMethod($listener, 'checkActionExist', $config, $requestController, $requestAction);

        //arrange
        //nothing happend
    }

    public function test_checkActionExist_not_internal_action()
    {
        //arrange
        $config = array(
            'route' => array(
                'index' => 'backend_base_backend_siteuser_index',
            ),
        );
        $requestController = BackendController\SiteUserController::class;
        $requestAction = 'testAction';
        $listener = $this->getMock(ControllerActionListener::class, array('hasAction', 'extractAction', 'isFunctionEnabled'));

        $listener->expects($this->once())
            ->method('extractAction')
            ->willReturnCallback(function($action){
                $this->assertEquals('testAction', $action);
                return 'demo';
            });
        $listener->expects($this->once())
            ->method('isFunctionEnabled')
            ->willReturn(true);

        $listener->expects($this->never())
            ->method('hasAction')
            ->willReturnCallback(function($resultConfig, $controllerName, $action) use($config){
                $this->assertEquals($config, $resultConfig);
                $this->assertEquals('index', $action);
                $this->assertEquals(BackendController\SiteUserController::class, $controllerName);
                return false;
            });
        //act
        $this->callObjectMethod($listener, 'checkActionExist', $config, $requestController, $requestAction);

        //arrange
        //nothing happend
    }

    /**
     * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    public function test_checkActionExist_has_no_action()
    {
        //arrange
        $config = array(
            'route' => array(
                'index' => 'backend_base_backend_siteuser_index',
            ),
        );
        $requestController = BackendController\SiteUserController::class;
        $requestAction = 'testAction';
        $listener = $this->getMock(ControllerActionListener::class, array('hasAction', 'extractAction', 'isFunctionEnabled'));

        $listener->expects($this->once())
            ->method('extractAction')
            ->willReturnCallback(function($action){
                $this->assertEquals('testAction', $action);
                return 'index';
            });
        $listener->expects($this->once())
            ->method('isFunctionEnabled')
            ->willReturn(true);
        $listener->expects($this->once())
            ->method('hasAction')
            ->willReturnCallback(function($resultConfig, $controllerName, $action) use($config){
                $this->assertEquals($config, $resultConfig);
                $this->assertEquals('index', $action);
                $this->assertEquals(BackendController\SiteUserController::class, $controllerName);
                return false;
            });
        //act
        $this->callObjectMethod($listener, 'checkActionExist', $config, $requestController, $requestAction);

        //arrange
        //throw exception
    }

    public function test_checkRole_has_role()
    {
        //arrange
        $config = array(
            'security' => array(
                'index' => 'ROLE_TEST',
            ),
        );
        $requestAction = 'testAction';
        $listener = $this->getMock(ControllerActionListener::class, array('hasRoleOrSuperAdmin', 'extractAction'));
        $listener->expects($this->once())
            ->method('hasRoleOrSuperAdmin')
            ->willReturnCallback(function($resultConfig, $action) use($config){
                $this->assertEquals($config, $resultConfig);
                $this->assertEquals('index', $action);
                return true;
            });
        $listener->expects($this->once())
            ->method('extractAction')
            ->willReturnCallback(function($action){
                $this->assertEquals('testAction', $action);
                return 'index';
            });

        //act
        $this->callObjectMethod($listener, 'checkRole', $config, $requestAction);

        //arrange
        //nothing happend
    }

    /**
     * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
     */
    public function test_checkRole_has_no_role()
    {
        //arrange
        $config = array(
            'security' => array(
                'index' => 'ROLE_TEST',
            ),
        );
        $requestAction = 'testAction';
        $listener = $this->getMock(ControllerActionListener::class, array('hasRoleOrSuperAdmin', 'extractAction'));
        $listener->expects($this->once())
            ->method('hasRoleOrSuperAdmin')
            ->willReturnCallback(function($resultConfig, $action) use($config){
                $this->assertEquals($config, $resultConfig);
                $this->assertEquals('index', $action);
                return false;
            });
        $listener->expects($this->once())
            ->method('extractAction')
            ->willReturnCallback(function($action){
                $this->assertEquals('testAction', $action);
                return 'index';
            });

        //act
        $this->callObjectMethod($listener, 'checkRole', $config, $requestAction);

        //arrange
        //nothing happend
    }

    public function test_hasRoleOrSuperAdmin_no_config_security()
    {
        //arrange
        $config = array();
        $action = 'index';
        $listener = new ControllerActionListener();

        //act
        $result = $this->callObjectMethod($listener, 'hasRoleOrSuperAdmin', $config, $action);

        //assert
        $this->assertTrue($result);
    }

    public function test_hasRoleOrSuperAdmin_no_config_security_action()
    {
        //arrange
        $config = array(
            'security' => array(),
        );
        $action = 'index';
        $listener = new ControllerActionListener();

        //act
        $result = $this->callObjectMethod($listener, 'hasRoleOrSuperAdmin', $config, $action);

        //assert
        $this->assertTrue($result);
    }

    public function test_hasRoleOrSuperAdmin_no_token()
    {
        //arrange
        $config = array(
            'security' => array(
                'index' => 'ROLE_TEST',
            ),
        );
        $action = 'index';
        $tokenStorage = $this->getMock(TokenStorage::class, array('getToken'));
        $tokenStorage->expects($this->once())
            ->method('getToken')
            ->willReturn(null);
        $listener = new ControllerActionListener();
        $this->setObjectAttribute($listener, 'tokenStorage', $tokenStorage);

        //act
        $result = $this->callObjectMethod($listener, 'hasRoleOrSuperAdmin', $config, $action);

        //assert
        $this->assertFalse($result);
    }

    public function test_hasRoleOrSuperAdmin_no_user()
    {
        //arrange
        $config = array(
            'security' => array(
                'index' => 'ROLE_TEST',
            ),
        );
        $action = 'index';
        $token = $this->getMock(UsernamePasswordToken::class, array('getUser'), array(), '', false);
        $token->expects($this->once())
            ->method('getUser')
            ->willReturn(null);
        $tokenStorage = $this->getMock(TokenStorage::class, array('getToken'));
        $tokenStorage->expects($this->once())
            ->method('getToken')
            ->willReturn($token);
        $listener = new ControllerActionListener();
        $this->setObjectAttribute($listener, 'tokenStorage', $tokenStorage);

        //act
        $result = $this->callObjectMethod($listener, 'hasRoleOrSuperAdmin', $config, $action);

        //assert
        $this->assertFalse($result);
    }

    public function test_hasRoleOrSuperAdmin_mock_language()
    {
        //arrange
        $config = array(
            'security' => array(
                'index' => 'ROLE_TEST',
            ),
        );
        $user = new SiteUser();
        $roles = array('ROLE1', 'ROLE2');
        $user->setDefaultRoles($roles);
        $action = 'index';
        $language = $this->getMock(ExpressionLanguage::class, array('evaluate'));
        $language->expects($this->once())
            ->method('evaluate')
            ->willReturnCallback(function($expression, $variable) use($user, $roles){
                $this->assertEquals(array(
                    'user' => $user,
                    'roles' => $roles,
                ), $variable);
                return false;
            });
        $token = $this->getMock(UsernamePasswordToken::class, array('getUser'), array(), '', false);
        $token->expects($this->once())
            ->method('getUser')
            ->willReturn($user);
        $tokenStorage = $this->getMock(TokenStorage::class, array('getToken'));
        $tokenStorage->expects($this->once())
            ->method('getToken')
            ->willReturn($token);
        $listener = new ControllerActionListener();
        $this->setObjectAttribute($listener, 'tokenStorage', $tokenStorage);
        $this->setObjectAttribute($listener, 'language', $language);

        //act
        $result = $this->callObjectMethod($listener, 'hasRoleOrSuperAdmin', $config, $action);

        //assert
        $this->assertFalse($result);
    }

    public function test_hasRoleOrSuperAdmin()
    {
        //arrange
        $config = array(
            'security' => array(
                'index' => 'ROLE_TEST',
            ),
        );
        $user = new SiteUser();
        $roles = array('ROLE1', 'ROLE2');
        $user->setDefaultRoles($roles);
        $action = 'index';
        $language = $this->getMock(ExpressionLanguage::class, array('evaluate'));
        $language->expects($this->once())
            ->method('evaluate')
            ->willReturnCallback(function($expression, $variable) use($user, $roles){
                $this->assertEquals(array(
                    'user' => $user,
                    'roles' => $roles,
                ), $variable);
                return true;
            });
        $token = $this->getMock(UsernamePasswordToken::class, array('getUser'), array(), '', false);
        $token->expects($this->once())
            ->method('getUser')
            ->willReturn($user);
        $tokenStorage = $this->getMock(TokenStorage::class, array('getToken'));
        $tokenStorage->expects($this->once())
            ->method('getToken')
            ->willReturn($token);
        $listener = new ControllerActionListener();
        $this->setObjectAttribute($listener, 'tokenStorage', $tokenStorage);
        $this->setObjectAttribute($listener, 'language', $language);

        //act
        $result = $this->callObjectMethod($listener, 'hasRoleOrSuperAdmin', $config, $action);

        //assert
        $this->assertTrue($result);
    }
}