Initial commit

This commit is contained in:
Dávid Danyi 2017-07-31 15:50:48 +02:00
commit 05f45e5153
44 changed files with 5668 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.idea
composer.phar
clover.xml
coveralls-upload.json
phpunit.xml
vendor/

View File

@ -0,0 +1,46 @@
<?php
/**
* Script for clearing the configuration cache.
*
* Can also be invoked as `composer clear-config-cache`.
*
* @see https://github.com/zendframework/zend-expressive-skeleton for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-expressive-skeleton/blob/master/LICENSE.md New BSD License
*/
chdir(__DIR__ . '/../');
require 'vendor/autoload.php';
$config = include 'config/config.php';
if (! isset($config['config_cache_path'])) {
echo "No configuration cache path found" . PHP_EOL;
exit(0);
}
if (! file_exists($config['config_cache_path'])) {
printf(
"Configured config cache file '%s' not found%s",
$config['config_cache_path'],
PHP_EOL
);
exit(0);
}
if (false === unlink($config['config_cache_path'])) {
printf(
"Error removing config cache file '%s'%s",
$config['config_cache_path'],
PHP_EOL
);
exit(1);
}
printf(
"Removed configured config cache file '%s'%s",
$config['config_cache_path'],
PHP_EOL
);
exit(0);

62
composer.json Normal file
View File

@ -0,0 +1,62 @@
{
"name": "zendframework/zend-expressive-skeleton",
"description": "Zend expressive skeleton. Begin developing PSR-7 middleware applications in seconds!",
"type": "project",
"homepage": "https://github.com/zendframework/zend-expressive-skeleton",
"license": "BSD-3-Clause",
"config": {
"sort-packages": true,
"timeout": 0
},
"require": {
"php": "^7.1",
"doctrine/common": "^2.8",
"roave/security-advisories": "dev-master",
"zendframework/zend-cache": "^2.7",
"zendframework/zend-component-installer": "^1.0",
"zendframework/zend-config": "^3.1",
"zendframework/zend-config-aggregator": "^1.0",
"zendframework/zend-expressive": "^2.0.2",
"zendframework/zend-expressive-fastroute": "^2.0",
"zendframework/zend-expressive-helpers": "^4.0",
"zendframework/zend-http": "^2.6",
"zendframework/zend-json": "^3.0",
"zendframework/zend-servicemanager": "^3.3",
"zendframework/zend-stdlib": "^3.1"
},
"require-dev": {
"phpunit/phpunit": "^6.0.8 || ^5.7.15",
"squizlabs/php_codesniffer": "^2.8.1",
"zfcampus/zf-development-mode": "^3.1",
"filp/whoops": "^2.1.7"
},
"autoload": {
"psr-4": {
"App\\": "src/App/"
}
},
"autoload-dev": {
"psr-4": {
"AppTest\\": "test/AppTest/"
}
},
"scripts": {
"post-create-project-cmd": [
"@development-enable"
],
"development-disable": "zf-development-mode disable",
"development-enable": "zf-development-mode enable",
"development-status": "zf-development-mode status",
"check": [
"@cs-check",
"@test"
],
"clear-config-cache": "php bin/clear-config-cache.php",
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"serve": "php -S 0.0.0.0:8080 -t public public/index.php",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"upload-coverage": "coveralls -v"
}
}

3615
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

1
config/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
development.config.php

2
config/autoload/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
local.php
*.local.php

View File

@ -0,0 +1,39 @@
<?php
use Zend\Expressive\Application;
use Zend\Expressive\Container;
use Zend\Expressive\Delegate;
use Zend\Expressive\Helper;
use Zend\Expressive\Middleware;
return [
// Provides application-wide services.
// We recommend using fully-qualified class names whenever possible as
// service names.
'dependencies' => [
// Use 'aliases' to alias a service name to another service. The
// key is the alias name, the value is the service to which it points.
'aliases' => [
'Zend\Expressive\Delegate\DefaultDelegate' => Delegate\NotFoundDelegate::class,
],
// Use 'invokables' for constructor-less services, or services that do
// not require arguments to the constructor. Map a service name to the
// class name.
'invokables' => [
// Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class,
Helper\ServerUrlHelper::class => Helper\ServerUrlHelper::class,
],
// Use 'factories' for services provided by callbacks/factory classes.
'factories' => [
Application::class => Container\ApplicationFactory::class,
Delegate\NotFoundDelegate::class => Container\NotFoundDelegateFactory::class,
Helper\ServerUrlMiddleware::class => Helper\ServerUrlMiddlewareFactory::class,
Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
Helper\UrlHelperMiddleware::class => Helper\UrlHelperMiddlewareFactory::class,
Zend\Stratigility\Middleware\ErrorHandler::class => Container\ErrorHandlerFactory::class,
Middleware\ErrorResponseGenerator::class => Container\ErrorResponseGeneratorFactory::class,
Middleware\NotFoundHandler::class => Container\NotFoundHandlerFactory::class,
],
],
];

View File

@ -0,0 +1,34 @@
<?php
/**
* Development-only configuration.
*
* Put settings you want enabled when under development mode in this file, and
* check it into your repository.
*
* Developers on your team will then automatically enable them by calling on
* `composer development-enable`.
*/
use Zend\Expressive\Container;
use Zend\Expressive\Middleware\ErrorResponseGenerator;
return [
'dependencies' => [
'invokables' => [
],
'factories' => [
ErrorResponseGenerator::class => Container\WhoopsErrorResponseGeneratorFactory::class,
'Zend\Expressive\Whoops' => Container\WhoopsFactory::class,
'Zend\Expressive\WhoopsPageHandler' => Container\WhoopsPageHandlerFactory::class,
],
],
'whoops' => [
'json_exceptions' => [
'display' => true,
'show_trace' => true,
'ajax_only' => true,
],
],
];

