More dependency injection, and code coverage

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,12 +22,24 @@ class Router {
*/
protected $config;
/**
* Class wrapper for input superglobals
* @var object
*/
protected $request;
/**
* Array containing request and response objects
* @var array $web
*/
protected $web;
/**
* Routes added to router
* @var array $output_routes
*/
protected $output_routes;
/**
* Constructor
*
@ -37,9 +49,10 @@ class Router {
{
$this->config = $config;
$this->router = $router;
$this->request = $request;
$this->web = [$request, $response];
$this->_setup_routes();
$this->output_routes = $this->_setup_routes();
}
/**
@ -51,7 +64,7 @@ class Router {
{
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 = "/" . trim($route_path, '/');
@ -64,9 +77,20 @@ class Router {
return $route;
}
/**
* Get list of routes applied
*
* @return array
*/
public function get_output_routes()
{
return $this->output_routes;
}
/**
* Handle the current route
*
* @codeCoverageIgnore
* @param [object] $route
* @return void
*/
@ -84,7 +108,7 @@ class Router {
$failure = $this->router->getFailedRoute();
$defaultHandler->addDataTable('failed_route', (array)$failure);
$controller_name = 'BaseController';
$controller_name = '\\AnimeClient\\BaseController';
$action_method = 'outputHTML';
$params = [
'template' => '404',
@ -117,59 +141,101 @@ class Router {
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
*
* @return void
* @return array
*/
private function _setup_routes()
public function _setup_routes()
{
$route_map = [
'anime' => '\\AnimeClient\\AnimeController',
'manga' => '\\AnimeClient\\MangaController',
];
$route_type = "anime";
if ($this->config->manga_host !== "" && strpos($_SERVER['HTTP_HOST'], $this->config->manga_host) !== FALSE)
{
$route_type = "manga";
}
else if ($this->config->manga_path !== "" && strpos($_SERVER['REQUEST_URI'], $this->config->manga_path) !== FALSE)
{
$route_type = "manga";
}
$output_routes = [];
$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
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'];
unset($route['path']);
$output_routes[] = $this->router->$add($name, $path)->addValues($route);
}
else
{
$tokens = $route['tokens'];
unset($route['tokens']);
// 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";
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);
}
$output_routes[] = $this->router->$add($name, $path)
->addValues($route)
->addTokens($tokens);
}
}
return $output_routes;
}
}
// End of Router.php

View File

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

View File

@ -22,10 +22,11 @@ $config = [
// Routing
//
// 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
// to an empty string
// options to an empty string, and set 'route_by' to 'path'. To route by host, set
// 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',
'manga_host' => 'manga.timshomepage.net',
'anime_path' => '',

View File

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

View File

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

View File

@ -25,12 +25,12 @@ class AnimeCollectionModel extends BaseDBModel {
/**
* Constructor
*/
public function __construct()
public function __construct(Config $config)
{
parent::__construct();
parent::__construct($config);
$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
// app can be run without a valid database

View File

@ -18,9 +18,9 @@ class AnimeModel extends BaseApiModel {
/**
* 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/";
/**
* Constructor
*/
public function __construct(Config $config)
{
parent::__construct($config);
}
/**
* Update the selected manga

View File

@ -11,7 +11,11 @@
</head>
<body class="<?= $url_type ?> list">
<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">
<?php if (is_logged_in()): ?>
[<a href="<?= full_url("/logout", $url_type) ?>">Logout</a>]
@ -20,6 +24,7 @@
<?php endif ?>
</span>
</h1>
<?php if ( ! empty($nav_routes)): ?>
<nav>
<ul>
<?php foreach($nav_routes as $title => $nav_path): ?>
@ -32,4 +37,5 @@
<li class="<?= is_selected('list', last_segment()) ?>"><a href="<?= full_url("{$route_path}/list", $url_type) ?>">List View</a></li>
</ul>
</nav>
<br />
<br />
<?php endif ?>

View File

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

View File

@ -18,5 +18,8 @@
<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_HOST" value="localhost" />
<server name="SERVER_NAME" value="localhost" />
<server name="REQUEST_URI" value="/" />
<server name="REQUEST_METHOD" value="GET" />
</php>
</phpunit>

View File

@ -4,9 +4,9 @@ use \AnimeClient\BaseApiModel;
class MockBaseApiModel extends BaseApiModel {
public function __construct()
public function __construct(\AnimeClient\Config $config)
{
parent::__construct();
parent::__construct($config);
}
public function __get($key)
@ -19,7 +19,7 @@ class BaseApiModelTest extends AnimeClient_TestCase {
public function testBaseApiModelSanity()
{
$baseApiModel = new MockBaseApiModel();
$baseApiModel = new MockBaseApiModel($this->config);
// Some basic type checks for class memebers
$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()
{
$baseDBModel = new BaseDBModel();
$baseDBModel = new BaseDBModel($this->config);
$this->assertTrue(is_object($baseDBModel));
}
}

View File

@ -6,7 +6,7 @@ class BaseModelTest extends AnimeClient_TestCase {
public function testBaseModelSanity()
{
$baseModel = new BaseModel();
$baseModel = new BaseModel($this->config);
$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 {
protected $config;
public function setUp()
{
parent::setUp();
global $config;
$config = new Config([
$this->config = new Config([
'config' => [],
'base_config' => [
'databaase' => []
'databaase' => [],
'routes' => [
'common' => [],
'anime' => [],
'manga' => []
]
]
]);
$config =& $this->config;
}
}
@ -42,6 +50,9 @@ class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
// Autoloaders
// -----------------------------------------------------------------------------
// Define WHOSE constant
define('WHOSE', "Foo's");
// Define base path constants
define('ROOT_DIR', realpath(__DIR__ . DIRECTORY_SEPARATOR . "/../"));
require ROOT_DIR . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'base' . DIRECTORY_SEPARATOR . 'pre_conf_functions.php';