6 Commits
7 changed files with 372 additions and 28 deletions
+2 -1
View File
@@ -21,7 +21,8 @@
"slim/psr7": "^1.3",
"php-di/slim-bridge": "^3.1.0",
"jetbrains/phpstorm-attributes": "^1.0.0",
"myclabs/php-enum": "^1.8.0"
"myclabs/php-enum": "^1.8.0",
"ext-pdo": "*"
},
"require-dev": {
"phpstan/phpstan": "^0.12.80",
@@ -12,6 +12,7 @@ use Slim\Exception\HttpNotFoundException;
use Slim\Psr7\Request;
use Slim\Psr7\Response;
use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryBadWhereDataException;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordAlreadyExistException;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordNotFoundException;
use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursRepository;
@@ -50,13 +51,20 @@ class WorkingHoursController extends AbstractController
* @return Response
*
* @throws HttpInternalServerErrorException
* @throws HttpBadRequestException
* @noinspection PhpUnusedParameterInspection
*/
public function browse(Request $request, Response $response, array $args): Response
{
$repository = new WorkingHoursRepository($this->databases);
$queryParams = $request->getQueryParams();
try {
return $this->printResponse($response, $repository->findAll(), StatusCodeInterface::STATUS_OK);
return $this->printResponse($response, $repository->findFiltered(
$queryParams['start-date'] ?? null,
$queryParams['end-date'] ?? null
), StatusCodeInterface::STATUS_OK);
} catch (RepositoryBadWhereDataException $exception) {
throw new HttpBadRequestException($request, 'Ein Wert für das Datum im Query ist ungültig', $exception);
} catch (Exception $exception) {
throw new HttpInternalServerErrorException($request, 'Der Wert für das Datum oder die Zeit ist falsch in der Datenbank', $exception);
}
@@ -0,0 +1,10 @@
<?php
namespace TorstenHettstedt\TimekeepingApi\Repositories;
class RepositoryBadWhereDataException extends RepositoryException
{
}
@@ -10,6 +10,7 @@ use Exception;
use InvalidArgumentException;
use PDO;
use PDOException;
use PDOStatement;
use TorstenHettstedt\TimekeepingApi\Models\ModelInterface;
use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
@@ -42,10 +43,33 @@ class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWri
* @throws PDOException
*/
public function findAll(): array
{
return $this->findFiltered(null, null);
}
/**
*
* Gibt alle vorhandenen Einträge zurück
*
* @param string|null $start
* @param string|null $end
*
* @return WorkingHours[]
* @throws Exception
*/
public function findFiltered(?string $start, ?string $end): array
{
$models = [];
$stmt = $this->database->prepare('select "Datum" as "workingDay", "Arbeitszeit" as "workingTime" from public."Arbeitszeiten" order by "Datum"');
$stmt = $this->buildFindFilteredStatement($start, $end);
try {
$stmt->execute();
} catch (PDOException $exception) {
if ($exception->getCode() === '22007' || $exception->getCode() === '22008') {
throw new RepositoryBadWhereDataException($exception);
}
throw $exception;
}
while ($row = $stmt->fetch()) {
$model = new WorkingHours();
$model
@@ -56,6 +80,35 @@ class WorkingHoursRepository implements RepositoryReaderInterface, RepositoryWri
return $models;
}
protected function buildFilterString(?string $start, ?string $end): string
{
if (is_string($start) && is_string($end)) {
return 'where "Datum" >= :start AND "Datum" <= :end ';
}
if (is_string($start)) {
return 'where "Datum" >= :start ';
}
if (is_string($end)) {
return 'where "Datum" <= :end ';
}
return '';
}
protected function buildFindFilteredStatement(?string $start, ?string $end): ?PDOStatement
{
$query = 'select "Datum" as "workingDay", "Arbeitszeit" as "workingTime" from public."Arbeitszeiten" ';
$query .= $this->buildFilterString($start, $end);
$query .= 'order by "Datum"';
$stmt = $this->database->prepare($query);
if (is_string($start)) {
$stmt->bindParam(':start', $start);
}
if (is_string($end)) {
$stmt->bindParam(':end', $end);
}
return $stmt ?? null;
}
/**
* Gibt den Eintrag zurück, der durch die ID
*
@@ -21,13 +21,28 @@ class BrowseWorkingHoursCest
$I->seeHttpHeader('Access-Control-Max-Age');
}
/**
* @param ApiTester $I
*
* @dataProvider validWorkingHoursFilterProvider
*/
public function browseEmptyWorkingHours(ApiTester $I): void
{
$I->haveHttpHeader('Origin', Api::TEST_ORIGIN);
$I->sendGet('/working-hours', ['start-date' => '2020-04-01', 'end-date' => '2020-04-30']);
$I->seeResponseCodeIs(HttpCode::OK);
$I->canSeeHttpHeader('Access-Control-Allow-Origin', Api::TEST_ORIGIN);
$I->seeResponseIsJson();
$I->seeResponseEquals('[]');
}
public function validWorkingHoursFilterProvider(): array
{
return [
[[null]],
[['start-date' => '2020-01-01', 'end-date' => '2020-01-08']],
[['start-date' => '2020-01-01', 'end-date' => '2020-04-30']],
[['start-date' => '2020-04-01', 'end-date' => '2020-04-30']],
[['start-date' => '2020-03-01', 'end-date' => '2020-03-07']],
];
}
@@ -2,6 +2,7 @@
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Controller;
use Codeception\Example;
use Codeception\Test\Unit;
use Exception;
use PDO;
@@ -37,13 +38,13 @@ class WorkingHoursControllerTest extends Unit
/** @noinspection SpellCheckingInspection */
$this->container = $this->makeEmpty(ContainerInterface::class, [
'has' => true,
'get' => new PDO('pgsql:host=psql.torsten-hettstedt.net;port=5432;dbname=testdb;user=bruce;password=mypass')
'get' => new PDO('pgsql:host=psql.torsten-hettstedt.net;port=5432;dbname=testdb;user=bruce;password=mypass'),
]);
$this->request = $this->makeEmpty(Request::class, [
'getParsedBody' => [
'workingDay' => self::EXISTING_DATE,
'workingTime' => self::EXISTING_INTERVAL,
]
],
]);
$this->response = $this->makeEmpty(Response::class, [
'getBody' => $this->makeEmpty(StreamInterface::class, [
@@ -65,23 +66,202 @@ class WorkingHoursControllerTest extends Unit
public function testConstructWithNonDatabase(): void
{
$this->container = $this->makeEmpty(ContainerInterface::class, [
'has' => false
'has' => false,
]);
$this->expectException(NotDatabasesException::class);
new WorkingHoursController($this->container);
}
/**
* @return array<int, array<int, array<string, string>>>
*/
public function valideFilterDataProvider(): array
{
return [
[
[],
],
[
[
'start-date' => '2020-03-01',
'end-date' => '2020-03-31',
],
],
[
[
'start-date' => '2020-03-01',
],
],
[
[
'end-date' => '2020-03-31',
],
],
];
}
/**
* @param array<string, string> $queryParams
*
* @dataProvider valideFilterDataProvider
*
* @throws HttpInternalServerErrorException
* @throws NotDatabasesException
* @throws Exception
*/
public function testBrowse(): void
public function testBrowseWithValideParameter(array $queryParams): void
{
$this->request = $this->makeEmpty(Request::class, [
'getQueryParams' => $queryParams,
]);
$controller = new WorkingHoursController($this->container);
$response = $controller->browse($this->request, $this->response, []);
$this->assertInstanceOf(Response::class, $response);
}
/**
* @return array<int, array<int, array<string, string|null>>>
*/
public function invalideFilterDataProvider(): array
{
return [
[
[
'start-date' => '',
'end-date' => '',
],
],
[
[
'start-date' => '',
],
],
[
[
'end-date' => '',
],
],
[
[
'start-date' => '2020-03-01',
'end-date' => '',
],
],
[
[
'start-date' => '',
'end-date' => '2020-03-31',
],
],
//
[
[
'start-date' => '2020-03-00',
'end-date' => '2020-04-31',
],
],
[
[
'start-date' => '2020-03-00',
],
],
[
[
'end-date' => '2020-04-31',
],
],
[
[
'start-date' => '2020-03-01',
'end-date' => '2020-04-31',
],
],
[
[
'start-date' => '2020-03-00',
'end-date' => '2020-03-31',
],
],
//
[
[
'start-date' => '2020-03',
'end-date' => '2020-03',
],
],
[
[
'start-date' => '2020-03',
],
],
[
[
'end-date' => '2020-03',
],
],
[
[
'start-date' => '2020-03-01',
'end-date' => '2020-03',
],
],
[
[
'start-date' => '2020-03',
'end-date' => '2020-03-31',
],
],
//
[
[
'start-date' => '2020',
'end-date' => '2020',
],
],
[
[
'start-date' => '2020',
],
],
[
[
'end-date' => '2020',
],
],
[
[
'start-date' => '2020-03-01',
'end-date' => '2020',
],
],
[
[
'start-date' => '2020',
'end-date' => '2020-03-31',
],
],
];
}
/**
* @param array<string, string> $queryParams
*
* @dataProvider invalideFilterDataProvider
*
* @throws HttpInternalServerErrorException
* @throws NotDatabasesException
* @throws Exception
*/
public function testBrowseWithInvalideParameter(array $queryParams): void
{
$this->request = $this->makeEmpty(Request::class, [
'getQueryParams' => $queryParams,
]);
$controller = new WorkingHoursController($this->container);
$this->expectException(HttpBadRequestException::class);
$controller->browse($this->request, $this->response, []);
}
/**
* @throws NotDatabasesException
* @throws HttpBadRequestException
@@ -91,7 +271,7 @@ class WorkingHoursControllerTest extends Unit
{
$controller = new WorkingHoursController($this->container);
$response = $controller->update($this->request, $this->response, [
'id' => self::EXISTING_DATE
'id' => self::EXISTING_DATE,
]);
$this->assertInstanceOf(Response::class, $response);
}
@@ -106,23 +286,23 @@ class WorkingHoursControllerTest extends Unit
$controller = new WorkingHoursController($this->container);
$this->expectException(HttpNotFoundException::class);
$controller->update($this->request, $this->response, [
'id' => self::NEW_DATE
'id' => self::NEW_DATE,
]);
}
/**
* @return string[][]
*/
public function invalidDataProvider(): array
public function invalidCreatDataProvider(): array
{
return [
[
self::NEW_DATE,
'07:59'
'07:59',
],
[
'2020-13-33',
self::NEW_INTERVAL
self::NEW_INTERVAL,
],
];
}
@@ -131,7 +311,7 @@ class WorkingHoursControllerTest extends Unit
* @param string $date
* @param string $time
*
* @dataProvider invalidDataProvider
* @dataProvider invalidCreatDataProvider
*
* @throws HttpBadRequestException
* @throws HttpConflictRequestException
@@ -145,7 +325,7 @@ class WorkingHoursControllerTest extends Unit
'getParsedBody' => [
'workingDay' => $date,
'workingTime' => $time,
]
],
]);
$controller = new WorkingHoursController($this->container);
$this->expectException(HttpBadRequestException::class);
@@ -165,7 +345,7 @@ class WorkingHoursControllerTest extends Unit
'getParsedBody' => [
'workingDay' => self::NEW_DATE,
'workingTime' => self::NEW_INTERVAL,
]
],
]);
$controller = new WorkingHoursController($this->container);
$response = $controller->creat($this->request, $this->response, []);
@@ -185,7 +365,7 @@ class WorkingHoursControllerTest extends Unit
'getParsedBody' => [
'workingDay' => self::EXISTING_DATE,
'workingTime' => self::EXISTING_INTERVAL,
]
],
]);
$controller = new WorkingHoursController($this->container);
$this->expectException(HttpConflictRequestException::class);
@@ -201,7 +381,7 @@ class WorkingHoursControllerTest extends Unit
{
$controller = new WorkingHoursController($this->container);
$response = $controller->read($this->request, $this->response, [
'id' => self::EXISTING_DATE
'id' => self::EXISTING_DATE,
]);
$this->assertInstanceOf(Response::class, $response);
}
@@ -216,7 +396,7 @@ class WorkingHoursControllerTest extends Unit
$controller = new WorkingHoursController($this->container);
$this->expectException(HttpNotFoundException::class);
$controller->read($this->request, $this->response, [
'id' => self::NEW_DATE
'id' => self::NEW_DATE,
]);
}
}
@@ -2,6 +2,7 @@
namespace TorstenHettstedt\TimekeepingApi\Tests\Unit\Repositories;
use Codeception\Example;
use Codeception\Test\Unit;
use DateInterval;
use DateTime;
@@ -10,6 +11,7 @@ use InvalidArgumentException;
use PDO;
use TorstenHettstedt\TimekeepingApi\Models\ModelInterface;
use TorstenHettstedt\TimekeepingApi\Models\WorkingHours;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryBadWhereDataException;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordAlreadyExistException;
use TorstenHettstedt\TimekeepingApi\Repositories\RepositoryRecordNotFoundException;
use TorstenHettstedt\TimekeepingApi\Repositories\WorkingHoursRepository;
@@ -34,7 +36,7 @@ class WorkingHoursRepositoryTest extends Unit
/**
* @throws Exception
*/
public function testFindAll(): void
public function testFindAllWithBlankParameter(): void
{
$repository = new WorkingHoursRepository($this->pdoObject);
$models = $repository->findAll();
@@ -43,6 +45,81 @@ class WorkingHoursRepositoryTest extends Unit
$this->assertCount(self::RECORDS_COUNT, $models);
}
/**
* @return array<int, array<int|string|null>>
*/
public function valideFilterParameter(): array
{
return [
[null, null, 61,],
['2020-04-01', null, 0,],
['2020-04-01', '2020-04-30', 0,],
[null, '2020-04-30', 61,],
['2020-03-01', null, 22,],
['2020-03-01', '2020-03-31', 22,],
['2020-03-01', '2020-03-07', 5,],
];
}
/**
* @param string|null $start
* @param string|null $ende
* @param int $count
*
* @dataProvider valideFilterParameter
*
* @throws Exception
*/
public function testFindAllWithValideParameter(?string $start, ?string $ende, int $count): void
{
$repository = new WorkingHoursRepository($this->pdoObject);
$models = $repository->findFiltered($start, $ende);
$this->assertIsArray($models);
$this->assertContainsOnly(WorkingHours::class, $models);
$this->assertCount($count, $models);
}
/**
* @return array<int, array<int|string|null>>
*/
public function invalideFilterParameter(): array
{
return [
['2020-04-31',null],
[null, '2020-04-31'],
['2020-04-00','2020-04-31'],
['2020-04-01','2020-04-31'],
['2020-04-00','2020-04-30'],
//
['2020-04', null],
[null, '2020-04'],
['2020-04', '2020-04'],
['2020-04-01','2020-04'],
['2020-04', '2020-04-30'],
//
['2020', null],
[null, '2020'],
['2020', '2020'],
['2020-04-01','2020'],
['2020', '2020-04-30'],
];
}
/**
* @param string|null $start
* @param string|null $ende
*
* @dataProvider invalideFilterParameter
*
* @throws Exception
*/
public function testFindAllWithInvalideParameter(?string $start, ?string $ende): void
{
$repository = new WorkingHoursRepository($this->pdoObject);
$this->expectException(RepositoryBadWhereDataException::class);
$repository->findFiltered($start, $ende);
}
/**
* @throws RepositoryRecordNotFoundException
*/