View File

@ -0,0 +1,16 @@
<?php
/**
* Local configuration.
*
* Copy this file to `local.php` and change its settings as required.
* `local.php` is ignored by git and safe to use for local and sensitive data like usernames and passwords.
*/
return [
'app.config' => [
'jira.user' => '',
'jira.password' => '',
'url.jiraKanbanBoard' => 'https://jirapducc.mo.ca.am.ericsson.se/rest/api/2/search?jql=filter=14223&maxResults=1000&fields=assignee,summary,issuetype,status,customfield_10010,',
],
];

View File

@ -0,0 +1,12 @@
<?php
use Zend\Expressive\Router\FastRouteRouter;
use Zend\Expressive\Router\RouterInterface;
return [
'dependencies' => [
'invokables' => [
RouterInterface::class => FastRouteRouter::class,
],
],
];

View File

@ -0,0 +1,27 @@
<?php
use Zend\ConfigAggregator\ConfigAggregator;
return [
// Toggle the configuration cache. Set this to boolean false, or remove the
// directive, to disable configuration caching. Toggling development mode
// will also disable it by default; clear the configuration cache using
// `composer clear-config-cache`.
ConfigAggregator::ENABLE_CACHE => true,
// Enable debugging; typically used to provide debugging information within templates.
'debug' => false,
'zend-expressive' => [
// Enable programmatic pipeline: Any `middleware_pipeline` or `routes`
// configuration will be ignored when creating the `Application` instance.
'programmatic_pipeline' => true,
// Provide templates for the error handling middleware to use when
// generating responses.
'error_handler' => [
'template_404' => 'error::404',
'template_error' => 'error::error',
],
],
];

34
config/config.php Normal file
View File

@ -0,0 +1,34 @@
<?php
use Zend\ConfigAggregator\ArrayProvider;
use Zend\ConfigAggregator\ConfigAggregator;
use Zend\ConfigAggregator\PhpFileProvider;
// To enable or disable caching, set the `ConfigAggregator::ENABLE_CACHE` boolean in
// `config/autoload/local.php`.
$cacheConfig = [
'config_cache_path' => 'data/config-cache.php',
];
$aggregator = new ConfigAggregator([
\Zend\Cache\ConfigProvider::class,
\Zend\Validator\ConfigProvider::class,
// Include cache configuration
new ArrayProvider($cacheConfig),
// Default App module config
App\ConfigProvider::class,
// Load application config in a pre-defined order in such a way that local settings
// overwrite global settings. (Loaded as first to last):
// - `global.php`
// - `*.global.php`
// - `local.php`
// - `*.local.php`
new PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
// Load development config if it exists
new PhpFileProvider('config/development.config.php'),
], $cacheConfig['config_cache_path']);
return $aggregator->getMergedConfig();

16
config/container.php Normal file
View File

@ -0,0 +1,16 @@
<?php
use Zend\ServiceManager\Config;
use Zend\ServiceManager\ServiceManager;
// Load configuration
$config = require __DIR__ . '/config.php';
// Build container
$container = new ServiceManager();
(new Config($config['dependencies']))->configureServiceManager($container);
// Inject config
$container->setService('config', $config);
return $container;

View File

@ -0,0 +1,29 @@
<?php
/**
* File required to allow enablement of development mode.
*
* For use with the zf-development-mode tool.
*
* Usage:
* $ composer development-disable
* $ composer development-enable
* $ composer development-status
*
* DO NOT MODIFY THIS FILE.
*
* Provide your own development-mode settings by editing the file
* `config/autoload/development.local.php.dist`.
*
* Because this file is aggregated last, it simply ensures:
*
* - The `debug` flag is _enabled_.
* - Configuration caching is _disabled_.
*/
use Zend\ConfigAggregator\ConfigAggregator;
return [
'debug' => true,
ConfigAggregator::ENABLE_CACHE => false,
];

55
config/pipeline.php Normal file
View File

@ -0,0 +1,55 @@
<?php
use Zend\Expressive\Helper\ServerUrlMiddleware;
use Zend\Expressive\Helper\UrlHelperMiddleware;
use Zend\Expressive\Middleware\ImplicitHeadMiddleware;
use Zend\Expressive\Middleware\ImplicitOptionsMiddleware;
use Zend\Expressive\Middleware\NotFoundHandler;
use Zend\Stratigility\Middleware\ErrorHandler;
/**
* Setup middleware pipeline:
*/
// The error handler should be the first (most outer) middleware to catch
// all Exceptions.
$app->pipe(ErrorHandler::class);
$app->pipe(ServerUrlMiddleware::class);
// Pipe more middleware here that you want to execute on every request:
// - bootstrapping
// - pre-conditions
// - modifications to outgoing responses
//
// Piped Middleware may be either callables or service names. Middleware may
// also be passed as an array; each item in the array must resolve to
// middleware eventually (i.e., callable or service name).
//
// Middleware can be attached to specific paths, allowing you to mix and match
// applications under a common domain. The handlers in each middleware
// attached this way will see a URI with the MATCHED PATH SEGMENT REMOVED!!!
//
// - $app->pipe('/api', $apiMiddleware);
// - $app->pipe('/docs', $apiDocMiddleware);
// - $app->pipe('/files', $filesMiddleware);
// Register the routing middleware in the middleware pipeline
$app->pipeRoutingMiddleware();
$app->pipe(ImplicitHeadMiddleware::class);
$app->pipe(ImplicitOptionsMiddleware::class);
$app->pipe(UrlHelperMiddleware::class);
// Add more middleware here that needs to introspect the routing results; this
// might include:
//
// - route-based authentication
// - route-based validation
// - etc.
// Register the dispatch middleware in the middleware pipeline
$app->pipeDispatchMiddleware();
// At this point, if no Response is return by any middleware, the
// NotFoundHandler kicks in; alternately, you can provide other fallback
// middleware to execute.
$app->pipe(NotFoundHandler::class);

