More test coverage

This commit is contained in:
Timothy Warren 2015-10-19 12:50:46 -04:00
parent 285a132d35
commit fee09c50ae
21 changed files with 448 additions and 223 deletions

View File

@ -2,7 +2,10 @@
<phpunit <phpunit
colors="true" colors="true"
stopOnFailure="false" stopOnFailure="false"
bootstrap="tests/bootstrap.php"> bootstrap="tests/bootstrap.php"
beStrictAboutTestsThatDoNotTestAnything="true"
checkForUnintentionallyCoveredCode="true"
>
<filter> <filter>
<whitelist> <whitelist>
<directory suffix=".php">src/Aviat/Ion</directory> <directory suffix=".php">src/Aviat/Ion</directory>

View File

@ -8,6 +8,7 @@ use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar; use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\ResponseInterface; use GuzzleHttp\Psr7\ResponseInterface;
use GuzzleHttp\Exception\ClientException;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\AnimeClient\Model as BaseModel; use Aviat\AnimeClient\Model as BaseModel;
@ -55,10 +56,11 @@ class API extends BaseModel {
$this->client = new Client([ $this->client = new Client([
'base_uri' => $this->base_url, 'base_uri' => $this->base_url,
'cookies' => TRUE, 'cookies' => TRUE,
'http_errors' => FALSE,
'defaults' => [ 'defaults' => [
'cookies' => $this->cookieJar, 'cookies' => $this->cookieJar,
'headers' => [ 'headers' => [
'User-Agent' => $_SERVER['HTTP_USER_AGENT'], 'User-Agent' => "Tim's Anime Client/2.0",
'Accept-Encoding' => 'application/json' 'Accept-Encoding' => 'application/json'
], ],
'timeout' => 5, 'timeout' => 5,
@ -106,16 +108,16 @@ class API extends BaseModel {
*/ */
public function authenticate($username, $password) public function authenticate($username, $password)
{ {
$result = $this->post('https://hummingbird.me/api/v1/users/authenticate', [ $response = $this->post('https://hummingbird.me/api/v1/users/authenticate', [
'body' => [ 'form_params' => [
'username' => $username, 'username' => $username,
'password' => $password 'password' => $password
] ]
]); ]);
if ($result->getStatusCode() === 201) if ($response->getStatusCode() === 201)
{ {
return json_decode($result->getBody(), TRUE); return json_decode($response->getBody(), TRUE);
} }
return FALSE; return FALSE;

View File

@ -0,0 +1,81 @@
<?php
use Aura\Session\SessionFactory;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Aviat\Ion\Friend;
use Aviat\AnimeClient\Auth\HummingbirdAuth;
class HummingbirdAuthTest extends AnimeClient_TestCase {
static $session;
static $sessionHandler;
public static function setUpBeforeClass()
{
self::$session = (new SessionFactory)->newInstance([]);
}
public function setUp()
{
parent::setUp();
$auth = new HummingbirdAuth($this->container);
$friend = new Friend($auth);
$this->auth = $friend;
$this->container->set('session', self::$session);
}
public function dataAuthenticate()
{
$testToken = 'notReallyAValidTokenButThisIsATest';
return [
'successful auth call' => [
'username' => 'timw4mailtest',
'password' => 'password',
'response_data' => [
'code' => 201,
'body' => json_encode($testToken)
],
'session_value' => $testToken,
'expected' => TRUE,
],
'unsuccessful auth call' => [
'username' => 'foo',
'password' => 'foobarbaz',
'response_data' => [
'code' => 401,
'body' => '{"error":"Invalid credentials"}',
],
'session_value' => FALSE,
'expected' => FALSE,
]
];
}
/**
* @dataProvider dataAuthenticate
*/
public function testAuthenticate($username, $password, $response_data, $session_value, $expected)
{
$this->container->get('config')
->set('hummingbird_username', $username);
$model = new MockBaseApiModel($this->container);
$mock = new MockHandler([
new Response($response_data['code'], [], $response_data['body'])
]);
$handler = HandlerStack::create($mock);
$client = new Client([
'handler' => $handler,
'http_errors' => FALSE // Don't throw an exception for 400/500 class status codes
]);
$model->__set('client', $client);
$this->auth->__set('model', $model);
$actual = $this->auth->authenticate($password);
$this->assertEquals($expected, $actual);
}
}

View File

@ -4,29 +4,12 @@ use Aviat\Ion\Friend;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\AnimeClient\Model\Anime as AnimeModel; use Aviat\AnimeClient\Model\Anime as AnimeModel;
class AnimeMock extends AnimeModel {
protected $transformed_data_file;
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
$this->transformed_data_file = __DIR__ . "/../../test_data/anime_list/anime-completed-transformed.json";
}
protected function _get_list_from_api($status="all")
{
$data = json_decode(file_get_contents($this->transformed_data_file), TRUE);
return $data;
}
}
class AnimeModelTest extends AnimeClient_TestCase { class AnimeModelTest extends AnimeClient_TestCase {
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
$this->animeModel = new Friend(new AnimeMock($this->container)); $this->animeModel = new Friend(new TestAnimeModel($this->container));
} }
protected function _pluck_anime_titles($array) protected function _pluck_anime_titles($array)

View File

@ -1,24 +1,14 @@
<?php <?php
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\AnimeClient\Container; use Aviat\AnimeClient\Container;
use Aviat\AnimeClient\Model\API as BaseApiModel; use Aviat\AnimeClient\Model\API as BaseApiModel;
class MockBaseApiModel extends BaseApiModel {
protected $base_url = 'https://httpbin.org/';
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
}
public function __get($key)
{
return $this->$key;
}
}
class BaseApiModelTest extends AnimeClient_TestCase { class BaseApiModelTest extends AnimeClient_TestCase {
public function setUp() public function setUp()
@ -228,5 +218,53 @@ class BaseApiModelTest extends AnimeClient_TestCase {
$this->assertEquals($expected, $actual); $this->assertEquals($expected, $actual);
} }
public function dataAuthenticate()
{
$test_token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4YTA5ZDk4Ny1iZWQxLTQyMTktYWVmOS0wMTcxYWVjYTE3ZWUiLCJzY29wZSI6WyJhbGwiXSwic3ViIjoxMDgwMTIsImlzcyI6MTQ0NTAxNzczNSwiZXhwIjoxNDUyOTY2NTM1fQ.fpha1ZDN9dSFAuHeJesfOP9pCk5-ZnZk4uv3zumRMY0';
return [
'successful authentication' => [
'username' => 'timw4mailtest',
'password' => 'password',
'response_data' => [
'code' => 201,
'body' => json_encode($test_token)
],
'expected' => $test_token
],
'failed authentication' => [
'username' => 'foo',
'password' => 'foobarbaz',
'response_data' => [
'code' => 401,
'body' => '{"error":"Invalid credentials"}',
],
'expected' => FALSE
]
];
}
/**
* @dataProvider dataAuthenticate
*/
public function testAuthenticate($username, $password, $response_data, $expected)
{
$mock = new MockHandler([
new Response($response_data['code'], [], $response_data['body'])
]);
$handler = HandlerStack::create($mock);
$client = new Client([
'handler' => $handler,
'http_errors' => FALSE // Don't throw an exception for 400/500 class status codes
]);
// Set the mock client
$this->model->__set('client', $client);
// Check results based on mock data
$actual = $this->model->authenticate($username, $password);
$this->assertEquals($expected, $actual, "Incorrect method return value");
}
} }

View File

@ -0,0 +1,73 @@
<?php
use Aura\Web\WebFactory;
use Aviat\AnimeClient\Config;
/**
* Base class for TestCases
*/
class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
protected $container;
protected static $staticContainer;
protected static $session_handler;
public static function setUpBeforeClass()
{
// Use mock session handler
$session_handler = new TestSessionHandler();
session_set_save_handler($session_handler, TRUE);
self::$session_handler = $session_handler;
}
public function setUp()
{
parent::setUp();
$config_array = [
'asset_path' => '//localhost/assets/',
'databaase' => [],
'routing' => [
],
'routes' => [
'convention' => [
'default_controller' => '',
'default_method' => '',
],
'common' => [],
'anime' => [],
'manga' => []
]
];
// Set up DI container
$di = require _dir(APP_DIR, 'bootstrap.php');
$container = $di($config_array);
$container->set('error-handler', new MockErrorHandler());
$container->set('session-handler', self::$session_handler);
$this->container = $container;
}
/**
* Set arbitrary superglobal values for testing purposes
*
* @param array $supers
* @return void
*/
public function setSuperGlobals($supers = [])
{
$default = [
'_GET' => $_GET,
'_POST' => $_POST,
'_COOKIE' => $_COOKIE,
'_SERVER' => $_SERVER,
'_FILES' => $_FILES
];
$web_factory = new WebFactory(array_merge($default,$supers));
$this->container->set('request', $web_factory->newRequest());
$this->container->set('response', $web_factory->newResponse());
}
}
// End of AnimeClient_TestCase.php

View File

@ -2,12 +2,6 @@
use Aviat\Ion\Enum; use Aviat\Ion\Enum;
class TestEnum extends Enum {
const FOO = 'bar';
const BAR = 'foo';
const FOOBAR = 'baz';
}
class EnumTest extends AnimeClient_TestCase { class EnumTest extends AnimeClient_TestCase {
protected $expectedConstList = [ protected $expectedConstList = [

View File

@ -2,36 +2,12 @@
use Aviat\Ion\Friend; use Aviat\Ion\Friend;
class GrandParentTestClass {
protected $grandParentProtected = 84;
}
class ParentTestClass extends GrandParentTestClass {
protected $parentProtected = 47;
private $parentPrivate = 654;
}
class TestClass extends ParentTestClass {
protected $protected = 356;
private $private = 486;
protected function getProtected()
{
return 4;
}
private function getPrivate()
{
return 23;
}
}
class FriendTest extends AnimeClient_TestCase { class FriendTest extends AnimeClient_TestCase {
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
$obj = new TestClass(); $obj = new FriendTestClass();
$this->friend = new Friend($obj); $this->friend = new Friend($obj);
} }

View File

@ -1,23 +1,5 @@
<?php <?php
use Aviat\Ion\Transformer\AbstractTransformer;
class TestTransformer extends AbstractTransformer {
public function transform($item)
{
$out = [];
$genre_list = (array) $item;
foreach($genre_list as $genre)
{
$out[] = $genre['name'];
}
return $out;
}
}
class AbstractTransformerTest extends AnimeClient_TestCase { class AbstractTransformerTest extends AnimeClient_TestCase {
protected $transformer; protected $transformer;

View File

@ -2,33 +2,6 @@
include_once __DIR__ . "/../ViewTest.php"; include_once __DIR__ . "/../ViewTest.php";
use Aviat\Ion\Friend;
use Aviat\Ion\View;
use Aviat\Ion\View\HtmlView;
class TestHtmlView extends HtmlView {
protected function output() {
$reflect = new ReflectionClass($this);
$properties = $reflect->getProperties();
$props = [];
foreach($properties as $reflectProp)
{
$reflectProp->setAccessible(TRUE);
$props[$reflectProp->getName()] = $reflectProp->getValue($this);
}
$view = new TestView($this->container);
$friend = new Friend($view);
foreach($props as $name => $val)
{
$friend->__set($name, $val);
}
$friend->output();
}
}
class HtmlViewTest extends ViewTest { class HtmlViewTest extends ViewTest {
protected $template_path; protected $template_path;
@ -36,13 +9,12 @@ class HtmlViewTest extends ViewTest {
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
$this->template_path = __DIR__ . "/../../test_views/";
$this->view = new TestHtmlView($this->container); $this->view = new TestHtmlView($this->container);
} }
public function testRenderTemplate() public function testRenderTemplate()
{ {
$path = $this->template_path . 'test_view.php'; $path = _dir(TEST_VIEW_DIR, 'test_view.php');
$expected = '<tag>foo</tag>'; $expected = '<tag>foo</tag>';
$actual = $this->view->render_template($path, [ $actual = $this->view->render_template($path, [
'var' => 'foo' 'var' => 'foo'

View File

@ -3,30 +3,6 @@
include_once __DIR__ . "/../ViewTest.php"; include_once __DIR__ . "/../ViewTest.php";
use Aviat\Ion\Friend; use Aviat\Ion\Friend;
use Aviat\Ion\View\HttpView;
class TestHttpView extends HttpView {
protected function output() {
$reflect = new ReflectionClass($this);
$properties = $reflect->getProperties();
$props = [];
foreach($properties as $reflectProp)
{
$reflectProp->setAccessible(TRUE);
$props[$reflectProp->getName()] = $reflectProp->getValue($this);
}
$view = new TestView($this->container);
$friend = new Friend($view);
foreach($props as $name => $val)
{
$friend->__set($name, $val);
}
$friend->output();
}
}
class HttpViewTest extends ViewTest { class HttpViewTest extends ViewTest {

View File

@ -1,14 +1,9 @@
<?php <?php
use Aviat\Ion\Friend; use Aviat\Ion\Friend;
use Aviat\Ion\View\JsonView;
include_once __DIR__ . "/../ViewTest.php"; include_once __DIR__ . "/../ViewTest.php";
class TestJsonView extends JsonView {
public function __destruct() {}
}
class JsonViewTest extends ViewTest { class JsonViewTest extends ViewTest {
public function setUp() public function setUp()

View File

@ -2,11 +2,6 @@
use Aura\Web\WebFactory; use Aura\Web\WebFactory;
use Aviat\Ion\Friend; use Aviat\Ion\Friend;
use Aviat\Ion\View;
class TestView extends View {
}
class ViewTest extends AnimeClient_TestCase { class ViewTest extends AnimeClient_TestCase {

View File

@ -0,0 +1,53 @@
<?php
class TestSessionHandler implements SessionHandlerInterface {
public $data = [];
public $save_path = __DIR__ . '/test_data/sessions';
public function close()
{
return TRUE;
}
public function destroy($id)
{
$file = "$this->save_path/$id";
if (file_exists($file))
{
@unlink($file);
}
$this->data[$id] = [];
return TRUE;
}
public function gc($maxLifetime)
{
return TRUE;
}
public function open($save_path, $name)
{
/*if ( ! array_key_exists($save_path, $this->data))
{
$this->save_path = $save_path;
$this->data = [];
}*/
return TRUE;
}
public function read($id)
{
return json_decode(@file_get_contents("$this->save_path/$id"), TRUE);
}
public function write($id, $data)
{
$file = "$this->save_path/$id";
file_put_contents($file, json_encode($data));
return TRUE;
}
}
// End of TestSessionHandler.php

View File

@ -2,12 +2,7 @@
/** /**
* Global setup for unit tests * Global setup for unit tests
*/ */
use Aura\Web\WebFactory;
use Aviat\AnimeClient\Config;
use Aviat\Ion\Di\Container;
use Aviat\AnimeClient\UrlGenerator;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Autoloaders // Autoloaders
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -29,6 +24,8 @@ define('APP_DIR', _dir(ROOT_DIR, 'app'));
define('CONF_DIR', _dir(APP_DIR, 'config')); define('CONF_DIR', _dir(APP_DIR, 'config'));
define('SRC_DIR', _dir(ROOT_DIR, 'src')); define('SRC_DIR', _dir(ROOT_DIR, 'src'));
define('BASE_DIR', _dir(SRC_DIR, 'Base')); define('BASE_DIR', _dir(SRC_DIR, 'Base'));
define('TEST_DATA_DIR', _dir(__DIR__, 'test_data'));
define('TEST_VIEW_DIR', _dir(__DIR__, 'test_views'));
require _dir(ROOT_DIR, '/vendor/autoload.php'); require _dir(ROOT_DIR, '/vendor/autoload.php');
require _dir(SRC_DIR, '/functions.php'); require _dir(SRC_DIR, '/functions.php');
@ -48,76 +45,27 @@ spl_autoload_register(function ($class) {
return; return;
} }
}); });
// -----------------------------------------------------------------------------
// Ini Settings
// -----------------------------------------------------------------------------
ini_set('session.use_cookies', 0);
ini_set("session.use_only_cookies",0);
ini_set("session.use_trans_sid",1);
// Start session here to supress error about headers not sent
session_start();
// -----------------------------------------------------------------------------
// Load base test case and mocks
// -----------------------------------------------------------------------------
// Pre-define some superglobals // Pre-define some superglobals
$_SESSION = []; $_SESSION = [];
$_COOKIE = []; $_COOKIE = [];
// ----------------------------------------------------------------------------- // Request base test case and mocks
// Mock the default error handler require _dir(__DIR__, 'TestSessionHandler.php');
// ----------------------------------------------------------------------------- require _dir(__DIR__, 'mocks.php');
require _dir(__DIR__, 'AnimeClient_TestCase.php');
class MockErrorHandler {
public function addDataTable($name, array $values=[]) {}
}
// -----------------------------------------------------------------------------
// Define a base testcase class
// -----------------------------------------------------------------------------
/**
* Base class for TestCases
*/
class AnimeClient_TestCase extends PHPUnit_Framework_TestCase {
protected $container;
public function setUp()
{
parent::setUp();
$config_array = [
'asset_path' => '//localhost/assets/',
'databaase' => [],
'routing' => [
],
'routes' => [
'convention' => [
'default_controller' => '',
'default_method' => '',
],
'common' => [],
'anime' => [],
'manga' => []
]
];
$di = require _dir(APP_DIR, 'bootstrap.php');
$container = $di($config_array);
$container->set('error-handler', new MockErrorHandler());
$this->container = $container;
}
/**
* Set arbitrary superglobal values for testing purposes
*
* @param array $supers
* @return void
*/
public function setSuperGlobals($supers = [])
{
$default = [
'_GET' => $_GET,
'_POST' => $_POST,
'_COOKIE' => $_COOKIE,
'_SERVER' => $_SERVER,
'_FILES' => $_FILES
];
$web_factory = new WebFactory(array_merge($default,$supers));
$this->container->set('request', $web_factory->newRequest());
$this->container->set('response', $web_factory->newResponse());
}
}
// End of bootstrap.php // End of bootstrap.php

152
tests/mocks.php Normal file
View File

@ -0,0 +1,152 @@
<?php
/**
* All the mock classes that extend the classes they are used to test
*/
use Aviat\Ion\Enum;
use Aviat\Ion\Friend;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Transformer\AbstractTransformer;
use Aviat\Ion\View;
use Aviat\Ion\View\HtmlView;
use Aviat\Ion\View\HttpView;
use Aviat\Ion\View\JsonView;
use Aviat\AnimeClient\Model\Anime as AnimeModel;
use Aviat\AnimeClient\Model\API as BaseApiModel;
// -----------------------------------------------------------------------------
// Mock the default error handler
// -----------------------------------------------------------------------------
class MockErrorHandler {
public function addDataTable($name, array $values=[]) {}
}
// -----------------------------------------------------------------------------
// Ion Mocks
// -----------------------------------------------------------------------------
class TestEnum extends Enum {
const FOO = 'bar';
const BAR = 'foo';
const FOOBAR = 'baz';
}
class FriendGrandParentTestClass {
protected $grandParentProtected = 84;
}
class FriendParentTestClass extends FriendGrandParentTestClass {
protected $parentProtected = 47;
private $parentPrivate = 654;
}
class FriendTestClass extends FriendParentTestClass {
protected $protected = 356;
private $private = 486;
protected function getProtected()
{
return 4;
}
private function getPrivate()
{
return 23;
}
}
class TestTransformer extends AbstractTransformer {
public function transform($item)
{
$out = [];
$genre_list = (array) $item;
foreach($genre_list as $genre)
{
$out[] = $genre['name'];
}
return $out;
}
}
class TestView extends View {}
trait MockViewOutputTrait {
protected function output() {
$reflect = new ReflectionClass($this);
$properties = $reflect->getProperties();
$props = [];
foreach($properties as $reflectProp)
{
$reflectProp->setAccessible(TRUE);
$props[$reflectProp->getName()] = $reflectProp->getValue($this);
}
$view = new TestView($this->container);
$friend = new Friend($view);
foreach($props as $name => $val)
{
$friend->__set($name, $val);
}
$friend->output();
}
}
class TestHtmlView extends HtmlView {
use MockViewOutputTrait;
}
class TestHttpView extends HttpView {
use MockViewOutputTrait;
}
class TestJsonView extends JsonView {
public function __destruct() {}
}
// -----------------------------------------------------------------------------
// AnimeClient Mocks
// -----------------------------------------------------------------------------
class MockBaseApiModel extends BaseApiModel {
protected $base_url = 'https://httpbin.org/';
public function __get($key)
{
return $this->$key;
}
public function __set($key, $value)
{
$this->$key = $value;
return $this;
}
}
class TestAnimeModel extends AnimeModel {
protected $transformed_data_file;
public function __construct(ContainerInterface $container)
{
parent::__construct($container);
$this->transformed_data_file = _dir(
TEST_DATA_DIR, 'anime_list','anime-completed-transformed.json'
);
}
protected function _get_list_from_api($status="all")
{
$data = json_decode(file_get_contents($this->transformed_data_file), TRUE);
return $data;
}
}
// End of mocks.php

View File

View File

@ -0,0 +1 @@
""

View File

@ -0,0 +1 @@
""