- <?php
- /**
- * Base class for all controller test cases.
- *
- * @copyright 2012 Perforce Software. All rights reserved.
- * @license Please see LICENSE.txt in top-level folder of this distribution.
- * @version <release>/<patch>
- */
-
- namespace ModuleTest;
-
- use P4\Connection\ConnectionInterface;
- use P4\Spec\Protections;
- use P4Test\TestCase;
- use Zend\Dom\Query as DomQuery;
- use Zend\Mvc\ResponseSender\SendResponseEvent;
- use Zend\Mvc\Service\ServiceManagerConfig;
- use Zend\ServiceManager\ServiceManager;
- use Zend\Stdlib\Parameters;
-
- class TestControllerCase extends TestCase
- {
- protected $application;
- protected $configuration;
- protected $superP4;
- protected $userP4;
-
- /**
- * Extends parent by setting up the application and initializing the site.
- */
- public function setUp()
- {
- // run parent to set up perforce connection and prepare directories
- parent::setUp();
-
- // disable console to behave like we communicate over http
- \Zend\Console\Console::overrideIsConsole(false);
-
- // initialize the application
- $this->initApplication();
- }
-
- /**
- * Set application config file. Useful for testing of this class.
- *
- * @param string $configuration path to the application config file
- */
- public function setConfiguration($configuration)
- {
- $this->configuration = (string) $configuration;
- }
-
- /**
- * Return application configuration.
- *
- * @return array application configuration
- */
- public function getConfiguration()
- {
- $config = $this->configuration ?: array(
- 'modules' => array_map(
- 'basename',
- array_map('dirname', glob(BASE_PATH . '/module/*/Module.php'))
- ),
- 'module_listener_options' => array(
- 'module_paths' => array(BASE_PATH . '/module')
- ),
- );
-
- return is_string($config) ? include $config : $config;
- }
-
- /**
- * Return the mvc application instance. It will also initialize
- * the application if it was not done before.
- *
- * @return \Zend\Mvc\Application application instance
- */
- public function getApplication()
- {
- if (!$this->application) {
- $this->initApplication();
- }
-
- return $this->application;
- }
-
- /**
- * Reset the application.
- */
- public function resetApplication()
- {
- $this->application = null;
- }
-
- /**
- * Dispatch to a given url.
- *
- * @param string $url url to dispatch to
- * @param bool $autoCsrf optional - by default auto includes CSRF token as needed
- * @return string raw output likely the same as getResponse()->getContent()
- */
- public function dispatch($url, $autoCsrf = true)
- {
- // set url on the request and run the application
- $request = $this->getRequest();
- $request->setUri($url);
-
- // handle query parameters (if any)
- $uriQuery = $request->getUri()->getQueryAsArray();
- if ($request->getQuery()->count() == 0 && $uriQuery) {
- $request->setQuery(new Parameters($uriQuery));
- }
-
- // if autoCsrf is enabled and this isn't a get; include the correct token
- if ($autoCsrf && !$request->isGet()) {
- $request->getPost()->set('_csrf', $this->application->getServiceManager()->get('csrf')->getToken());
- }
-
- // run the application and capture the response in the output buffer
- ob_start();
- $this->getApplication()->run();
- return ob_get_clean();
- }
-
- /**
- * Get the request instance.
- *
- * @return \Zend\Stdlib\RequestInterface request instance
- */
- public function getRequest()
- {
- return $this->getApplication()->getRequest();
- }
-
- /**
- * Get the application response.
- *
- * @return \Zend\Stdlib\ResponseInterface mvc-event response
- */
- public function getResponse()
- {
- return $this->getApplication()->getResponse();
- }
-
- /**
- * Get the application result (usualy what is returned by the controller).
- *
- * @return mixed mvc-event result
- */
- public function getResult()
- {
- $event = $this->getApplication()->getMvcEvent();
- return $event->getResult();
- }
-
- /**
- * Evaluate if the given module was dispatched.
- *
- * @param string $moduleName name of the module to check
- * @param string $message optional message
- * @throws \PHPUnit_Framework_ExpectationFailedException
- */
- public function assertModule($moduleName, $message = '')
- {
- $this->addToAssertionCount(1);
-
- $controllerClass = $this->getMatchedControllerClass();
- $matchedModule = current(explode('\\', $controllerClass));
- $moduleName = strtolower($moduleName);
- $matchedModule = strtolower($matchedModule);
- if ($moduleName !== $matchedModule) {
- $this->fail(
- sprintf(
- "Failed asserting module name was '%s', actual module is '%s'\n%s",
- $moduleName,
- $matchedModule,
- $message
- )
- );
- }
- }
-
- /**
- * Evaluate if the given controller was dispatched.
- *
- * @param string $controllerClass name of the controller class to check
- * @param string $message optional message
- * @throws \PHPUnit_Framework_ExpectationFailedException
- */
- public function assertController($controllerClass, $message = '')
- {
- $this->addToAssertionCount(1);
-
- $controllerClass = strtolower($controllerClass);
- $matchedController = strtolower($this->getMatchedControllerClass());
- if ($controllerClass !== $matchedController) {
- $this->fail(
- sprintf(
- "Failed asserting controller class was '%s', actual controller is '%s'\n%s",
- $controllerClass,
- $matchedController,
- $message
- )
- );
- }
- }
-
- /**
- * Evaluate if the given action was dispatched.
- *
- * @param string $actionName name of the action to check
- * @param string $message optional message
- * @throws \PHPUnit_Framework_ExpectationFailedException
- */
- public function assertAction($actionName, $message = '')
- {
- $this->addToAssertionCount(1);
-
- $routeMatch = $this->getApplication()->getMvcEvent()->getRouteMatch();
- $actionName = strtolower($actionName);
- $matchedAction = strtolower($routeMatch->getParam('action'));
- if ($actionName !== $matchedAction) {
- $this->fail(
- sprintf(
- "Failed asserting action was '%s', actual action is '%s'\n%s",
- $actionName,
- $matchedAction,
- $message
- )
- );
- }
- }
-
- /**
- * Convenient function for evaluating what module & controller & action were
- * matched by the router in one method call.
- *
- * @param string $moduleName name of the module to check
- * @param string $controllerClass name of the controller class to check
- * @param string $actionName name of the action to check
- * @param string $message optional message
- */
- public function assertRouteMatch($moduleName, $controllerClass, $actionName, $message = '')
- {
- $this->assertModule($moduleName, $message);
- $this->assertController($controllerClass, $message);
- $this->assertAction($actionName, $message);
- }
-
- /**
- * Evaluate if the given route was used.
- *
- * @param string $routeName route name to check for
- * @param string $message optional message
- */
- public function assertRoute($routeName, $message = '')
- {
- $this->addToAssertionCount(1);
-
- $routeMatch = $this->getApplication()->getMvcEvent()->getRouteMatch();
- $routeName = strtolower($routeName);
- $matchedRouteName = strtolower($routeMatch->getMatchedRouteName());
- if ($routeName !== $matchedRouteName) {
- $this->fail(
- sprintf(
- "Failed asserting matched route was '%s', actual route is '%s'\n%s",
- $routeName,
- $matchedRouteName,
- $message
- )
- );
- }
- }
-
- /**
- * Evaluate if the status code from the response matches the given value.
- *
- * @param int $statusCode status code to check
- * @param string $message optional message
- * @throws \PHPUnit_Framework_ExpectationFailedException
- */
- public function assertResponseStatusCode($statusCode, $message = '')
- {
- $this->addToAssertionCount(1);
-
- $matchedCode = $this->getResponse()->getStatusCode();
- if ($statusCode !== $matchedCode) {
- $this->fail(
- sprintf(
- "Failed asserting response code is %d, actual value is %d\n%s",
- $statusCode,
- $matchedCode,
- $message
- )
- );
- }
- }
-
- /**
- * Evaluate the path to verify that it exists in the response body.
- *
- * @param string $path path to check
- * @throws \PHPUnit_Framework_ExpectationFailedException
- */
- public function assertQuery($path, $message = '')
- {
- $this->addToAssertionCount(1);
-
- $match = $this->query($path);
- if (count($match) <= 0) {
- $this->fail(
- sprintf(
- "Failed asserting node denoted by %s exists\n%s",
- $path,
- $message
- )
- );
- }
- }
-
- /**
- * Evaluate the path to verify that it doesn't exist in the response body.
- *
- * @param string $path path to check
- * @throws \PHPUnit_Framework_ExpectationFailedException
- */
- public function assertNotQuery($path, $message = '')
- {
- $this->addToAssertionCount(1);
-
- $match = $this->query($path);
- if (count($match) > 0) {
- $this->fail(
- sprintf(
- "Failed asserting node denoted by %s does not exist\n%s",
- $path,
- $message
- )
- );
- }
- }
-
- /**
- * Evaluate the path to verify that it occurs in the response body exactly
- * the given numer of times.
- *
- * @param string $path path to check
- * @throws \PHPUnit_Framework_ExpectationFailedException
- */
- public function assertQueryCount($path, $count, $message = '')
- {
- $this->addToAssertionCount(1);
-
- $match = $this->query($path);
- if (count($match) !== $count) {
- $this->fail(
- sprintf(
- "Failed asserting node denoted by %s occurs exactly %d times\n%s",
- $path,
- $count,
- $message
- )
- );
- }
- }
-
- /**
- * Evaluate the dom node specified by the path to verify that it contains the given content.
- * @param string $path dom path to check
- * @param string $content content to check for dom node value
- * @param string $message optional message
- */
- public function assertQueryContentContains($path, $content, $message = '')
- {
- $this->addToAssertionCount(1);
-
- $nodeList = $this->query($path);
- $found = false;
-
- $nodeList->rewind();
- while (!$found && $nodeList->valid()) {
- $found = strpos($nodeList->current()->nodeValue, $content) !== false;
- $nodeList->next();
- }
-
- if (!$found) {
- $this->fail(
- sprintf(
- "Failed asserting node denoted by %s contains %s\n%s",
- $path,
- $content,
- $message
- )
- );
- }
- }
-
- /**
- * Perform a CSS selector query. Return number of occurences the path
- * exists in the response body.
- *
- * @param string $path path to check for
- * @return \Zend\Dom\NodeList list of dom nodes with the given path
- */
- protected function query($path)
- {
- $dom = new DomQuery($this->getResponse()->getBody());
- return $dom->execute($path);
- }
-
- /**
- * Return class name of the controller matched during the route event.
- *
- * @return string matched controller class name
- */
- protected function getMatchedControllerClass()
- {
- $application = $this->getApplication();
- $routeMatch = $application->getMvcEvent()->getRouteMatch();
- $controller = $routeMatch->getParam('controller');
- $controllerManager = $application->getServiceManager()->get('ControllerLoader');
- return get_class($controllerManager->get($controller));
- }
-
- /**
- * Initialize the application - set the ServiceManager, load modules and
- * bootstrap it. It then takes the aggregated application/modules config,
- * substitutes values for testing and sets it back to the ServiceManager.
- * Also marks the request object to denote the testing environment.
- */
- protected function initApplication()
- {
- // load modules with default application configuration
- $configuration = $this->getConfiguration();
- $serviceManager = new ServiceManager(new ServiceManagerConfig());
- $serviceManager->setService('ApplicationConfig', $configuration);
- $serviceManager->get('ModuleManager')->loadModules();
-
- // configure service manager for testing
- $this->configureServiceManager($serviceManager);
-
- // mark request to denote we are in testing environment
- $application = $serviceManager->get('Application');
- $request = $application->getRequest();
- $request->isTest = true;
-
- // mark response event to denote we are testing
- $responseListener = $serviceManager->get('SendResponseListener')->getEventManager();
- $responseListener->attach(
- SendResponseEvent::EVENT_SEND_RESPONSE,
- function ($event) {
- $event->setParam('isTest', true);
- }
- );
-
- // bootstrap application
- $application = $serviceManager->get('Application');
- $application->bootstrap();
-
- $this->application = $application;
- }
-
- /**
- * Create a Perforce connection for testing. The perforce connection will
- * connect using a p4d started with the -i (run for inetd) flag.
- *
- * Extends parent to ensure we create a super, admin and plain user connection
- * (parent only makes super).
- *
- * Also, sets the admin connection as the default connection and $this->p4
- * ensuring the bulk of our work is done with those permissions (to more
- * accurately mirror our suggested deployment configuration).
- *
- * @param string|null $type allow caller to force the API
- * implementation.
- * @return P4\Connection\ConnectionInterface a Perforce API implementation
- */
- public function createP4Connection($type = null)
- {
- // let parent take care of the super user connection creation super
- // user will have the id defined by p4Params, tester by default
- parent::createP4Connection($type);
-
- // pull out the parent created super connection for later use
- $superP4 = $this->superP4 = $this->p4;
-
- // create the admin user and store its connection
- $adminP4 = $this->p4 = \P4\Connection\Connection::factory(
- $this->getP4Params('port'),
- 'admin',
- null,
- ''
- );
- $userForm = array(
- 'User' => 'admin',
- 'Email' => 'admin@testhost',
- 'FullName' => 'Admin User',
- 'Password' => ''
- );
- $adminP4->run('user', '-i', $userForm);
-
- // add 'admin' protections for our new admin user
- $protections = Protections::fetch($this->superP4);
- $protections->addProtection('admin', 'user', 'admin', '*', '//...');
- $protections->save();
-
- // clear the client from the super p4 and set a client on the new admin $this->p4
- $superP4->setClient(null);
- $clientForm = array(
- 'Client' => $this->p4Params['client'],
- 'Owner' => 'admin',
- 'Root' => $this->p4Params['clientRoot'] . '/adminuser',
- 'View' => array('//depot/... //' . $this->p4Params['client'] . '/...')
- );
- $superP4->run('client', array('-d', $this->p4Params['client']));
- $adminP4->run('client', '-i', $clientForm);
- $adminP4->setClient($this->p4Params['client']);
- \P4\Connection\Connection::setDefaultConnection($adminP4);
-
- // lastly create the nonadmin standard user account
- $userP4 = \P4\Connection\Connection::factory(
- $this->getP4Params('port'),
- 'nonadmin',
- null,
- ''
- );
-
- // actually create the user
- $userForm = array(
- 'User' => 'nonadmin',
- 'Email' => 'nonadmin@testhost',
- 'FullName' => 'Test User',
- 'Password' => ''
- );
- $userP4->run('user', '-i', $userForm);
-
- $this->userP4 = $userP4;
- }
-
- /**
- * Extend parrent to pass the super user connection as $this->p4 now refers to admin connection.
- *
- * @param string $user user to create
- * @param array $paths list of paths to grant user access to each path can be specified as:
- * ['path' => 'permission'] or ['path']
- * @param ConnectionInterafce $p4Super optional - super user connection needed
- * to modify protections table
- * @return Connection connection for the new user
- */
- public function connectWithAccess($user, array $paths, ConnectionInterface $p4Super = null)
- {
- return parent::connectWithAccess($user, $paths, $p4Super ?: $this->superP4);
- }
-
- /**
- * Configure service manager for testing environment.
- *
- * @param ServiceManager $serviceManager service manager instance
- */
- protected function configureServiceManager(ServiceManager $serviceManager)
- {
- // allow overriding
- $allowOverride = $serviceManager->getAllowOverride();
- $serviceManager->setAllowOverride(true);
-
- // set p4 factory to return p4 connection for testing
- if (!$this->p4 instanceof \P4\Connection\ConnectionInterface
- || !$this->userP4 instanceof \P4\Connection\ConnectionInterface
- || !$this->superP4 instanceof \P4\Connection\ConnectionInterface
- ) {
- $this->createP4Connection();
-
- // now that we have a non-admin user, ensure only admins can
- // access keys to allow us to actually exercise things.
- // this is only supported on 13.1+ servers so we do it selectively.
- if ($this->superP4->isServerMinVersion('2013.1')) {
- $this->superP4->run('configure', array('set', 'dm.keys.hide=1'));
- }
- }
-
- // (re)create the cache, client pool, and translator services for the admin account
- $adminP4 = $this->p4;
- $adminP4->setService(
- 'cache',
- function ($p4) {
- $cache = new \Record\Cache\Cache($p4);
- $cache->setCacheDir(DATA_PATH . '/cache');
- return $cache;
- }
- );
- $adminP4->setService(
- 'clients',
- function ($p4) {
- $clients = new \P4\ClientPool\ClientPool($p4);
- $clients->setMax(10)->setRoot(DATA_PATH . '/clients')->setPrefix('test-');
- return $clients;
- }
- );
- $adminP4->setService(
- 'translator',
- function ($p4) use ($serviceManager) {
- return $serviceManager->get('translator');
- }
- );
-
- // setup the super connection to use the same cache and translator and have a client pool
- $superP4 = $this->superP4;
- $superP4->setService(
- 'cache',
- function () use ($adminP4) {
- return $adminP4->getService('cache');
- }
- );
- $superP4->setService(
- 'clients',
- function ($p4) {
- $clients = new \P4\ClientPool\ClientPool($p4);
- $clients->setMax(10)->setRoot(DATA_PATH . '/clients')->setPrefix('test-');
- return $clients;
- }
- );
- $superP4->setService(
- 'translator',
- function () use ($adminP4) {
- return $adminP4->getService('translator');
- }
- );
-
- // (re)create the client pool and borrow the admin account's cache and translator services for the user account
- $userP4 = $this->userP4;
- $userP4->setService(
- 'clients',
- function ($p4) {
- $clients = new \P4\ClientPool\ClientPool($p4);
- $clients->setMax(10)->setRoot(DATA_PATH . '/clients')->setPrefix('test-');
- return $clients;
- }
- );
- $userP4->setService(
- 'cache',
- function () use ($adminP4) {
- return $adminP4->getService('cache');
- }
- );
- $userP4->setService(
- 'translator',
- function () use ($adminP4) {
- return $adminP4->getService('translator');
- }
- );
-
- // configure the application service manager to use our test connections
- $serviceManager->setFactory(
- 'p4',
- function () use ($userP4) {
- return $userP4;
- }
- );
- $serviceManager->setFactory(
- 'p4_admin',
- function () use ($adminP4) {
- return $adminP4;
- }
- );
- $serviceManager->setFactory(
- 'p4_super',
- function () use ($superP4) {
- return $superP4;
- }
- );
- $serviceManager->setFactory(
- 'p4_user',
- function () use ($userP4) {
- return $userP4;
- }
- );
-
- // pretend the non-admin user is logged in
- $serviceManager->setFactory(
- 'auth',
- function () {
- $storage = new \Zend\Authentication\Storage\NonPersistent;
- $storage->write(array('id' => 'nonadmin'));
- return new \Zend\Authentication\AuthenticationService($storage);
- }
- );
-
- // configure mail transport to write messages to disk
- $path = DATA_PATH . '/mail';
- $config = $serviceManager->get('config');
- $config['mail']['transport'] = array('path' => $path);
- $serviceManager->setService('config', $config);
- @mkdir($path);
- }
- }