33
config/routes.php Normal file
View File

@ -0,0 +1,33 @@
<?php
/**
* Setup routes with a single request method:
*
* $app->get('/', App\Action\HomePageAction::class, 'home');
* $app->post('/album', App\Action\AlbumCreateAction::class, 'album.create');
* $app->put('/album/:id', App\Action\AlbumUpdateAction::class, 'album.put');
* $app->patch('/album/:id', App\Action\AlbumUpdateAction::class, 'album.patch');
* $app->delete('/album/:id', App\Action\AlbumDeleteAction::class, 'album.delete');
*
* Or with multiple request methods:
*
* $app->route('/contact', App\Action\ContactAction::class, ['GET', 'POST', ...], 'contact');
*
* Or handling all request methods:
*
* $app->route('/contact', App\Action\ContactAction::class)->setName('contact');
*
* or:
*
* $app->route(
* '/contact',
* App\Action\ContactAction::class,
* Zend\Expressive\Router\Route::HTTP_METHOD_ANY,
* 'contact'
* );
*/
$app->get('/', App\Action\HomePageAction::class, 'home');
$app->get('/api/ping', App\Action\PingAction::class, 'api.ping');
$app->get('/api/kanban', App\Action\KanbanAction::class, 'api.kanban');
$app->get('/avatars/{signum}', App\Action\AvatarAction::class, 'user.avatar');

2
data/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

59
deploy.php Normal file
View File

@ -0,0 +1,59 @@
<?php
namespace Deployer;
require 'recipe/common.php';
// Configuration
set('ssh_type', 'native');
set('ssh_multiplexing', true);
set('repository', 'https://gogs.ragnarok.yvan.hu/TSP/taurus-api.git');
set('shared_files', [
'config/autoload/local.php',
// 'config/autoload/doctrine.local.php',
// 'config/autoload/los-basepath.local.php',
// 'config/autoload/errorhandler.local.php',
]);
/*
set('shared_dirs', [
'data/persistent',
]);
*/
set('writable_dirs', []);
set('keep_releases', 3);
set('default_stage', 'production');
// Servers
server('vasgyuro', 'vasgyuro.tsp')
->stage('production')
->user('edvidan')
->forwardAgent()
->set('php_service_name', 'php7.1-fpm')
->set('deploy_path', '/home/edvidan/applications/taurus-api');
desc('Reload PHP-FPM service');
task('php-fpm:reload', function () {
// The user must have rights for restart service
// /etc/sudoers: username ALL=NOPASSWD:/bin/systemctl restart php-fpm.service
run('sudo service {{php_service_name}} reload');
}); //->onlyOn('alfheim');
after('deploy:symlink', 'php-fpm:reload');
desc('Deploy your project');
task('deploy', [
'deploy:prepare',
'deploy:lock',
'deploy:release',
'deploy:update_code',
'deploy:shared',
'deploy:writable',
'deploy:vendors',
'deploy:clear_paths',
'deploy:symlink',
'deploy:unlock',
'cleanup',
]);
after('deploy', 'success');

20
phpcs.xml.dist Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<ruleset name="Expressive Skeleton coding standard">
<description>Expressive Skeleton coding standard</description>
<!-- display progress -->
<arg value="p"/>
<arg name="colors"/>
<!-- inherit rules from: -->
<rule ref="PSR2"/>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
<properties>
<property name="ignoreBlankLines" value="false"/>
</properties>
</rule>
<!-- Paths to check -->
<file>src</file>
</ruleset>

17
phpunit.xml.dist Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="App\\Tests">
<directory>./test</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

17
public/.htaccess Normal file
View File

@ -0,0 +1,17 @@
RewriteEngine On
# The following rule tells Apache that if the requested filename
# exists, simply serve it.
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
# The following rewrites all other queries to index.php. The
# condition ensures that if you are using Apache aliases to do
# mass virtual hosting, the base path will be prepended to
# allow proper resolution of the index.php file; it will work
# in non-aliased environments as well, providing a safe, one-size
# fits all solution.
RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
RewriteRule ^(.*) - [E=BASE:%1]
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

29
public/index.php Normal file
View File

@ -0,0 +1,29 @@
<?php
// Delegate static file requests back to the PHP built-in webserver
if (php_sapi_name() === 'cli-server'
&& is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))
) {
return false;
}
chdir(dirname(__DIR__));
require 'vendor/autoload.php';
/**
* Self-called anonymous function that creates its own scope and keep the global namespace clean.
*/
call_user_func(function () {
/** @var \Interop\Container\ContainerInterface $container */
$container = require 'config/container.php';
/** @var \Zend\Expressive\Application $app */
$app = $container->get(\Zend\Expressive\Application::class);
// Import programmatic/declarative middleware pipeline and routing
// configuration statements
require 'config/pipeline.php';
require 'config/routes.php';
$app->run();
});

BIN
public/zf-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

View File

