3 Commits
25 changed files with 562 additions and 4 deletions
-1
View File
@@ -105,4 +105,3 @@ Temporary Items
/api/vendor/ /api/vendor/
/api/tests/_* /api/tests/_*
/api/tests/*.suite.yml /api/tests/*.suite.yml
/api/html/swagger/
+5 -2
View File
@@ -27,7 +27,8 @@
"codeception/codeception": "^4.1.18", "codeception/codeception": "^4.1.18",
"codeception/module-phpbrowser": "^1.0.0", "codeception/module-phpbrowser": "^1.0.0",
"codeception/module-asserts": "^1.0.0", "codeception/module-asserts": "^1.0.0",
"codeception/module-db": "^1.1.0" "codeception/module-db": "^1.1.0",
"codeception/module-rest": "^1.2.8"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@@ -36,7 +37,9 @@
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"TorstenHettstedt\\TimekeepingApi\\Tests\\Unit\\": "tests/unit" "TorstenHettstedt\\TimekeepingApi\\Tests\\Unit\\": "tests/unit",
"TorstenHettstedt\\TimekeepingApi\\Tests\\Api\\": "tests/api",
"Helper\\": "tests/_support/Helper"
} }
} }
} }
+1
View File
@@ -15,6 +15,7 @@
# absolute physical path to the directory that contains this htaccess file. # absolute physical path to the directory that contains this htaccess file.
# RewriteBase / # RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L] RewriteRule ^ index.php [QSA,L]
</IfModule> </IfModule>
+3 -1
View File
@@ -174,7 +174,9 @@
}, },
"workingHours": { "workingHours": {
"description": "Arbeitszeit des Tages", "description": "Arbeitszeit des Tages",
"type": "integer" "type": "string",
"pattern": "[0-5]\\d:[0-5]\\d:[0-5]\\d",
"example": "08:01:00"
} }
} }
}, },
Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

+60
View File
@@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="swagger-ui.css" >
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="swagger-ui-bundle.js"> </script>
<script src="swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "../openapi.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}
</script>
</body>
</html>
+68
View File
@@ -0,0 +1,68 @@
<!doctype html>
<html lang="en-US">
<title>Swagger UI: OAuth2 Redirect</title>
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+34
View File
@@ -0,0 +1,34 @@
<?php
namespace Helper;
// here you can define custom actions
// all public methods declared in helper class will be available in $I
use Codeception\Module;
class Api extends Module
{
const FORMAT_TIME = 'string:regex([0-5]\d:[0-5]\d:[0-5]\d)';
const WORKING_HOURS_JSON_FORMAT = [
'date' => 'string:date',
'workingHours' => self::FORMAT_TIME,
];
const WORKING_HOURS_VIEW_JSON_FORMAT = [
"period" => "string",
"periodDesignation" => "string",
"totalHours" => self::FORMAT_TIME,
"workingDays" => 'integer:>0',
"overtime" => self::FORMAT_TIME,
];
const ERROR_JSON_FORMAT = [
"timestamp" => "string:date-time",
"status" => 0,
"error" => "string",
"message" => "string",
"path" => "string:path",
];
}
+8
View File
@@ -0,0 +1,8 @@
actor: ApiTester
modules:
enabled:
- \Helper\Api
- REST:
url: http://localhost:8090/
depends: PhpBrowser
part: Json
@@ -0,0 +1,21 @@
<?php /** @noinspection PhpUnused */
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\Views;
use ApiTester;
use Codeception\Util\HttpCode;
use Helper\Api;
class BrowseWorkingHoursMonthlyCest
{
/**
* @param ApiTester $I
*/
public function browseWorkingHoursView(ApiTester $I): void
{
$I->sendGet('/views/working-hours/monthly');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT);
}
}
@@ -0,0 +1,21 @@
<?php /** @noinspection PhpUnused */
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\Views;
use ApiTester;
use Codeception\Util\HttpCode;
use Helper\Api;
class BrowseWorkingHoursWeeklyCest
{
/**
* @param ApiTester $I
*/
public function browseWorkingHoursView(ApiTester $I): void
{
$I->sendGet('/views/working-hours/weekly');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT);
}
}
@@ -0,0 +1,21 @@
<?php /** @noinspection PhpUnused */
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\Views;
use ApiTester;
use Codeception\Util\HttpCode;
use Helper\Api;
class BrowseWorkingHoursYearlyCest
{
/**
* @param ApiTester $I
*/
public function browseWorkingHoursView(ApiTester $I): void
{
$I->sendGet('/views/working-hours/yearly');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_VIEW_JSON_FORMAT);
}
}
@@ -0,0 +1,21 @@
<?php /** @noinspection PhpUnused */
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours;
use ApiTester;
use Codeception\Util\HttpCode;
use Helper\Api;
class BrowseWorkingHoursCest
{
/**
* @param ApiTester $I
*/
public function browseWorkingHours(ApiTester $I): void
{
$I->sendGet('/working-hours');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
}
}
@@ -0,0 +1,51 @@
<?php /** @noinspection PhpUnused */
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours;
use ApiTester;
use Codeception\Util\HttpCode;
use Helper\Api;
class CreateWorkingHoursCest
{
public function createWorkingHoursWithNewValidRecord(ApiTester $I): void
{
$I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json');
$I->sendPost('/working-hours', [
'date' => '2020-04-01',
'workingHours' => '08:00:00',
]);
$I->seeResponseCodeIs(HttpCode::CREATED);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
}
// tests
public function createWorkingHoursWithNewInvalidRecord(ApiTester $I): void
{
$I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json');
$I->sendPost('/working-hours', [
'date' => '2020-04-01',
'workingHours' => 8.0,
]);
$I->seeResponseCodeIs(HttpCode::BAD_REQUEST);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
}
// tests
public function createWorkingHoursWithExistingValidRecord(ApiTester $I): void
{
$I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json');
$I->sendPost('/working-hours', [
'date' => '2020-01-15',
'workingHours' => '08:00:00',
]);
$I->seeResponseCodeIs(HttpCode::CONFLICT);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
}
}
@@ -0,0 +1,26 @@
<?php /** @noinspection PhpUnused */
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours;
use ApiTester;
use Codeception\Util\HttpCode;
use Helper\Api;
class ReadWorkingHoursCest
{
public function readExistingWorkingHours(ApiTester $I): void
{
$I->sendGet('/working-hours/2020-01-15');
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
}
public function readNotExistingWorkingHours(ApiTester $I): void
{
$I->sendGet('/working-hours/2020-04-15');
$I->seeResponseCodeIs(HttpCode::NOT_FOUND);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
}
}
@@ -0,0 +1,49 @@
<?php /** @noinspection PhpUnused */
namespace TorstenHettstedt\TimekeepingApi\Tests\Api\WorkingHours;
use ApiTester;
use Codeception\Util\HttpCode;
use Helper\Api;
class UpdateWorkingHoursCest
{
public function updateWorkingHoursWithExistingValidRecord(ApiTester $I): void
{
$I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json');
$I->sendPut('/working-hours/2020-01-15', [
'date' => '2020-01-15',
'workingHours' => '10:00:00',
]);
$I->seeResponseCodeIs(HttpCode::OK);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::WORKING_HOURS_JSON_FORMAT);
}
public function updateWorkingHoursWithNewValidRecord(ApiTester $I): void
{
$I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json');
$I->sendPut('/working-hours/2020-01-15', [
'date' => '2020-04-15',
'workingHours' => '10:00:00',
]);
$I->seeResponseCodeIs(HttpCode::NOT_FOUND);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
}
public function updateWorkingHoursWithNewInvalidRecord(ApiTester $I): void
{
$I->haveHttpHeader('accept', 'application/json');
$I->haveHttpHeader('content-type', 'application/json');
$I->sendPut('/working-hours/2020-01-15', [
'date' => '2020-01-15',
'workingHours' => 8.0,
]);
$I->seeResponseCodeIs(HttpCode::BAD_REQUEST);
$I->seeResponseIsJson();
$I->seeResponseMatchesJsonType(Api::ERROR_JSON_FORMAT);
}
}