Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nekudo committed Nov 25, 2024
1 parent 4f9b7b4 commit 3980a63
Show file tree
Hide file tree
Showing 16 changed files with 467 additions and 23 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
# Project
/vendor/
/config/config.php

/config/config.testing.php
/phpunit.xml
/.phpunit.result.cache
4 changes: 4 additions & 0 deletions db_structure.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
-- Drop Logs Table
DROP TABLE IF EXISTS `logs`;

-- Create Logs Table
CREATE TABLE `logs` (
`log_id` bigint(20) NOT NULL AUTO_INCREMENT,
`source` varchar(100) NOT NULL,
Expand Down
20 changes: 20 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd"
bootstrap="./tests/bootstrap.php"
colors="true"
>

<testsuites>
<testsuite name="Pile Feature Tests">
<directory>./tests/Feature</directory>
</testsuite>
</testsuites>

<source>
<include>
<directory>./src/</directory>
</include>
</source>

</phpunit>
4 changes: 3 additions & 1 deletion public/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
$config = include $configFile;

$pile = new \Bloatless\Pile\Pile($config);
$pile->__invoke($_REQUEST, $_SERVER);
$response = $pile->__invoke($_REQUEST, $_SERVER);

echo $response;
41 changes: 20 additions & 21 deletions src/Pile.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function __construct(array $config)
spl_autoload_register(array($this, 'registerAutoload'));
}

public function __invoke($request, $server): void
public function __invoke($request, $server): string
{
// @todo Remove composer (?)
// @todo adjust config.sample
Expand All @@ -72,9 +72,10 @@ public function __invoke($request, $server): void
$requestMethod = $server['REQUEST_METHOD'];
$requestUri = $server['REQUEST_URI'] ?? '';
$action = $this->route($requestUri, $requestMethod);
$this->dispatch($action, $request, $server);

return $this->dispatch($action, $request, $server);
} catch (Exception | Throwable $e) {
$this->sendErrorResponse($e);
return $this->sendErrorResponse($e);
}
}

Expand Down Expand Up @@ -129,7 +130,7 @@ protected function initConfiguration(): void
$this->authConfig = $authConfig;

// validate views path
$pathViews = $this->config['path_views'] ?? null;
$pathViews = $this->config['path_views'] ?? '';
$pathViews = $pathViews !== '/' ? rtrim($pathViews, '/') : '/';
if (empty($pathViews)) {
throw new PileException('Error: Path to views missing in config. Check config file.');
Expand Down Expand Up @@ -178,25 +179,23 @@ protected function route(string $requestUri, string $requestMethod): string
* @throws HttpUnauthorizedException
* @throws HttpBadRequestException
*/
protected function dispatch(string $action, array $request, array $server): void
protected function dispatch(string $action, array $request, array $server): string
{
switch ($action) {
case 'showLogs':
$this->contentType = self::CONTENT_TYPE_HTML;
if ($this->webRequestIsAuthorized($server) === false) {
$this->sendRequestAuthorizationResponse();
return $this->sendRequestAuthorizationResponse();
}

$this->handleShowLogsRequest($request, $server);
break;
return $this->handleShowLogsRequest($request, $server);
case 'storeLog':
$this->contentType = self::CONTENT_TYPE_JSON;
if ($this->apiRequestIsAuthorized($server) === false) {
throw new HttpUnauthorizedException();
}

$this->handleStoreLogRequest();
break;
return $this->handleStoreLogRequest();
default:
throw new PileException('Error: Unknown Action.');
}
Expand All @@ -210,7 +209,7 @@ protected function dispatch(string $action, array $request, array $server): void
* @throws PileException
* @throws DatabaseException
*/
protected function handleShowLogsRequest(array $request, array $server): void
protected function handleShowLogsRequest(array $request, array $server): string
{
// collect data from request
$urlPath = (string) parse_url($server['REQUEST_URI'], PHP_URL_PATH);
Expand Down Expand Up @@ -244,15 +243,15 @@ protected function handleShowLogsRequest(array $request, array $server): void
]);

// display response
$this->sendResponse($html);
return $this->sendResponse($html);
}

