More dependency injection, and code coverage

This commit is contained in:
Timothy Warren 2015-06-30 13:03:20 -04:00
parent ef4e9b3e85
commit c33575e3d8
21 changed files with 402 additions and 69 deletions

View File

@ -33,9 +33,9 @@ class BaseApiModel extends BaseModel {
/** /**
* Constructor * Constructor
*/ */
public function __construct() public function __construct(Config $config)
{ {
parent::__construct(); parent::__construct($config);
$this->cookieJar = new CookieJar(); $this->cookieJar = new CookieJar();
$this->client = new Client([ $this->client = new Client([

View File

@ -39,12 +39,19 @@ class BaseController {
* Common data to be sent to views * Common data to be sent to views
* @var array * @var array
*/ */
protected $base_data = []; protected $base_data = [
'url_type' => 'anime',
'other_type' => 'manga',
'nav_routes' => []
];
/** /**
* Constructor * Constructor
*
* @param \AnimeClient\Client $config
* @param array $web
*/ */
public function __construct(Config $config, Array $web) public function __construct(Config &$config, Array $web)
{ {
$this->config = $config; $this->config = $config;
@ -53,6 +60,11 @@ class BaseController {
$this->response = $response; $this->response = $response;
} }
/**
* Destructor
*
* @codeCoverageIgnore
*/
public function __destruct() public function __destruct()
{ {
$this->output(); $this->output();
@ -61,6 +73,7 @@ class BaseController {
/** /**
* Get the string output of a partial template * Get the string output of a partial template
* *
* @codeCoverageIgnore
* @param string $template * @param string $template
* @param array|object $data * @param array|object $data
* @return string * @return string
@ -99,6 +112,7 @@ class BaseController {
/** /**
* Output a template to HTML, using the provided data * Output a template to HTML, using the provided data
* *
* @codeCoverageIgnore
* @param string $template * @param string $template
* @param array|object $data * @param array|object $data
* @return void * @return void
@ -131,6 +145,7 @@ class BaseController {
/** /**
* Redirect to the selected page * Redirect to the selected page
* *
* @codeCoverageIgnore
* @param string $url * @param string $url
* @param int $code * @param int $code
* @return void * @return void
@ -152,6 +167,7 @@ class BaseController {
/** /**
* Add a message box to the page * Add a message box to the page
* *
* @codeCoverageIgnore
* @param string $type * @param string $type
* @param string $message * @param string $message
* @return string * @return string
@ -167,6 +183,7 @@ class BaseController {
/** /**
* Clear the api session * Clear the api session
* *
* @codeCoverageIgnore
* @return void * @return void
*/ */
public function logout() public function logout()
@ -178,6 +195,7 @@ class BaseController {
/** /**
* Show the login form * Show the login form
* *
* @codeCoverageIgnore
* @param string $status * @param string $status
* @return void * @return void
*/ */
@ -220,6 +238,7 @@ class BaseController {
/** /**
* Send the appropriate response * Send the appropriate response
* *
* @codeCoverageIgnore
* @return void * @return void
*/ */
private function output() private function output()

View File

@ -23,9 +23,9 @@ class BaseDBModel extends BaseModel {
/** /**
* Constructor * Constructor
*/ */
public function __construct() public function __construct(Config $config)
{ {
parent::__construct(); parent::__construct($config);
$this->db_config = $this->config->database; $this->db_config = $this->config->database;
} }
} }

View File

@ -20,9 +20,8 @@ class BaseModel {
/** /**
* Constructor * Constructor
*/ */
public function __construct() public function __construct(Config &$config)
{ {
global $config;
$this->config = $config; $this->config = $config;
} }

View File

@ -22,12 +22,24 @@ class Router {
*/ */
protected $config; protected $config;
/**
* Class wrapper for input superglobals
* @var object
*/
protected $request;
/** /**
* Array containing request and response objects * Array containing request and response objects
* @var array $web * @var array $web
*/ */
protected $web; protected $web;
/**
* Routes added to router
* @var array $output_routes
*/
protected $output_routes;
/** /**
* Constructor * Constructor
* *
@ -37,9 +49,10 @@ class Router {
{ {
$this->config = $config; $this->config = $config;
$this->router = $router; $this->router = $router;
$this->request = $request;
$this->web = [$request, $response]; $this->web = [$request, $response];
$this->_setup_routes(); $this->output_routes = $this->_setup_routes();
} }
/** /**
@ -51,7 +64,7 @@ class Router {
{ {
global $defaultHandler; global $defaultHandler;
$raw_route = $_SERVER['REQUEST_URI']; $raw_route = $this->request->server->get('REQUEST_URI');
$route_path = str_replace([$this->config->anime_path, $this->config->manga_path], '', $raw_route); $route_path = str_replace([$this->config->anime_path, $this->config->manga_path], '', $raw_route);
$route_path = "/" . trim($route_path, '/'); $route_path = "/" . trim($route_path, '/');
@ -64,9 +77,20 @@ class Router {
return $route; return $route;
} }
/**
* Get list of routes applied
*
* @return array
*/
public function get_output_routes()
{
return $this->output_routes;
}
/** /**
* Handle the current route * Handle the current route
* *
* @codeCoverageIgnore
* @param [object] $route * @param [object] $route
* @return void * @return void
*/ */
@ -84,7 +108,7 @@ class Router {
$failure = $this->router->getFailedRoute(); $failure = $this->router->getFailedRoute();
$defaultHandler->addDataTable('failed_route', (array)$failure); $defaultHandler->addDataTable('failed_route', (array)$failure);
$controller_name = 'BaseController'; $controller_name = '\\AnimeClient\\BaseController';
$action_method = 'outputHTML'; $action_method = 'outputHTML';
$params = [ $params = [
'template' => '404', 'template' => '404',
@ -117,59 +141,101 @@ class Router {
call_user_func_array([$controller, $action_method], $params); call_user_func_array([$controller, $action_method], $params);
} }
/**
* Get the type of route, to select the current controller
*
* @return string
*/
public function get_route_type()
{
$route_type = "";
$host = $this->request->server->get("HTTP_HOST");
$request_uri = $this->request->server->get('REQUEST_URI');
// Host-based controller selection
if ($this->config->route_by === "host")
{
if (strtolower($host) === strtolower($this->config->anime_host))
{
$route_type = "anime";
}
if (strtolower($host) === strtolower($this->config->manga_host))
{
$route_type = "manga";
}
}
// Path-based controller selection
if ($this->config->route_by === "path")
{
$path = trim($request_uri, '/');
if (stripos($path, trim($this->config->anime_path, '/')) === 0)
{
$route_type = "anime";
}
if (stripos($path, trim($this->config->manga_path, '/')) === 0)
{
$route_type = "manga";
}
}
return $route_type;
}
/** /**
* Select controller based on the current url, and apply its relevent routes * Select controller based on the current url, and apply its relevent routes
* *
* @return void * @return array
*/ */
private function _setup_routes() public function _setup_routes()
{ {
$route_map = [ $route_map = [
'anime' => '\\AnimeClient\\AnimeController', 'anime' => '\\AnimeClient\\AnimeController',
'manga' => '\\AnimeClient\\MangaController', 'manga' => '\\AnimeClient\\MangaController',
]; ];
$route_type = "anime";
if ($this->config->manga_host !== "" && strpos($_SERVER['HTTP_HOST'], $this->config->manga_host) !== FALSE) $output_routes = [];
{
$route_type = "manga";
}
else if ($this->config->manga_path !== "" && strpos($_SERVER['REQUEST_URI'], $this->config->manga_path) !== FALSE)
{
$route_type = "manga";
}
$routes = $this->config->routes; $route_type = $this->get_route_type();
// Return early if invalid route array
if ( ! array_key_exists($route_type, $this->config->routes)) return [];
$applied_routes = array_merge($this->config->routes['common'], $this->config->routes[$route_type]);
// Add routes // Add routes
foreach(['common', $route_type] as $key) foreach($applied_routes as $name => &$route)
{ {
foreach($routes[$key] as $name => &$route) $path = $route['path'];
unset($route['path']);
// Prepend the controller to the route parameters
array_unshift($route['action'], $route_map[$route_type]);
// Select the appropriate router method based on the http verb
$add = (array_key_exists('verb', $route)) ? "add" . ucfirst(strtolower($route['verb'])) : "addGet";
// Add the route to the router object
if ( ! array_key_exists('tokens', $route))
{ {
$path = $route['path']; $output_routes[] = $this->router->$add($name, $path)->addValues($route);
unset($route['path']); }
else
{
$tokens = $route['tokens'];
unset($route['tokens']);
// Prepend the controller to the route parameters $output_routes[] = $this->router->$add($name, $path)
array_unshift($route['action'], $route_map[$route_type]); ->addValues($route)
->addTokens($tokens);
// Select the appropriate router method based on the http verb
$add = (array_key_exists('verb', $route)) ? "add" . ucfirst(strtolower($route['verb'])) : "addGet";
if ( ! array_key_exists('tokens', $route))
{
$this->router->$add($name, $path)->addValues($route);
}
else
{
$tokens = $route['tokens'];
unset($route['tokens']);
$this->router->$add($name, $path)
->addValues($route)
->addTokens($tokens);
}
} }
} }
return $output_routes;
} }
} }
// End of Router.php // End of Router.php

View File

@ -6,8 +6,6 @@ use \Whoops\Handler\PrettyPageHandler;
use \Whoops\Handler\JsonResponseHandler; use \Whoops\Handler\JsonResponseHandler;
use \Aura\Web\WebFactory; use \Aura\Web\WebFactory;
use \Aura\Router\RouterFactory; use \Aura\Router\RouterFactory;
use \GuzzleHttp\Client;
use \GuzzleHttp\Cookie\CookieJar;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Setup error handling // Setup error handling

View File

@ -22,10 +22,11 @@ $config = [
// Routing // Routing
// //
// Route by path, or route by domain. To route by path, set the _host suffixed // Route by path, or route by domain. To route by path, set the _host suffixed
// options to an empty string. To route by host, set the _path suffixed options // options to an empty string, and set 'route_by' to 'path'. To route by host, set
// to an empty string // the _path suffixed options to an empty string, and set 'route_by' to 'host'.
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
'route_by' => 'host', // host or path
'anime_host' => 'anime.timshomepage.net', 'anime_host' => 'anime.timshomepage.net',
'manga_host' => 'manga.timshomepage.net', 'manga_host' => 'manga.timshomepage.net',
'anime_path' => '', 'anime_path' => '',

View File

@ -54,8 +54,8 @@ class AnimeController extends BaseController {
unset($this->nav_routes['Collection']); unset($this->nav_routes['Collection']);
} }
$this->model = new AnimeModel(); $this->model = new AnimeModel($config);
$this->collection_model = new AnimeCollectionModel(); $this->collection_model = new AnimeCollectionModel($config);
$this->base_data = [ $this->base_data = [
'message' => '', 'message' => '',
'url_type' => 'anime', 'url_type' => 'anime',

View File

@ -41,7 +41,7 @@ class MangaController extends BaseController {
public function __construct(Config $config, Array $web) public function __construct(Config $config, Array $web)
{ {
parent::__construct($config, $web); parent::__construct($config, $web);
$this->model = new MangaModel(); $this->model = new MangaModel($config);
$this->base_data = [ $this->base_data = [
'url_type' => 'manga', 'url_type' => 'manga',
'other_type' => 'anime', 'other_type' => 'anime',

View File

@ -25,12 +25,12 @@ class AnimeCollectionModel extends BaseDBModel {
/** /**
* Constructor * Constructor
*/ */
public function __construct() public function __construct(Config $config)
{ {
parent::__construct(); parent::__construct($config);
$this->db = \Query($this->db_config['collection']); $this->db = \Query($this->db_config['collection']);
$this->anime_model = new AnimeModel(); $this->anime_model = new AnimeModel($config);
// Is database valid? If not, set a flag so the // Is database valid? If not, set a flag so the
// app can be run without a valid database // app can be run without a valid database

View File

@ -18,9 +18,9 @@ class AnimeModel extends BaseApiModel {
/** /**
* Constructor * Constructor
*/ */
public function __construct() public function __construct(Config $config)
{ {
parent::__construct(); parent::__construct($config);
} }
/** /**

View File

@ -15,6 +15,13 @@ class MangaModel extends BaseApiModel {
*/ */
protected $base_url = "https://hummingbird.me/"; protected $base_url = "https://hummingbird.me/";
/**
* Constructor
*/
public function __construct(Config $config)
{
parent::__construct($config);
}
/** /**
* Update the selected manga * Update the selected manga

View File

@ -11,7 +11,11 @@
</head> </head>
<body class="<?= $url_type ?> list"> <body class="<?= $url_type ?> list">
<h1 class="flex flex-align-end flex-wrap"> <h1 class="flex flex-align-end flex-wrap">
<span class="flex-no-wrap grow-1"><?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?> [<a href="<?= full_url("", $other_type) ?>"><?= ucfirst($other_type) ?> List</a>]</span> <span class="flex-no-wrap grow-1">
<a href="<?= full_url("", $url_type) ?>">
<?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?>
</a> [<a href="<?= full_url("", $other_type) ?>"><?= ucfirst($other_type) ?> List</a>]
</span>
<span class="flex-no-wrap small-font"> <span class="flex-no-wrap small-font">
<?php if (is_logged_in()): ?> <?php if (is_logged_in()): ?>
[<a href="<?= full_url("/logout", $url_type) ?>">Logout</a>] [<a href="<?= full_url("/logout", $url_type) ?>">Logout</a>]
@ -20,6 +24,7 @@
<?php endif ?> <?php endif ?>
</span> </span>
</h1> </h1>
<?php if ( ! empty($nav_routes)): ?>
<nav> <nav>
<ul> <ul>
<?php foreach($nav_routes as $title => $nav_path): ?> <?php foreach($nav_routes as $title => $nav_path): ?>
@ -33,3 +38,4 @@
</ul> </ul>
</nav> </nav>
<br /> <br />
<?php endif ?>

View File

@ -3,8 +3,8 @@
<aside> <aside>
<form method="post" action="<?= full_url('/login', $url_type) ?>"> <form method="post" action="<?= full_url('/login', $url_type) ?>">
<dl> <dl>
<dt><label for="username">Username: </label></dt> <?php /*<dt><label for="username">Username: </label></dt>
<dd><input type="text" id="username" name="username" required="required" /></dd> <dd><input type="text" id="username" name="username" required="required" /></dd>*/ ?>
<dt><label for="password">Password: </label></dt> <dt><label for="password">Password: </label></dt>
<dd><input type="password" id="password" name="password" required="required" /></dd> <dd><input type="password" id="password" name="password" required="required" /></dd>

View File

@ -18,5 +18,8 @@
<php> <php>
<server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" /> <server name="HTTP_USER_AGENT" value="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0" />
<server name="HTTP_HOST" value="localhost" /> <server name="HTTP_HOST" value="localhost" />
<server name="SERVER_NAME" value="localhost" />
<server name="REQUEST_URI" value="/" />
<server name="REQUEST_METHOD" value="GET" />
</php> </php>
</phpunit> </phpunit>

View File

@ -4,9 +4,9 @@ use \AnimeClient\BaseApiModel;
class MockBaseApiModel extends BaseApiModel { class MockBaseApiModel extends BaseApiModel {
public function __construct() public function __construct(\AnimeClient\Config $config)
{ {
parent::__construct(); parent::__construct($config);
} }
public function __get($key) public function __get($key)
@ -19,7 +19,7 @@ class BaseApiModelTest extends AnimeClient_TestCase {
public function testBaseApiModelSanity() public function testBaseApiModelSanity()
{ {
$baseApiModel = new MockBaseApiModel(); $baseApiModel = new MockBaseApiModel($this->config);
// Some basic type checks for class memebers // Some basic type checks for class memebers
$this->assertInstanceOf('\AnimeClient\BaseModel', $baseApiModel); $this->assertInstanceOf('\AnimeClient\BaseModel', $baseApiModel);

View File

@ -0,0 +1,31 @@
<?php
use \AnimeClient\BaseController;
use \Aura\Web\WebFactory;
use \Aura\Router\RouterFactory;
class BaseControllerTest extends AnimeClient_TestCase {
public function setUp()
{
parent::setUp();
// Create Request/Response Objects
$web_factory = new WebFactory([
'_GET' => [],
'_POST' => [],
'_COOKIE' => [],
'_SERVER' => $_SERVER,
'_FILES' => []
]);
$request = $web_factory->newRequest();
$response = $web_factory->newResponse();
$this->BaseController = new BaseController($this->config, [$request, $response]);
}
public function testBaseControllerSanity()
{
$this->assertTrue(is_object($this->BaseController));
}
}

View File

@ -6,7 +6,7 @@ class BaseDBModelTest extends AnimeClient_TestCase {
public function testBaseDBModelSanity() public function testBaseDBModelSanity()
{ {
$baseDBModel = new BaseDBModel(); $baseDBModel = new BaseDBModel($this->config);
$this->assertTrue(is_object($baseDBModel)); $this->assertTrue(is_object($baseDBModel));
} }
} }

View File

@ -6,7 +6,7 @@ class BaseModelTest extends AnimeClient_TestCase {
public function testBaseModelSanity() public function testBaseModelSanity()
{ {
$baseModel = new BaseModel(); $baseModel = new BaseModel($this->config);
$this->assertTrue(is_object($baseModel)); $this->assertTrue(is_object($baseModel));
} }
} }

192
tests/base/RouterTest.php Normal file
View File

@ -0,0 +1,192 @@
<?php
use AnimeClient\Router;
use AnimeClient\Config;
use Aura\Web\WebFactory;
use Aura\Router\RouterFactory;
class RouterTest extends AnimeClient_TestCase {
protected $aura_router;
protected $request;
protected $response;
protected $router;
public function testRouterSanity()
{
parent::setUp();
$router_factory = new RouterFactory();
$this->aura_router = $router_factory->newInstance();
// Create Request/Response Objects
$web_factory = new WebFactory([
'_GET' => [],
'_POST' => [],
'_COOKIE' => [],
'_SERVER' => $_SERVER,
'_FILES' => []
]);
$this->request = $web_factory->newRequest();
$this->response = $web_factory->newResponse();
$this->router = new Router($this->config, $this->aura_router, $this->request, $this->response);
$this->assertTrue(is_object($this->router));
}
public function RouteTestProvider()
{
return [
'manga_path_routing' => array(
'config' => array(
'config' => [
'anime_host' => '',
'manga_host' => '',
'anime_path' => 'anime',
'manga_path' => 'manga',
'route_by' => 'path',
],
'base_config' => []
),
'type' => 'manga',
'host' => "localhost",
'uri' => "/manga/plan_to_read",
'check_var' => 'manga_path'
),
'manga_host_routing' => array(
'config' => array(
'config' => [
'anime_host' => 'anime.host.me',
'manga_host' => 'manga.host.me',
'anime_path' => '',
'manga_path' => '',
'route_by' => 'host',
],
'base_config' => []
),
'type' => 'manga',
'host' => 'manga.host.me',
'uri' => '/plan_to_read',
'check_var' => 'manga_host'
),
'anime_path_routing' => array(
'config' => array(
'config' => [
'anime_host' => '',
'manga_host' => '',
'anime_path' => 'anime',
'manga_path' => 'manga',
'route_by' => 'path',
],
'base_config' => [
'routes' => []
]
),
'type' => 'anime',
'host' => "localhost",
'uri' => "/anime/watching",
'check_var' => 'anime_path'
),
'anime_host_routing' => array(
'config' => array(
'config' => [
'anime_host' => 'anime.host.me',
'manga_host' => 'manga.host.me',
'anime_path' => '',
'manga_path' => '',
'route_by' => 'host',
],
'base_config' => []
),
'type' => 'anime',
'host' => 'anime.host.me',
'uri' => '/watching',
'check_var' => 'anime_host'
)
];
}
/**
* @dataProvider RouteTestProvider
*/
public function testRoute($config, $type, $host, $uri, $check_var)
{
$config['base_config']['routes'] = [
'common' => [
'login_form' => [
'path' => '/login',
'action' => ['login'],
'verb' => 'get'
],
],
'anime' => [
'watching' => [
'path' => '/watching{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'currently-watching',
'title' => WHOSE . " Anime List &middot; Watching"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
],
'manga' => [
'plan_to_read' => [
'path' => '/plan_to_read{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Plan to Read',
'title' => WHOSE . " Manga List &middot; Plan to Read"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
]
];
$this->config = new Config($config);
// Set up the environment
$_SERVER = array_merge($_SERVER, [
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => $uri,
'HTTP_HOST' => $host,
'SERVER_NAME' => $host
]);
$router_factory = new RouterFactory();
$this->aura_router = $router_factory->newInstance();
// Create Request/Response Objects
$web_factory = new WebFactory([
'_GET' => [],
'_POST' => [],
'_COOKIE' => [],
'_SERVER' => $_SERVER,
'_FILES' => []
]);
$this->request = $web_factory->newRequest();
$this->response = $web_factory->newResponse();
$this->router = new Router($this->config, $this->aura_router, $this->request, $this->response);
// Check route setup
$this->assertEquals($config['base_config']['routes'], $this->config->routes);
$this->assertTrue(is_array($this->router->get_output_routes()));
// Check environment variables
$this->assertEquals($uri, $this->request->server->get('REQUEST_URI'));
$this->assertEquals($host, $this->request->server->get('HTTP_HOST'));
// Make sure the route is an anime type
$this->assertTrue($this->aura_router->count() > 0, "More than 0 routes");
$this->assertTrue($this->config->$check_var !== '', "Check variable is not empty");
$this->assertEquals($type, $this->router->get_route_type(), "Correct Route type");
// Make sure the route matches, by checking that it is actually an object
$route = $this->router->get_route();
$this->assertInstanceOf('Aura\\Router\\Route', $route, "Route is valid, and matched");
}
}

View File

@ -24,17 +24,25 @@ $defaultHandler = new MockErrorHandler();
*/ */
class AnimeClient_TestCase extends PHPUnit_Framework_TestCase { class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
protected $config;
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
global $config; global $config;
$config = new Config([ $this->config = new Config([
'config' => [], 'config' => [],
'base_config' => [ 'base_config' => [
'databaase' => [] 'databaase' => [],
'routes' => [
'common' => [],
'anime' => [],
'manga' => []
]
] ]
]); ]);
$config =& $this->config;
} }
} }
@ -42,6 +50,9 @@ class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
// Autoloaders // Autoloaders
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Define WHOSE constant
define('WHOSE', "Foo's");
// Define base path constants // Define base path constants
define('ROOT_DIR', realpath(__DIR__ . DIRECTORY_SEPARATOR . "/../")); define('ROOT_DIR', realpath(__DIR__ . DIRECTORY_SEPARATOR . "/../"));
require ROOT_DIR . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'base' . DIRECTORY_SEPARATOR . 'pre_conf_functions.php'; require ROOT_DIR . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'base' . DIRECTORY_SEPARATOR . 'pre_conf_functions.php';