@ -0,0 +1,33 @@
<?php
namespace App\Action;
use App\Service\AvatarService;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\EmptyResponse;
use Zend\Diactoros\Response\TextResponse;
class AvatarAction implements ServerMiddlewareInterface
{
private $avatarService;
public function __construct(AvatarService $avatarService)
{
$this->avatarService = $avatarService;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$signum = $request->getAttribute('signum', false);
if(!$signum) {
return new EmptyResponse();
}
$avatarData = $this->avatarService->getAvatarData($signum);
return new TextResponse($avatarData, 200, [
'content-type' => 'image/png',
]);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Action;
use App\Service\AvatarService;
use Interop\Container\ContainerInterface;
class AvatarFactory
{
public function __invoke(ContainerInterface $container)
{
$avatarService = $container->get(AvatarService::class);
return new AvatarAction($avatarService);
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Action;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Expressive\Router;
use Zend\Expressive\Template;
use Zend\Expressive\Plates\PlatesRenderer;
use Zend\Expressive\Twig\TwigRenderer;
use Zend\Expressive\ZendView\ZendViewRenderer;
class HomePageAction implements ServerMiddlewareInterface
{
private $router;
private $template;
public function __construct(Router\RouterInterface $router, Template\TemplateRendererInterface $template = null)
{
$this->router = $router;
$this->template = $template;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
if (! $this->template) {
return new JsonResponse([
'welcome' => 'Congratulations! You have installed the zend-expressive skeleton application.',
'docsUrl' => 'https://docs.zendframework.com/zend-expressive/',
]);
}
$data = [];
if ($this->router instanceof Router\AuraRouter) {
$data['routerName'] = 'Aura.Router';
$data['routerDocs'] = 'http://auraphp.com/packages/2.x/Router.html';
} elseif ($this->router instanceof Router\FastRouteRouter) {
$data['routerName'] = 'FastRoute';
$data['routerDocs'] = 'https://github.com/nikic/FastRoute';
} elseif ($this->router instanceof Router\ZendRouter) {
$data['routerName'] = 'Zend Router';
$data['routerDocs'] = 'https://docs.zendframework.com/zend-router/';
}
if ($this->template instanceof PlatesRenderer) {
$data['templateName'] = 'Plates';
$data['templateDocs'] = 'http://platesphp.com/';
} elseif ($this->template instanceof TwigRenderer) {
$data['templateName'] = 'Twig';
$data['templateDocs'] = 'http://twig.sensiolabs.org/documentation';
} elseif ($this->template instanceof ZendViewRenderer) {
$data['templateName'] = 'Zend View';
$data['templateDocs'] = 'https://docs.zendframework.com/zend-view/';
}
return new HtmlResponse($this->template->render('app::home-page', $data));
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Action;
use Interop\Container\ContainerInterface;
use Zend\Expressive\Router\RouterInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
class HomePageFactory
{
public function __invoke(ContainerInterface $container)
{
$router = $container->get(RouterInterface::class);
$template = $container->has(TemplateRendererInterface::class)
? $container->get(TemplateRendererInterface::class)
: null;
return new HomePageAction($router, $template);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Action;
use App\Service\DataCollectorService;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\JsonResponse;
class KanbanAction implements ServerMiddlewareInterface
{
private $dataCollector;
public function __construct(DataCollectorService $dataCollectorService)
{
$this->dataCollector = $dataCollectorService;
}
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$kanbanResult = $this->dataCollector->getKanbanBoard();
return new JsonResponse($kanbanResult);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Action;
use App\Service\DataCollectorService;
use Interop\Container\ContainerInterface;
class KanbanFactory
{
public function __invoke(ContainerInterface $container)
{
$dataCollectorService = $container->get(DataCollectorService::class);
return new KanbanAction($dataCollectorService);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Action;
use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface as ServerMiddlewareInterface;
use Zend\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ServerRequestInterface;
class PingAction implements ServerMiddlewareInterface
{
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
return new JsonResponse(['ack' => time()]);
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App;
use Interop\Container\ContainerInterface;
use Zend\Cache\Storage\Adapter\Filesystem as FilesytemCache;
/**
* The configuration provider for the App module
*
* @see https://docs.zendframework.com/zend-component-installer/
*/
class ConfigProvider
{
/**
* Returns the configuration array
*
* To add a bit of a structure, each section is defined in a separate
* method which returns an array with its configuration.
*
* @return array
*/
public function __invoke()
{
return [
'dependencies' => $this->getDependencies(),
'templates' => $this->getTemplates(),
];
}
/**
* Returns the container dependencies
*
* @return array
*/
public function getDependencies()
{
return [
'invokables' => [
Action\PingAction::class => Action\PingAction::class,
],
'factories' => [
Action\AvatarAction::class => Action\AvatarFactory::class,
Action\HomePageAction::class => Action\HomePageFactory::class,
Action\KanbanAction::class => Action\KanbanFactory::class,
Service\AvatarService::class => Service\AvatarServiceFactory::class,
Service\DataCollectorService::class => Service\DataCollectorServiceFactory::class,
'service.avatarCache' => function(ContainerInterface $container) {
$cache = new FilesytemCache();
$cache->getOptions()
->setFromArray([
'ttl' => 28800,
'cache_dir' => 'data/cache',
]);
return $cache;
},
],
];
}
/**
* Returns the templates configuration
*
* @return array
*/
public function getTemplates()
{
return [
'paths' => [
'app' => ['templates/app'],
'error' => ['templates/error'],
'layout' => ['templates/layout'],
],
];
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace App\Entity;
class JiraAssignee implements \JsonSerializable
{
/**
* @var string
*/
private $signum;
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $email;
/**
* @var string
*/
private $avatar;
/**
* @var bool
*/
private $active = false;
/**
* @return string
*/
public function getSignum(): string
{
return $this->signum;
}
/**
* @param string $signum
* @return JiraAssignee
*/
public function setSignum(string $signum): JiraAssignee
{
$this->signum = $signum;
return $this;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
* @return JiraAssignee
*/
public function setName(string $name): JiraAssignee
{
$this->name = $name;
return $this;
}
/**
* @return string
*/
public function getEmail(): string
{
return $this->email;
}
/**
* @param string $email
* @return JiraAssignee
*/
public function setEmail(string $email): JiraAssignee
{
$this->email = $email;
return $this;
}
/**
* @return string
*/
public function getAvatar(): string
{
return $this->avatar;
}
/**
* @param string $avatar
* @return JiraAssignee
*/
public function setAvatar(string $avatar): JiraAssignee
{
$this->avatar = $avatar;
return $this;
}
/**
* @return bool
*/
public function isActive(): bool
{
return $this->active;
}
/**
* @param bool $active
* @return JiraAssignee
*/
public function setActive(bool $active): JiraAssignee
{
$this->active = $active;
return $this;
}
/**
* @return array
*/
function jsonSerialize()
{
return [
'signum' => $this->getSignum(),
'name' => $this->getName(),
'email' => $this->getEmail(),
'avatar' => $this->getAvatar(),
'active' => $this->isActive(),
];
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace App\Entity;
class JiraIssueType implements \JsonSerializable
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $description;
/**
* @var string
*/
private $icon;
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
* @return JiraIssueType
*/
public function setName(string $name): JiraIssueType
{
$this->name = $name;
return $this;
}
/**
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* @param string $description
* @return JiraIssueType
*/
public function setDescription(string $description): JiraIssueType
{
$this->description = $description;
return $this;
}
/**
* @return string
*/
public function getIcon(): string
{
return $this->icon;
}
/**
* @param string $icon
* @return JiraIssueType
*/
public function setIcon(string $icon): JiraIssueType
{
$this->icon = $icon;
return $this;
}
/**
* @return array
*/
function jsonSerialize()
{
return [
'name' => $this->getName(),
'description' => $this->getDescription(),
'icon' => $this->getIcon(),
];
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace App\Entity;
class JiraStatus implements \JsonSerializable
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $color;
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
* @return JiraStatus
*/
public function setName(string $name): JiraStatus
{
$this->name = $name;
return $this;
}
/**
* @return string
*/
public function getColor(): string
{
return $this->color;
}
/**
* @param string $color
* @return JiraStatus
*/
public function setColor(string $color): JiraStatus
{
$this->color = $color;
return $this;
}
/**
* @return array
*/
function jsonSerialize()
{
return [
'name' => $this->getName(),
'color' => $this->getColor(),
];
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class KanbanBoard implements \JsonSerializable
{
/**
* @var KanbanEntry[]|ArrayCollection
*/
private $kanbanEntries;
public function __construct()
{
$this->kanbanEntries = new ArrayCollection();
}
/**
* @return KanbanEntry[]|ArrayCollection
*/
public function getKanbanEntries(): ArrayCollection
{
return $this->kanbanEntries;
}
/**
* @param KanbanEntry[]|ArrayCollection $kanbanEntries
* @return KanbanBoard
*/
public function setKanbanEntries(ArrayCollection $kanbanEntries): KanbanBoard
{
$this->kanbanEntries = $kanbanEntries;
return $this;
}
/**
* @param KanbanEntry $kanbanEntry
* @return KanbanBoard
*/
public function addKanbanEntry(KanbanEntry $kanbanEntry): KanbanBoard
{
if(!$this->kanbanEntries->contains($kanbanEntry)) {
$this->kanbanEntries->add($kanbanEntry);
}
return $this;
}
/**
* @param KanbanEntry $kanbanEntry
* @return KanbanBoard
*/
public function removeKanbanEntry(KanbanEntry $kanbanEntry): KanbanBoard
{
if($this->kanbanEntries->contains($kanbanEntry)) {
$this->kanbanEntries->removeElement($kanbanEntry);
}
return $this;
}
/**
* @return array
*/
function jsonSerialize()
{
return $this->kanbanEntries->getValues();
}
}

View File

@ -0,0 +1,444 @@
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class KanbanEntry implements \JsonSerializable
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $key;
// 'fields' below
/**
* @var string
*/
private $summary;
/**
* @var JiraIssueType
*/
private $issueType;
/**
* @var JiraStatus
*/
private $status;
/**
* @var JiraAssignee
*/
private $assignee;
/**
* JIRA: customfield_11226
* @var int
*/
private $prio;
/**
* JIRA: customfield_11225
* @var string[]|ArrayCollection
*/
private $functionalAreas;
/**
* JIRA: customfield_10010
* @var string
*/
private $externalId;
/**
* JIRA: customfield_10850
* @var string
*/
private $externalLink;
/**
* JIRA: customfield_10840
* @var string
*/
private $project;
/**
* JIRA: customfield_10844
* @var string
*/
private $mhwebStatus;
/**
* JIRA: customfield_10847
* @var string
*/
private $mhwebHot;
/**
* JIRA: customfield_10849
* @var bool
*/
private $mhwebExternal = false;
/**
* JIRA: customfield_10904
* @var string
*/
private $team;
/**
* JIRA: customfield_11692
* @var string
*/
private $answerCode;
/**
* KanbanEntry constructor.
*/
public function __construct()
{
$this->functionalAreas = new ArrayCollection();
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @param int $id
* @return KanbanEntry
*/
public function setId(int $id): KanbanEntry
{
$this->id = $id;
return $this;
}
/**
* @return string
*/
public function getKey(): string
{
return $this->key;
}
/**
* @param string $key
* @return KanbanEntry
*/
public function setKey(string $key): KanbanEntry
{
$this->key = $key;
return $this;
}
/**
* @return string
*/
public function getSummary(): string
{
return $this->summary;
}
/**
* @param string $summary
* @return KanbanEntry
*/
public function setSummary(string $summary): KanbanEntry
{
$this->summary = $summary;
return $this;
}
/**
* @return JiraIssueType
*/
public function getIssueType(): ?JiraIssueType
{
return $this->issueType;
}
/**
* @param JiraIssueType $issueType
* @return KanbanEntry
*/
public function setIssueType(?JiraIssueType $issueType): KanbanEntry
{
$this->issueType = $issueType;
return $this;
}
/**
* @return JiraStatus
*/
public function getStatus(): ?JiraStatus
{
return $this->status;
}
/**
* @param JiraStatus $status
* @return KanbanEntry
*/
public function setStatus(?JiraStatus $status): KanbanEntry
{
$this->status = $status;
return $this;
}
/**
* @return JiraAssignee
*/
public function getAssignee(): ?JiraAssignee
{
return $this->assignee;
}
/**
* @param JiraAssignee $assignee
* @return KanbanEntry
*/
public function setAssignee(?JiraAssignee $assignee): KanbanEntry
{
$this->assignee = $assignee;
return $this;
}
/**
* @return int
*/
public function getPrio(): ?int
{
return $this->prio;
}
/**
* @param int $prio
* @return KanbanEntry
*/
public function setPrio(?int $prio)
{
$this->prio = $prio;
return $this;
}
/**
* @return string[]|ArrayCollection
*/
public function getFunctionalAreas(): ?ArrayCollection
{
return $this->functionalAreas;
}
/**
* @param string[]|ArrayCollection $functionalAreas
* @return KanbanEntry
*/
public function setFunctionalAreas(ArrayCollection $functionalAreas): KanbanEntry
{
$this->functionalAreas = $functionalAreas;
return $this;
}
/**
* @param string $functionalArea
* @return KanbanEntry
*/
public function addFunctionalArea(string $functionalArea): KanbanEntry
{
if(!$this->functionalAreas->contains($functionalArea)) {
$this->functionalAreas->add($functionalArea);
}
return $this;
}
/**
* @param string $functionalArea
* @return KanbanEntry
*/
public function removeFunctionalArea(string $functionalArea): KanbanEntry
{
if($this->functionalAreas->contains($functionalArea)) {
$this->functionalAreas->removeElement($functionalArea);
}
return $this;
}
/**
* @return string
*/
public function getExternalId(): ?string
{
return $this->externalId;
}
/**
* @param string $externalId
* @return KanbanEntry
*/
public function setExternalId(?string $externalId): KanbanEntry
{
$this->externalId = $externalId;
return $this;
}
/**
* @return string
*/
public function getExternalLink(): ?string
{
return $this->externalLink;
}
/**
* @param string $externalLink
* @return KanbanEntry
*/
public function setExternalLink(?string $externalLink): KanbanEntry
{
$this->externalLink = $externalLink;
return $this;
}
/**
* @return string
*/
public function getProject(): ?string
{
return $this->project;
}
/**
* @param string $project
* @return KanbanEntry
*/
public function setProject(?string $project): KanbanEntry
{
$this->project = $project;
return $this;
}
/**
* @return string
*/
public function getMhwebStatus(): ?string
{
return $this->mhwebStatus;
}
/**
* @param string $mhwebStatus
* @return KanbanEntry
*/
public function setMhwebStatus(?string $mhwebStatus): KanbanEntry
{
$this->mhwebStatus = $mhwebStatus;
return $this;
}
/**
* @return bool
*/
public function getMhwebHot(): ?bool
{
return $this->mhwebHot;
}
/**
* @param bool $mhwebHot
* @return KanbanEntry
*/
public function setMhwebHot(?bool $mhwebHot): KanbanEntry
{
$this->mhwebHot = $mhwebHot;
return $this;
}
/**
* @return bool
*/
public function getMhwebExternal(): ?bool
{
return $this->mhwebExternal;
}
/**
* @param bool $mhwebExternal
* @return KanbanEntry
*/
public function setMhwebExternal(?bool $mhwebExternal): KanbanEntry
{
$this->mhwebExternal = $mhwebExternal;
return $this;
}
/**
* @return string
*/
public function getTeam(): ?string
{
return $this->team;
}
/**
* @param string $team
* @return KanbanEntry
*/
public function setTeam(?string $team): KanbanEntry
{
$this->team = $team;
return $this;
}
/**
* @return string
*/
public function getAnswerCode(): ?string
{
return $this->answerCode;
}
/**
* @param string $answerCode
* @return KanbanEntry
*/
public function setAnswerCode(?string $answerCode): KanbanEntry
{
$this->answerCode = $answerCode;
return $this;
}
/**
* @return array
*/
function jsonSerialize()
{
return [
'id' => $this->getId(),
'key' => $this->getKey(),
'summary' => $this->getSummary(),
'issueType' => $this->getIssueType(),
'status' => $this->getStatus(),
'assignee' => $this->getAssignee(),
'prio' => $this->getPrio(),
'functionalArea' => $this->getFunctionalAreas()->getValues(),
'externalId' => $this->getExternalId(),
'externalLink' => $this->getExternalLink(),
'project' => $this->getProject(),
'mhwebStatus' => $this->getMhwebStatus(),
'mhwebHot' => $this->getMhwebHot(),
'mhwebExternal' => $this->getMhwebExternal(),
'team' => $this->getTeam(),
'answerCode' => $this->getAnswerCode(),
];
}
}

View File

@ -0,0 +1,86 @@
<?php
namespace App\Service;
use Zend\Cache\Storage\StorageInterface;
use Zend\Config\Config;
use Zend\Expressive\Router\RouterInterface;
use Zend\Http\Client;
class AvatarService
{
/**
* @var Config
*/
private $config;
/**
* @var Client
*/
private $httpClient;
/**
* @var RouterInterface
*/
private $router;
/**
* @var StorageInterface
*/
private $cache;
/**
* JiraClientService constructor.
* @param Client $client
* @param Config $config
* @param RouterInterface $router
*/
public function __construct(Client $client, Config $config, RouterInterface $router, StorageInterface $cache)
{
$this->httpClient = $client;
$this->config = $config;
$this->router = $router;
$this->cache = $cache;
}
/**
* @param string $jiraAvatarUrl
* @param string $signum
* @return string
*/
public function getJiraAvatarUrl(string $jiraAvatarUrl, string $signum): string
{
if (!$this->cache->hasItem($signum)) {
$user = $this->config->get('jira.user');
$password = $this->config->get('jira.password');
$response = $this->httpClient
->setAuth($user, $password)
->setUri($jiraAvatarUrl)
->send();
if (!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad JIRA result", $response->getStatusCode());
}
$this->cache->setItem($signum, $response->getBody());
}
return $this->router->generateUri('user.avatar', [
'signum' => $signum,
]);
}
/**
* @param string $signum
* @return string
*/
public function getAvatarData(string $signum): string
{
if (!$this->cache->hasItem($signum)) {
throw new \UnexpectedValueException("Missing avatar", 404);
}
return $this->cache->getItem($signum);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Service;
use Interop\Container\ContainerInterface;
use Zend\Config\Config;
use Zend\Expressive\Router\RouterInterface;
use Zend\Http\Client;
class AvatarServiceFactory
{
public function __invoke(ContainerInterface $container)
{
$router = $container->get(RouterInterface::class);
$configArray = $container->get('config');
$httpClient = new Client();
$httpClient->getAdapter()->setOptions([
'timeout' => 60,
]);
$cache = $container->get('service.avatarCache');
$config = new Config($configArray['app.config']);
return new AvatarService($httpClient, $config, $router, $cache);
}
}

View File

@ -0,0 +1,172 @@
<?php
namespace App\Service;
use App\Entity\JiraAssignee;
use App\Entity\JiraIssueType;
use App\Entity\JiraStatus;
use App\Entity\KanbanBoard;
use App\Entity\KanbanEntry;
use Zend\Config\Config;
use Zend\Http\Client;
use Zend\Json\Decoder;
use Zend\Json\Json;
class DataCollectorService
{
/**
* @var Config
*/
private $config;
/**
* @var Client
*/
private $httpClient;
/**
* @var AvatarService
*/
private $avatarService;
/**
* JiraClientService constructor.
* @param Client $client
* @param Config $config
*/
public function __construct(Client $client, Config $config, AvatarService $avatarService)
{
$this->avatarService = $avatarService;
$this->httpClient = $client;
$this->config = $config;
}
/**
* @return KanbanBoard
*/
public function getKanbanBoard(): KanbanBoard
{
$user = $this->config->get('jira.user');
$password = $this->config->get('jira.password');
/** @var Config $kanbanBoardUriParams */
$kanbanBoardUriParams = $this->config->get('url.jiraKanbanBoard');
$kanbanBoardUri = sprintf(
$kanbanBoardUriParams['baseUrl'],
$kanbanBoardUriParams['filterId'],
implode(",", $kanbanBoardUriParams['fields']->toArray())
);
$response = $this->httpClient
->setAuth($user, $password)
->setUri($kanbanBoardUri)
->send();
if(!$response->isSuccess()) {
throw new \UnexpectedValueException("Bad JIRA result", $response->getStatusCode());
}
$parsedJsonData = Decoder::decode($response->getBody(), Json::TYPE_ARRAY);
return $this->hydrateKanbanBoard($parsedJsonData);
}
/**
* @param $parsedJsonData
* @return KanbanBoard
* @todo check if avatar has to be locally cached
*/
private function hydrateKanbanBoard($parsedJsonData): KanbanBoard
{
$kanbanBoard = new KanbanBoard();
foreach($parsedJsonData['issues'] as $jsonIssue) {
$kanbanEntry = new KanbanEntry();
$kanbanEntry->setId(intval($jsonIssue['id']))
->setKey($jsonIssue['key'])
->setSummary($jsonIssue['fields']['summary'])
->setExternalLink($jsonIssue['fields']['customfield_10850'])
->setMhwebStatus($jsonIssue['fields']['customfield_10844'])
->setAnswerCode($jsonIssue['fields']['customfield_11692'])
;
// externalId : customfield_10010
if(isset($jsonIssue['fields']['customfield_10010'])) {
$kanbanEntry->setPrio(intval($jsonIssue['fields']['customfield_10010']));
}
// prio : customfield_10840
if(isset($jsonIssue['fields']['customfield_11226'])) {
$kanbanEntry->setPrio($jsonIssue['fields']['customfield_11226']);
}
// functional area : customfield_11225
if(isset($jsonIssue['fields']['customfield_11225'])) {
foreach ($jsonIssue['fields']['customfield_11225'] as $functionalArea) {
$kanbanEntry->addFunctionalArea($functionalArea['value']);
}
}
// project : customfield_10840
if(isset($jsonIssue['fields']['customfield_10840'])) {
$kanbanEntry->setProject($jsonIssue['fields']['customfield_10840']['value']);
}
// mhweb hot : customfield_10847
if(isset($jsonIssue['fields']['customfield_10847'])) {
$boolVal = $jsonIssue['fields']['customfield_10847'][0]['value'] == 'yes';
$kanbanEntry->setMhwebHot($boolVal);
}
// mhweb external : customfield_10849
if(isset($jsonIssue['fields']['customfield_10849'])) {
$boolVal = $jsonIssue['fields']['customfield_10849'][0]['value'] == 'yes';
$kanbanEntry->setMhwebExternal($boolVal);
}
// team : customfield_10904
if(isset($jsonIssue['fields']['customfield_10904'])) {
$kanbanEntry->setTeam($jsonIssue['fields']['customfield_10904']['value']);
}
// jira status
$jiraStatus = new JiraStatus();
$jiraStatus->setName($jsonIssue['fields']['status']['name'])
->setColor($jsonIssue['fields']['status']['statusCategory']['colorName']);
$kanbanEntry->setStatus($jiraStatus);
// assignee
if($jsonIssue['fields']['assignee']) {
$avatarUrl = $this->avatarService->getJiraAvatarUrl(
$jsonIssue['fields']['assignee']['avatarUrls']['48x48'],
$jsonIssue['fields']['assignee']['key']
);
$jiraAssignee = new JiraAssignee();
$jiraAssignee->setName($jsonIssue['fields']['assignee']['displayName'])
->setSignum($jsonIssue['fields']['assignee']['key'])
->setEmail(strtolower($jsonIssue['fields']['assignee']['emailAddress']))
->setAvatar($avatarUrl)
->setActive($jsonIssue['fields']['assignee']['active']);
$kanbanEntry->setAssignee($jiraAssignee);
unset($jiraAssignee);
}
// issue type
if($jsonIssue['fields']['issuetype']) {
$jiraIssueType = new JiraIssueType();
$jiraIssueType->setName($jsonIssue['fields']['issuetype']['name'])
->setDescription($jsonIssue['fields']['issuetype']['description'])
->setIcon($jsonIssue['fields']['issuetype']['iconUrl']);
$kanbanEntry->setIssueType($jiraIssueType);
unset($jiraIssueType);
}
$kanbanBoard->addKanbanEntry($kanbanEntry);
unset($kanbanEntry);
}
return $kanbanBoard;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Service;
use Interop\Container\ContainerInterface;
use Zend\Config\Config;
use Zend\Http\Client;
class DataCollectorServiceFactory
{
public function __invoke(ContainerInterface $container)
{
$configArray = $container->get('config');
$httpClient = new Client();
$httpClient->getAdapter()->setOptions([
'timeout' => 60,
]);
$config = new Config($configArray['app.config']);
$avatarService = $container->get(AvatarService::class);
return new DataCollectorService($httpClient, $config, $avatarService);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace AppTest\Action;
use App\Action\HomePageAction;
use Interop\Http\ServerMiddleware\DelegateInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Expressive\Router\RouterInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
class HomePageActionTest extends TestCase
{
/** @var RouterInterface */
protected $router;
protected function setUp()
{
$this->router = $this->prophesize(RouterInterface::class);
}
public function testReturnsJsonResponseWhenNoTemplateRendererProvided()
{
$homePage = new HomePageAction($this->router->reveal(), null);
$response = $homePage->process(
$this->prophesize(ServerRequestInterface::class)->reveal(),
$this->prophesize(DelegateInterface::class)->reveal()
);
$this->assertInstanceOf(JsonResponse::class, $response);
}
public function testReturnsHtmlResponseWhenTemplateRendererProvided()
{
$renderer = $this->prophesize(TemplateRendererInterface::class);
$renderer
->render('app::home-page', Argument::type('array'))
->willReturn('');
$homePage = new HomePageAction($this->router->reveal(), $renderer->reveal());
$response = $homePage->process(
$this->prophesize(ServerRequestInterface::class)->reveal(),
$this->prophesize(DelegateInterface::class)->reveal()
);
$this->assertInstanceOf(HtmlResponse::class, $response);
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace AppTest\Action;
use App\Action\HomePageAction;
use App\Action\HomePageFactory;
use Interop\Container\ContainerInterface;
use PHPUnit\Framework\TestCase;
use Zend\Expressive\Router\RouterInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
class HomePageFactoryTest extends TestCase
{
/** @var ContainerInterface */
protected $container;
protected function setUp()
{
$this->container = $this->prophesize(ContainerInterface::class);
$router = $this->prophesize(RouterInterface::class);
$this->container->get(RouterInterface::class)->willReturn($router);
}
public function testFactoryWithoutTemplate()
{
$factory = new HomePageFactory();
$this->container->has(TemplateRendererInterface::class)->willReturn(false);
$this->assertInstanceOf(HomePageFactory::class, $factory);
$homePage = $factory($this->container->reveal());
$this->assertInstanceOf(HomePageAction::class, $homePage);
}
public function testFactoryWithTemplate()
{
$factory = new HomePageFactory();
$this->container->has(TemplateRendererInterface::class)->willReturn(true);
$this->container
->get(TemplateRendererInterface::class)
->willReturn($this->prophesize(TemplateRendererInterface::class));
$this->assertInstanceOf(HomePageFactory::class, $factory);
$homePage = $factory($this->container->reveal());
$this->assertInstanceOf(HomePageAction::class, $homePage);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace AppTest\Action;
use App\Action\PingAction;
use Interop\Http\ServerMiddleware\DelegateInterface;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\JsonResponse;
class PingActionTest extends TestCase
{
public function testResponse()
{
$pingAction = new PingAction();
$response = $pingAction->process(
$this->prophesize(ServerRequestInterface::class)->reveal(),
$this->prophesize(DelegateInterface::class)->reveal()
);
$json = json_decode((string) $response->getBody());
$this->assertInstanceOf(JsonResponse::class, $response);
$this->assertTrue(isset($json->ack));
}
}