/**
* @throws HttpBadRequestException
* @throws DatabaseException
* @throws PileException
*/
protected function handleStoreLogRequest(): void
protected function handleStoreLogRequest(): string
{
$rawData = $this->getRequestBody();
if (empty($rawData)) {
Expand All @@ -269,7 +268,7 @@ protected function handleStoreLogRequest(): void
$attributes = $this->preprocessLogData($logData);
$attributes['log_id'] = $this->storeLogData($attributes);

$this->sendResponse(json_encode($attributes));
return $this->sendResponse(json_encode($attributes));
}

// -----------------------
Expand Down Expand Up @@ -305,7 +304,7 @@ protected function getRequestBody(): string
// Response Logic
// -----------------------

protected function sendResponse(string $content = '', int $code = 200): void
protected function sendResponse(string $content = '', int $code = 200): string
{
switch ($this->contentType) {
case self::CONTENT_TYPE_JSON:
Expand All @@ -316,24 +315,24 @@ protected function sendResponse(string $content = '', int $code = 200): void
break;
}

echo $content;
exit;
return $content;
}

protected function sendErrorResponse(Throwable|Exception $e): void
protected function sendErrorResponse(Throwable|Exception $e): string
{
$errorMessage = $e->getMessage();
if ($this->contentType === self::CONTENT_TYPE_JSON) {
$errorMessage = json_encode($errorMessage);
}

$this->sendResponse($errorMessage, $e->getCode());
return $this->sendResponse($errorMessage, $e->getCode());
}

protected function sendRequestAuthorizationResponse(): void
protected function sendRequestAuthorizationResponse(): string
{
header('WWW-Authenticate: Basic realm="Restricted access"', true, 401);
exit;

return 'Authorization required. Please log in.';
}

// -----------------------
Expand Down
22 changes: 22 additions & 0 deletions tests/Doubles/PileDouble.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Bloatless\Pile\Tests\Doubles;

use Bloatless\Pile\Pile;

class PileDouble extends Pile
{
protected string $requestBody = '';

public function setRequestBody(string $requestBody): void
{
$this->requestBody = $requestBody;
}

protected function getRequestBody(): string
{
return $this->requestBody;
}
}
18 changes: 18 additions & 0 deletions tests/Feature/ApplicationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Bloatless\Pile\Tests\Feature;

use Bloatless\Pile\Pile;
use PHPUnit\Framework\TestCase;

class ApplicationTest extends TestCase
{
public function testAppCanBeInitialized()
{
$config = include PATH_ROOT . '/config/config.testing.php';
$app = new Pile($config);
$this->assertInstanceOf(Pile::class, $app);
}
}
81 changes: 81 additions & 0 deletions tests/Feature/AuthorizationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace Bloatless\Pile\Tests\Feature;

use Bloatless\Pile\Tests\Doubles\PileDouble;
use PHPUnit\Framework\TestCase;

class AuthorizationTest extends TestCase
{
public function testHomepageRequiresAuthorization()
{
$config = include PATH_ROOT . '/config/config.testing.php';
$app = new PileDouble($config);

$response = $app->__invoke([], [
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => '/',
]);

$this->assertStringContainsString('Authorization required.', $response);
}

public function testLoginIsPossibleWithValidCredentials()
{
$config = include PATH_ROOT . '/config/config.testing.php';
$app = new PileDouble($config);

$response = $app->__invoke([], [
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => '/',
'HTTP_AUTHORIZATION' => 'Basic ' . base64_encode('foo:bar'),
]);

$this->assertStringContainsString('<div class="container">', $response);
}

public function testLoginIsNotPossibleWithInvalidCredentials()
{
$config = include PATH_ROOT . '/config/config.testing.php';
$app = new PileDouble($config);

$response = $app->__invoke([], [
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => '/',
'HTTP_AUTHORIZATION' => 'Basic ' . base64_encode('foo:baz'),
]);

$this->assertStringContainsString('Authorization required.', $response);
}

public function testApiRequestIsPossibleWithValidKey()
{
$config = include PATH_ROOT . '/config/config.testing.php';
$app = new PileDouble($config);
$app->setRequestBody('[]');

$response = $app->__invoke([], [
'REQUEST_METHOD' => 'POST',
'REQUEST_URI' => '/api/v1/log',
'HTTP_X_API_KEY' => '123123123',
]);

$this->assertStringContainsString('Error: Invalid data', $response);
}

public function testApiRequestIsNotPossibleWithInvalidKey()
{
$config = include PATH_ROOT . '/config/config.testing.php';
$app = new PileDouble($config);

$response = $app->__invoke([], [
'REQUEST_METHOD' => 'POST',
'REQUEST_URI' => '/api/v1/log',
'HTTP_X_API_KEY' => 'non-existent-key',
]);

$this->assertStringContainsString('Error 401: Unauthorized', $response);
}
}
56 changes: 56 additions & 0 deletions tests/Feature/ConfigurationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace Bloatless\Pile\Tests\Feature;

use Bloatless\Pile\Pile;
use PHPUnit\Framework\TestCase;

class ConfigurationTest extends TestCase
{
public function testDbConfigurationIsValidated()
{
$config = include PATH_ROOT . '/config/config.testing.php';

unset($config['db']['dsn']);
$app = new Pile($config);
$response = $app->__invoke([], []);
$this->assertStringContainsString('Invalid database configuration', $response);

unset($config['db']);
$app = new Pile($config);
$response = $app->__invoke([], []);
$this->assertStringContainsString('Database configuration missing', $response);
}

public function testAuthConfigurationIsValidated()
{
$config = include PATH_ROOT . '/config/config.testing.php';

unset($config['auth']['users']);
$app = new Pile($config);
$response = $app->__invoke([], []);
$this->assertStringContainsString('Auth configuration is invalid', $response);

unset($config['auth']);
$app = new Pile($config);
$response = $app->__invoke([], []);
$this->assertStringContainsString('Auth configuration missing', $response);
}

public function testViewConfigurationIsValidated()
{
$config = include PATH_ROOT . '/config/config.testing.php';

unset($config['path_views']);
$app = new Pile($config);
$response = $app->__invoke([], []);
$this->assertStringContainsString('Path to views missing in config', $response);

$config['path_views'] = 'non_existent_path';
$app = new Pile($config);
$response = $app->__invoke([], []);
$this->assertStringContainsString('Paths to views is invalid', $response);
}
}
Loading

0 comments on commit 3980a63

Please sign in to comment.