summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Scheller <igor.scheller@igorshp.de>2019-07-09 21:43:18 +0200
committerIgor Scheller <igor.scheller@igorshp.de>2019-07-09 21:43:18 +0200
commit7414f9b23dbcc66e5f0efda3d0cbfd79372ec780 (patch)
tree891a11b71d8b25922fc4b343dbc77a01a0646ba1
parent508695efb253d7bc0caea1fa017ed5608d774596 (diff)
Implemented Validation for controllers
-rw-r--r--config/app.php1
-rw-r--r--src/Controllers/BaseController.php4
-rw-r--r--src/Http/Exceptions/ValidationException.php37
-rw-r--r--src/Http/Validation/Validates.php154
-rw-r--r--src/Http/Validation/ValidatesRequest.php37
-rw-r--r--src/Http/Validation/ValidationServiceProvider.php28
-rw-r--r--src/Http/Validation/Validator.php76
-rw-r--r--src/Middleware/ErrorHandler.php30
-rw-r--r--tests/Unit/Controllers/BaseControllerTest.php2
-rw-r--r--tests/Unit/Http/Exceptions/ValidationExceptionTest.php25
-rw-r--r--tests/Unit/Http/Validation/Stub/ValidatesRequestImplementation.php27
-rw-r--r--tests/Unit/Http/Validation/ValidatesRequestTest.php46
-rw-r--r--tests/Unit/Http/Validation/ValidatesTest.php308
-rw-r--r--tests/Unit/Http/Validation/ValidationServiceProviderTest.php34
-rw-r--r--tests/Unit/Http/Validation/ValidatorTest.php50
-rw-r--r--tests/Unit/Middleware/ErrorHandlerTest.php70
16 files changed, 927 insertions, 2 deletions
diff --git a/config/app.php b/config/app.php
index 5fda67dd..c4503086 100644
--- a/config/app.php
+++ b/config/app.php
@@ -25,6 +25,7 @@ return [
\Engelsystem\Middleware\RouteDispatcherServiceProvider::class,
\Engelsystem\Middleware\RequestHandlerServiceProvider::class,
\Engelsystem\Middleware\SessionHandlerServiceProvider::class,
+ \Engelsystem\Http\Validation\ValidationServiceProvider::class,
// Additional services
\Engelsystem\Mail\MailerServiceProvider::class,
diff --git a/src/Controllers/BaseController.php b/src/Controllers/BaseController.php
index cbc00931..655ed759 100644
--- a/src/Controllers/BaseController.php
+++ b/src/Controllers/BaseController.php
@@ -2,8 +2,12 @@
namespace Engelsystem\Controllers;
+use Engelsystem\Http\Validation\ValidatesRequest;
+
abstract class BaseController
{
+ use ValidatesRequest;
+
/** @var string[]|string[][] A list of Permissions required to access the controller or certain pages */
protected $permissions = [];
diff --git a/src/Http/Exceptions/ValidationException.php b/src/Http/Exceptions/ValidationException.php
new file mode 100644
index 00000000..e48fb0c3
--- /dev/null
+++ b/src/Http/Exceptions/ValidationException.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Engelsystem\Http\Exceptions;
+
+use Engelsystem\Http\Validation\Validator;
+use RuntimeException;
+use Throwable;
+
+class ValidationException extends RuntimeException
+{
+ /** @var Validator */
+ protected $validator;
+
+ /**
+ * @param Validator $validator
+ * @param string $message
+ * @param int $code
+ * @param Throwable|null $previous
+ */
+ public function __construct(
+ Validator $validator,
+ string $message = '',
+ int $code = 0,
+ Throwable $previous = null
+ ) {
+ $this->validator = $validator;
+ parent::__construct($message, $code, $previous);
+ }
+
+ /**
+ * @return Validator
+ */
+ public function getValidator(): Validator
+ {
+ return $this->validator;
+ }
+}
diff --git a/src/Http/Validation/Validates.php b/src/Http/Validation/Validates.php
new file mode 100644
index 00000000..2e3a1a73
--- /dev/null
+++ b/src/Http/Validation/Validates.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Engelsystem\Http\Validation;
+
+use InvalidArgumentException;
+
+class Validates
+{
+ /**
+ * @param mixed $value
+ * @return bool
+ */
+ public function accepted($value): bool
+ {
+ return in_array($value, ['true', '1', 'y', 'yes', 'on', 1, true], true);
+ }
+
+ /**
+ * @param string $value
+ * @param array $parameters ['min', 'max']
+ * @return bool
+ */
+ public function between($value, $parameters): bool
+ {
+ $this->validateParameterCount(2, $parameters, __FUNCTION__);
+ $size = $this->getSize($value);
+
+ return $size >= $parameters[0] && $size <= $parameters[1];
+ }
+
+ /**
+ * @param mixed $value
+ * @return bool
+ */
+ public function bool($value): bool
+ {
+ return in_array($value, ['1', 1, true, '0', 0, false], true);
+ }
+
+ /**
+ * @param mixed $value
+ * @param array $parameters ['1,2,3,56,7']
+ * @return bool
+ */
+ public function in($value, $parameters): bool
+ {
+ $this->validateParameterCount(1, $parameters, __FUNCTION__);
+
+ return in_array($value, explode(',', $parameters[0]));
+ }
+
+ /**
+ * @param mixed $value
+ * @return bool
+ */
+ public function int($value): bool
+ {
+ return filter_var($value, FILTER_VALIDATE_INT) !== false;
+ }
+
+ /**
+ * @param string $value
+ * @param array $parameters ['max']
+ * @return bool
+ */
+ public function max($value, $parameters): bool
+ {
+ $this->validateParameterCount(1, $parameters, __FUNCTION__);
+ $size = $this->getSize($value);
+
+ return $size <= $parameters[0];
+ }
+
+ /**
+ * @param string $value
+ * @param array $parameters ['min']
+ * @return bool
+ */
+ public function min($value, $parameters)
+ {
+ $this->validateParameterCount(1, $parameters, __FUNCTION__);
+ $size = $this->getSize($value);
+
+ return $size >= $parameters[0];
+ }
+
+ /**
+ * @param mixed $value
+ * @param array $parameters ['1,2,3,56,7']
+ * @return bool
+ */
+ public function notIn($value, $parameters): bool
+ {
+ $this->validateParameterCount(1, $parameters, __FUNCTION__);
+
+ return !$this->in($value, $parameters);
+ }
+
+ /**
+ * @param mixed $value
+ * @return bool
+ */
+ public function numeric($value): bool
+ {
+ return is_numeric($value);
+ }
+
+ /**
+ * @param mixed $value
+ * @return bool
+ */
+ public function required($value): bool
+ {
+ if (
+ is_null($value)
+ || (is_string($value) && trim($value) === '')
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param mixed $value
+ * @return int|float
+ */
+ protected function getSize($value)
+ {
+ if (is_numeric($value)) {
+ return $value;
+ }
+
+ return mb_strlen($value);
+ }
+
+ /**
+ * @param int $count
+ * @param array $parameters
+ * @param string $rule
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function validateParameterCount(int $count, array $parameters, string $rule)
+ {
+ if (count($parameters) < $count) {
+ throw new InvalidArgumentException(sprintf(
+ 'The rule "%s" requires at least %d parameters',
+ $rule,
+ $count
+ ));
+ }
+ }
+}
diff --git a/src/Http/Validation/ValidatesRequest.php b/src/Http/Validation/ValidatesRequest.php
new file mode 100644
index 00000000..33ff76af
--- /dev/null
+++ b/src/Http/Validation/ValidatesRequest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Engelsystem\Http\Validation;
+
+use Engelsystem\Http\Exceptions\ValidationException;
+use Engelsystem\Http\Request;
+
+trait ValidatesRequest
+{
+ /** @var Validator */
+ protected $validator;
+
+ /**
+ * @param Request $request
+ * @param array $rules
+ * @return array
+ */
+ protected function validate(Request $request, array $rules)
+ {
+ if (!$this->validator->validate(
+ (array)$request->getParsedBody(),
+ $rules
+ )) {
+ throw new ValidationException($this->validator);
+ }
+
+ return $this->validator->getData();
+ }
+
+ /**
+ * @param Validator $validator
+ */
+ public function setValidator(Validator $validator)
+ {
+ $this->validator = $validator;
+ }
+}
diff --git a/src/Http/Validation/ValidationServiceProvider.php b/src/Http/Validation/ValidationServiceProvider.php
new file mode 100644
index 00000000..2f1c6359
--- /dev/null
+++ b/src/Http/Validation/ValidationServiceProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Engelsystem\Http\Validation;
+
+use Engelsystem\Application;
+use Engelsystem\Container\ServiceProvider;
+use Engelsystem\Controllers\BaseController;
+
+class ValidationServiceProvider extends ServiceProvider
+{
+ public function register()
+ {
+ $validates = $this->app->make(Validates::class);
+ $this->app->instance(Validates::class, $validates);
+
+ $validator = $this->app->make(Validator::class);
+ $this->app->instance(Validator::class, $validator);
+ $this->app->instance('validator', $validator);
+
+ $this->app->afterResolving(function ($object, Application $app) {
+ if (!$object instanceof BaseController) {
+ return;
+ }
+
+ $object->setValidator($app->get(Validator::class));
+ });
+ }
+}
diff --git a/src/Http/Validation/Validator.php b/src/Http/Validation/Validator.php
new file mode 100644
index 00000000..a9235a5f
--- /dev/null
+++ b/src/Http/Validation/Validator.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Engelsystem\Http\Validation;
+
+use Illuminate\Support\Str;
+use InvalidArgumentException;
+
+class Validator
+{
+ /** @var Validates */
+ protected $validate;
+
+ /** @var string[] */
+ protected $errors = [];
+
+ /** @var array */
+ protected $data = [];
+
+ /**
+ * @param Validates $validate
+ */
+ public function __construct(Validates $validate)
+ {
+ $this->validate = $validate;
+ }
+
+ /**
+ * @param array $data
+ * @param array $rules
+ * @return bool
+ */
+ public function validate($data, $rules)
+ {
+ $this->errors = [];
+ $this->data = [];
+
+ foreach ($rules as $key => $values) {
+ foreach (explode('|', $values) as $parameters) {
+ $parameters = explode(':', $parameters);
+ $rule = array_shift($parameters);
+ $rule = Str::camel($rule);
+
+ if (!method_exists($this->validate, $rule)) {
+ throw new InvalidArgumentException('Unknown validation rule: ' . $rule);
+ }
+
+ $value = isset($data[$key]) ? $data[$key] : null;
+ if (!$this->validate->{$rule}($value, $parameters, $data)) {
+ $this->errors[$key][] = implode('.', ['validation', $key, $rule]);
+
+ continue;
+ }
+
+ $this->data[$key] = $value;
+ }
+ }
+
+ return empty($this->errors);
+ }
+
+ /**
+ * @return array
+ */
+ public function getData(): array
+ {
+ return $this->data;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getErrors(): array
+ {
+ return $this->errors;
+ }
+}
diff --git a/src/Middleware/ErrorHandler.php b/src/Middleware/ErrorHandler.php
index 29b1fac1..c89edb1a 100644
--- a/src/Middleware/ErrorHandler.php
+++ b/src/Middleware/ErrorHandler.php
@@ -3,6 +3,8 @@
namespace Engelsystem\Middleware;
use Engelsystem\Http\Exceptions\HttpException;
+use Engelsystem\Http\Exceptions\ValidationException;
+use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
@@ -43,6 +45,21 @@ class ErrorHandler implements MiddlewareInterface
$response = $handler->handle($request);
} catch (HttpException $e) {
$response = $this->createResponse($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
+ } catch (ValidationException $e) {
+ $response = $this->createResponse('', 302, ['Location' => $this->getPreviousUrl($request)]);
+
+ if ($request instanceof Request) {
+ $session = $request->getSession();
+ $session->set(
+ 'errors',
+ array_merge_recursive(
+ $session->get('errors', []),
+ ['validation' => $e->getValidator()->getErrors()]
+ )
+ );
+
+ $session->set('form-data', $request->request->all());
+ }
}
$statusCode = $response->getStatusCode();
@@ -106,4 +123,17 @@ class ErrorHandler implements MiddlewareInterface
{
return response($content, $status, $headers);
}
+
+ /**
+ * @param ServerRequestInterface $request
+ * @return string
+ */
+ protected function getPreviousUrl(ServerRequestInterface $request)
+ {
+ if ($header = $request->getHeader('referer')) {
+ return array_pop($header);
+ }
+
+ return '/';
+ }
}
diff --git a/tests/Unit/Controllers/BaseControllerTest.php b/tests/Unit/Controllers/BaseControllerTest.php
index 738b538f..2adc9dc7 100644
--- a/tests/Unit/Controllers/BaseControllerTest.php
+++ b/tests/Unit/Controllers/BaseControllerTest.php
@@ -21,5 +21,7 @@ class BaseControllerTest extends TestCase
'dolor',
],
], $controller->getPermissions());
+
+ $this->assertTrue(method_exists($controller, 'setValidator'));
}
}
diff --git a/tests/Unit/Http/Exceptions/ValidationExceptionTest.php b/tests/Unit/Http/Exceptions/ValidationExceptionTest.php
new file mode 100644
index 00000000..c5a38b5a
--- /dev/null
+++ b/tests/Unit/Http/Exceptions/ValidationExceptionTest.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Http\Exceptions;
+
+use Engelsystem\Http\Exceptions\ValidationException;
+use Engelsystem\Http\Validation\Validator;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+
+class ValidationExceptionTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Http\Exceptions\ValidationException::__construct
+ * @covers \Engelsystem\Http\Exceptions\ValidationException::getValidator
+ */
+ public function testConstruct()
+ {
+ /** @var Validator|MockObject $validator */
+ $validator = $this->createMock(Validator::class);
+
+ $exception = new ValidationException($validator);
+
+ $this->assertEquals($validator, $exception->getValidator());
+ }
+}
diff --git a/tests/Unit/Http/Validation/Stub/ValidatesRequestImplementation.php b/tests/Unit/Http/Validation/Stub/ValidatesRequestImplementation.php
new file mode 100644
index 00000000..772b1dc9
--- /dev/null
+++ b/tests/Unit/Http/Validation/Stub/ValidatesRequestImplementation.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Http\Validation\Stub;
+
+use Engelsystem\Controllers\BaseController;
+use Engelsystem\Http\Request;
+
+class ValidatesRequestImplementation extends BaseController
+{
+ /**
+ * @param Request $request
+ * @param array $rules
+ * @return array
+ */
+ public function validateData(Request $request, array $rules)
+ {
+ return $this->validate($request, $rules);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasValidator()
+ {
+ return !is_null($this->validator);
+ }
+}
diff --git a/tests/Unit/Http/Validation/ValidatesRequestTest.php b/tests/Unit/Http/Validation/ValidatesRequestTest.php
new file mode 100644
index 00000000..8011bd03
--- /dev/null
+++ b/tests/Unit/Http/Validation/ValidatesRequestTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Http\Validation;
+
+use Engelsystem\Http\Exceptions\ValidationException;
+use Engelsystem\Http\Request;
+use Engelsystem\Http\Validation\Validator;
+use Engelsystem\Test\Unit\Http\Validation\Stub\ValidatesRequestImplementation;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+
+class ValidatesRequestTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Http\Validation\ValidatesRequest::validate
+ * @covers \Engelsystem\Http\Validation\ValidatesRequest::setValidator
+ */
+ public function testValidate()
+ {
+ /** @var Validator|MockObject $validator */
+ $validator = $this->createMock(Validator::class);
+ $validator->expects($this->exactly(2))
+ ->method('validate')
+ ->withConsecutive(
+ [['foo' => 'bar'], ['foo' => 'required']],
+ [[], ['foo' => 'required']]
+ )
+ ->willReturnOnConsecutiveCalls(
+ true,
+ false
+ );
+ $validator->expects($this->once())
+ ->method('getData')
+ ->willReturn(['foo' => 'bar']);
+
+ $implementation = new ValidatesRequestImplementation();
+ $implementation->setValidator($validator);
+
+ $return = $implementation->validateData(new Request([], ['foo' => 'bar']), ['foo' => 'required']);
+
+ $this->assertEquals(['foo' => 'bar'], $return);
+
+ $this->expectException(ValidationException::class);
+ $implementation->validateData(new Request([], []), ['foo' => 'required']);
+ }
+}
diff --git a/tests/Unit/Http/Validation/ValidatesTest.php b/tests/Unit/Http/Validation/ValidatesTest.php
new file mode 100644
index 00000000..5cf0447a
--- /dev/null
+++ b/tests/Unit/Http/Validation/ValidatesTest.php
@@ -0,0 +1,308 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Http\Validation;
+
+use Engelsystem\Http\Validation\Validates;
+use InvalidArgumentException;
+use PHPUnit\Framework\TestCase;
+
+class ValidatesTest extends TestCase
+{
+ /**
+ * @return array
+ */
+ public function provideAccepted()
+ {
+ return [
+ ['true'],
+ ['1'],
+ ['y'],
+ ['yes'],
+ ['on'],
+ ['1test', false],
+ ['false', false],
+ ['no', false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::accepted
+ * @param mixed $value
+ * @param bool $result
+ * @dataProvider provideAccepted
+ */
+ public function testAccepted($value, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->accepted($value) === $result);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideBetween()
+ {
+ return [
+ ['42', [10, 100]],
+ [42.5, [42, 43]],
+ [42, [42, 1000]],
+ [1337, [0, 99], false],
+ [-17, [32, 45], false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::between
+ * @param mixed $value
+ * @param array $parameters
+ * @param bool $result
+ * @dataProvider provideBetween
+ */
+ public function testBetween($value, array $parameters, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->between($value, $parameters) === $result);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideBool()
+ {
+ return [
+ ['1'],
+ [1],
+ [true],
+ ['0'],
+ [0],
+ [false],
+ ['true', false],
+ ['false', false],
+ ['yes', false],
+ ['no', false],
+ ['bool', false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::bool
+ * @param mixed $value
+ * @param bool $result
+ * @dataProvider provideBool
+ */
+ public function testBool($value, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->bool($value) === $result);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideIn()
+ {
+ return [
+ ['lorem', ['lorem,ipsum,dolor']],
+ [99, ['66,77,88,99,111']],
+ [4, ['1,3,5,7'], false],
+ ['toggle', ['on,off'], false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::in
+ * @param mixed $value
+ * @param array $parameters
+ * @param bool $result
+ * @dataProvider provideIn
+ */
+ public function testIn($value, array $parameters, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->in($value, $parameters) === $result);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideInt()
+ {
+ return [
+ ['1337'],
+ [42],
+ ['0'],
+ [false, false],
+ ['12asd1', false],
+ ['one', false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::int
+ * @param mixed $value
+ * @param bool $result
+ * @dataProvider provideInt
+ */
+ public function testInt($value, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->int($value) === $result);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideMax()
+ {
+ return [
+ ['99', [100]],
+ [-42, [1024]],
+ [99, [99]],
+ [100, [10], false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::max
+ * @param mixed $value
+ * @param array $parameters
+ * @param bool $result
+ * @dataProvider provideMax
+ */
+ public function testMax($value, array $parameters, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->max($value, $parameters) === $result);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideMin()
+ {
+ return [
+ [32, [0]],
+ [7, [7]],
+ ['99', [10]],
+ [3, [42], false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::min
+ * @param mixed $value
+ * @param array $parameters
+ * @param bool $result
+ * @dataProvider provideMin
+ */
+ public function testMin($value, array $parameters, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->min($value, $parameters) === $result);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideNotIn()
+ {
+ return [
+ [77, ['50,60,70']],
+ ['test', ['coding,deployment']],
+ ['PHP', ['Java,PHP,bash'], false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::notIn
+ * @param mixed $value
+ * @param array $parameters
+ * @param bool $result
+ * @dataProvider provideNotIn
+ */
+ public function testNotIn($value, array $parameters, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->notIn($value, $parameters) === $result);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideNumeric()
+ {
+ return [
+ [77],
+ ['42'],
+ ['1337e0'],
+ ['123f00', false],
+ [null, false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::numeric
+ * @param mixed $value
+ * @param bool $result
+ * @dataProvider provideNumeric
+ */
+ public function testNumeric($value, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->numeric($value) === $result);
+ }
+
+ /**
+ * @return array
+ */
+ public function provideRequired()
+ {
+ return [
+ ['Lorem ipsum'],
+ ['1234'],
+ [1234],
+ ['0'],
+ [0],
+ ['', false],
+ [' ', false],
+ [null, false],
+ ];
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::required
+ * @param mixed $value
+ * @param bool $result
+ * @dataProvider provideRequired
+ */
+ public function testRequired($value, bool $result = true)
+ {
+ $val = new Validates;
+ $this->assertTrue($val->required($value) === $result);
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::getSize
+ */
+ public function testGetSize()
+ {
+ $val = new Validates;
+ $this->assertTrue($val->max(42, [999]));
+ $this->assertTrue($val->max('99', [100]));
+ $this->assertFalse($val->max('101', [100]));
+ $this->assertTrue($val->max('lorem', [5]));
+ $this->assertFalse($val->max('Lorem Ipsum', [5]));
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validates::validateParameterCount
+ */
+ public function testValidateParameterCount()
+ {
+ $val = new Validates;
+ $this->assertTrue($val->between(42, [1, 100]));
+
+ $this->expectException(InvalidArgumentException::class);
+ $val->between(42, [1]);
+ }
+}
diff --git a/tests/Unit/Http/Validation/ValidationServiceProviderTest.php b/tests/Unit/Http/Validation/ValidationServiceProviderTest.php
new file mode 100644
index 00000000..969f4351
--- /dev/null
+++ b/tests/Unit/Http/Validation/ValidationServiceProviderTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Http\Validation;
+
+use Engelsystem\Application;
+use Engelsystem\Http\Validation\ValidationServiceProvider;
+use Engelsystem\Http\Validation\Validator;
+use Engelsystem\Test\Unit\Http\Validation\Stub\ValidatesRequestImplementation;
+use Engelsystem\Test\Unit\ServiceProviderTest;
+use stdClass;
+
+class ValidationServiceProviderTest extends ServiceProviderTest
+{
+ /**
+ * @covers \Engelsystem\Http\Validation\ValidationServiceProvider::register
+ */
+ public function testRegister()
+ {
+ $app = new Application();
+
+ $serviceProvider = new ValidationServiceProvider($app);
+ $serviceProvider->register();
+
+ $this->assertTrue($app->has(Validator::class));
+ $this->assertTrue($app->has('validator'));
+
+ /** @var ValidatesRequestImplementation $validatesRequest */
+ $validatesRequest = $app->make(ValidatesRequestImplementation::class);
+ $this->assertTrue($validatesRequest->hasValidator());
+
+ // Test afterResolving early return
+ $app->make(stdClass::class);
+ }
+}
diff --git a/tests/Unit/Http/Validation/ValidatorTest.php b/tests/Unit/Http/Validation/ValidatorTest.php
new file mode 100644
index 00000000..799265ec
--- /dev/null
+++ b/tests/Unit/Http/Validation/ValidatorTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Http\Validation;
+
+use Engelsystem\Http\Validation\Validates;
+use Engelsystem\Http\Validation\Validator;
+use InvalidArgumentException;
+use PHPUnit\Framework\TestCase;
+
+class ValidatorTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Http\Validation\Validator::__construct
+ * @covers \Engelsystem\Http\Validation\Validator::validate
+ * @covers \Engelsystem\Http\Validation\Validator::getData
+ * @covers \Engelsystem\Http\Validation\Validator::getErrors
+ */
+ public function testValidate()
+ {
+ $val = new Validator(new Validates);
+ $this->assertTrue($val->validate(
+ ['foo' => 'bar', 'lorem' => 'on'],
+ ['foo' => 'required|not_in:lorem,ipsum,dolor', 'lorem' => 'accepted']
+ ));
+ $this->assertEquals(['foo' => 'bar', 'lorem' => 'on'], $val->getData());
+
+ $this->assertFalse($val->validate(
+ [],
+ ['lorem' => 'required|min:3']
+ ));
+ $this->assertEquals(
+ ['lorem' => ['validation.lorem.required', 'validation.lorem.min']],
+ $val->getErrors()
+ );
+ }
+
+ /**
+ * @covers \Engelsystem\Http\Validation\Validator::validate
+ */
+ public function testValidateNotImplemented()
+ {
+ $val = new Validator(new Validates);
+ $this->expectException(InvalidArgumentException::class);
+
+ $val->validate(
+ ['lorem' => 'bar'],
+ ['foo' => 'never_implemented']
+ );
+ }
+}
diff --git a/tests/Unit/Middleware/ErrorHandlerTest.php b/tests/Unit/Middleware/ErrorHandlerTest.php
index 6c37b651..ea9cb216 100644
--- a/tests/Unit/Middleware/ErrorHandlerTest.php
+++ b/tests/Unit/Middleware/ErrorHandlerTest.php
@@ -2,14 +2,23 @@
namespace Engelsystem\Test\Unit\Middleware;
+use Engelsystem\Application;
use Engelsystem\Http\Exceptions\HttpException;
+use Engelsystem\Http\Exceptions\ValidationException;
+use Engelsystem\Http\Psr7ServiceProvider;
+use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
+use Engelsystem\Http\ResponseServiceProvider;
+use Engelsystem\Http\Validation\Validator;
use Engelsystem\Middleware\ErrorHandler;
use Engelsystem\Test\Unit\Middleware\Stub\ReturnResponseMiddlewareHandler;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Twig_LoaderInterface as TwigLoader;
class ErrorHandlerTest extends TestCase
@@ -104,7 +113,7 @@ class ErrorHandlerTest extends TestCase
/**
* @covers \Engelsystem\Middleware\ErrorHandler::process
*/
- public function testProcessException()
+ public function testProcessHttpException()
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
@@ -146,6 +155,63 @@ class ErrorHandlerTest extends TestCase
/**
* @covers \Engelsystem\Middleware\ErrorHandler::process
+ * @covers \Engelsystem\Middleware\ErrorHandler::getPreviousUrl
+ */
+ public function testProcessValidationException()
+ {
+ /** @var TwigLoader|MockObject $twigLoader */
+ $twigLoader = $this->createMock(TwigLoader::class);
+ $handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
+ $validator = $this->createMock(Validator::class);
+
+ $handler->expects($this->exactly(2))
+ ->method('handle')
+ ->willReturnCallback(function () use ($validator) {
+ throw new ValidationException($validator);
+ });
+
+ $validator->expects($this->exactly(2))
+ ->method('getErrors')
+ ->willReturn(['foo' => ['validation.foo.numeric']]);
+
+ $session = new Session(new MockArraySessionStorage());
+ $session->set('errors', ['validation' => ['foo' => ['validation.foo.required']]]);
+ $request = Request::create('/foo/bar', 'POST', ['foo' => 'bar']);
+ $request->setSession($session);
+
+ /** @var Application $app */
+ $app = app();
+ (new ResponseServiceProvider($app))->register();
+ (new Psr7ServiceProvider($app))->register();
+
+ $errorHandler = new ErrorHandler($twigLoader);
+
+ $return = $errorHandler->process($request, $handler);
+
+ $this->assertEquals(302, $return->getStatusCode());
+ $this->assertEquals('/', $return->getHeaderLine('location'));
+ $this->assertEquals([
+ 'errors' => [
+ 'validation' => [
+ 'foo' => [
+ 'validation.foo.required',
+ 'validation.foo.numeric',
+ ],
+ ],
+ ],
+ 'form-data' => [
+ 'foo' => 'bar',
+ ],
+ ], $session->all());
+
+ $request = $request->withAddedHeader('referer', '/foo/batz');
+ $return = $errorHandler->process($request, $handler);
+
+ $this->assertEquals('/foo/batz', $return->getHeaderLine('location'));
+ }
+
+ /**
+ * @covers \Engelsystem\Middleware\ErrorHandler::process
*/
public function testProcessContentTypeSniffer()
{
@@ -153,7 +219,7 @@ class ErrorHandlerTest extends TestCase
$request = $this->createMock(ServerRequestInterface::class);
/** @var TwigLoader|MockObject $twigLoader */
$twigLoader = $this->createMock(TwigLoader::class);
- $response = new Response('<!DOCTYPE html><html><body><h1>Hi!</h1></body></html>', 500);
+ $response = new Response('<!DOCTYPE html><html lang="en"><body><h1>Hi!</h1></body></html>', 500);
$returnResponseHandler = new ReturnResponseMiddlewareHandler($response);
/** @var ErrorHandler|MockObject $errorHandler */