Initial service bundle (#1)
This commit is contained in:
parent
9309a2d122
commit
d44da0d531
34
.github/workflows/ci.yml
vendored
Normal file
34
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
tags-ignore:
|
||||||
|
- '*.*'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version: ['7.3', '7.4', '8.0']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup PHP ${{ matrix.php-version }}
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-version }}
|
||||||
|
coverage: pcov
|
||||||
|
- name: Composer cache
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ env.HOME }}/.composer/cache
|
||||||
|
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer install -o
|
||||||
|
- name: Run tests
|
||||||
|
run: composer run tests
|
||||||
|
- name: Coverage
|
||||||
|
run: bash <(curl -s https://codecov.io/bash)
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -30,6 +30,7 @@
|
|||||||
!bin/console
|
!bin/console
|
||||||
!bin/symfony_requirements
|
!bin/symfony_requirements
|
||||||
/vendor/
|
/vendor/
|
||||||
|
composer.lock
|
||||||
|
|
||||||
# Assets and user uploads
|
# Assets and user uploads
|
||||||
/web/bundles/
|
/web/bundles/
|
||||||
@ -38,6 +39,8 @@
|
|||||||
# PHPUnit
|
# PHPUnit
|
||||||
/app/phpunit.xml
|
/app/phpunit.xml
|
||||||
/phpunit.xml
|
/phpunit.xml
|
||||||
|
.phpunit.result.cache
|
||||||
|
test-report.xml
|
||||||
|
|
||||||
# Build data
|
# Build data
|
||||||
/build/
|
/build/
|
||||||
@ -50,3 +53,6 @@
|
|||||||
|
|
||||||
# Embedded web-server pid file
|
# Embedded web-server pid file
|
||||||
/.web-server-pid
|
/.web-server-pid
|
||||||
|
|
||||||
|
.idea
|
||||||
|
coverage.xml
|
||||||
|
43
ArgumentResolver/AbstractValueResolver.php
Normal file
43
ArgumentResolver/AbstractValueResolver.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\ArgumentResolver;
|
||||||
|
|
||||||
|
use RetailCrm\ServiceBundle\Exceptions\InvalidRequestArgumentException;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AbstractValueResolver
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\ArgumentResolver
|
||||||
|
*/
|
||||||
|
abstract class AbstractValueResolver
|
||||||
|
{
|
||||||
|
protected $validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractValueResolver constructor.
|
||||||
|
*
|
||||||
|
* @param ValidatorInterface $validator
|
||||||
|
*/
|
||||||
|
public function __construct(ValidatorInterface $validator)
|
||||||
|
{
|
||||||
|
$this->validator = $validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object $data
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function validate(object $data): void
|
||||||
|
{
|
||||||
|
$errors = $this->validator->validate($data);
|
||||||
|
if (0 !== count($errors)) {
|
||||||
|
throw new InvalidRequestArgumentException(
|
||||||
|
sprintf("Invalid request parameter %s", \get_class($data)),
|
||||||
|
400,
|
||||||
|
$errors
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
ArgumentResolver/CallbackValueResolver.php
Normal file
86
ArgumentResolver/CallbackValueResolver.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\ArgumentResolver;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
|
||||||
|
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
use Generator;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CallbackValueResolver
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\ArgumentResolver
|
||||||
|
*/
|
||||||
|
class CallbackValueResolver extends AbstractValueResolver implements ArgumentValueResolverInterface
|
||||||
|
{
|
||||||
|
private $serializer;
|
||||||
|
private $requestSchema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CallbackValueResolver constructor.
|
||||||
|
*
|
||||||
|
* @param SerializerInterface $serializer
|
||||||
|
* @param ValidatorInterface $validator
|
||||||
|
* @param array $requestSchema
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
SerializerInterface $serializer,
|
||||||
|
ValidatorInterface $validator,
|
||||||
|
array $requestSchema
|
||||||
|
) {
|
||||||
|
parent::__construct($validator);
|
||||||
|
|
||||||
|
$this->serializer = $serializer;
|
||||||
|
$this->requestSchema = $requestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function supports(Request $request, ArgumentMetadata $argument): bool
|
||||||
|
{
|
||||||
|
if (empty($this->requestSchema) || $request->getMethod() !== Request::METHOD_POST) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null !== $this->search($request, $argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function resolve(Request $request, ArgumentMetadata $argument): Generator
|
||||||
|
{
|
||||||
|
$parameter = $this->search($request, $argument);
|
||||||
|
$data = $this->serializer->deserialize($request->request->get($parameter), $argument->getType(), 'json');
|
||||||
|
$this->validate($data);
|
||||||
|
|
||||||
|
yield $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
* @param ArgumentMetadata $argument
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
private function search(Request $request, ArgumentMetadata $argument): ?string
|
||||||
|
{
|
||||||
|
foreach ($this->requestSchema as $callback) {
|
||||||
|
if ($argument->getType() !== $callback['type']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($callback['params'] as $param) {
|
||||||
|
if ($request->request->has($param)) {
|
||||||
|
return $param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
93
ArgumentResolver/ClientValueResolver.php
Normal file
93
ArgumentResolver/ClientValueResolver.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\ArgumentResolver;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
|
||||||
|
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||||
|
use Symfony\Component\Serializer\Exception\ExceptionInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ClientValueResolver
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\ArgumentResolver
|
||||||
|
*/
|
||||||
|
class ClientValueResolver extends AbstractValueResolver implements ArgumentValueResolverInterface
|
||||||
|
{
|
||||||
|
private $serializer;
|
||||||
|
private $denormalizer;
|
||||||
|
private $requestSchema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClientValueResolver constructor.
|
||||||
|
*
|
||||||
|
* @param ValidatorInterface $validator
|
||||||
|
* @param SerializerInterface $serializer
|
||||||
|
* @param DenormalizerInterface $denormalizer
|
||||||
|
* @param array $requestSchema
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
ValidatorInterface $validator,
|
||||||
|
SerializerInterface $serializer,
|
||||||
|
DenormalizerInterface $denormalizer,
|
||||||
|
array $requestSchema
|
||||||
|
) {
|
||||||
|
parent::__construct($validator);
|
||||||
|
|
||||||
|
$this->serializer = $serializer;
|
||||||
|
$this->denormalizer = $denormalizer;
|
||||||
|
$this->requestSchema = $requestSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function supports(Request $request, ArgumentMetadata $argument): bool
|
||||||
|
{
|
||||||
|
return in_array($argument->getType(), $this->requestSchema, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function resolve(Request $request, ArgumentMetadata $argument): Generator
|
||||||
|
{
|
||||||
|
if (Request::METHOD_GET === $request->getMethod()) {
|
||||||
|
$dto = $this->handleGetData($request->query->all(), $argument->getType());
|
||||||
|
} else {
|
||||||
|
$dto = $this->handlePostData($request->getContent(), $argument->getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->validate($dto);
|
||||||
|
|
||||||
|
yield $dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $data
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
|
* @return object
|
||||||
|
*
|
||||||
|
* @throws ExceptionInterface
|
||||||
|
*/
|
||||||
|
private function handleGetData(array $data, string $type): object
|
||||||
|
{
|
||||||
|
return $this->denormalizer->denormalize($data, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $data
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
private function handlePostData(string $data, string $type): object
|
||||||
|
{
|
||||||
|
return $this->serializer->deserialize($data, $type, 'json');
|
||||||
|
}
|
||||||
|
}
|
46
DependencyInjection/Configuration.php
Normal file
46
DependencyInjection/Configuration.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\DependencyInjection;
|
||||||
|
|
||||||
|
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||||
|
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Configuration
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\DependencyInjection
|
||||||
|
*/
|
||||||
|
class Configuration implements ConfigurationInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function getConfigTreeBuilder(): TreeBuilder
|
||||||
|
{
|
||||||
|
$treeBuilder = new TreeBuilder('retail_crm_service');
|
||||||
|
$rootNode = $treeBuilder->getRootNode();
|
||||||
|
|
||||||
|
$rootNode
|
||||||
|
->children()
|
||||||
|
->arrayNode('request_schema')
|
||||||
|
->children()
|
||||||
|
->arrayNode('callback')
|
||||||
|
->arrayPrototype()
|
||||||
|
->children()
|
||||||
|
->scalarNode('type')->isRequired()->end()
|
||||||
|
->arrayNode('params')
|
||||||
|
->isRequired()->scalarPrototype()->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
->arrayNode('client')
|
||||||
|
->scalarPrototype()->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
->end();
|
||||||
|
|
||||||
|
return $treeBuilder;
|
||||||
|
}
|
||||||
|
}
|
63
DependencyInjection/RetailCrmServiceExtension.php
Normal file
63
DependencyInjection/RetailCrmServiceExtension.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\DependencyInjection;
|
||||||
|
|
||||||
|
use RetailCrm\ServiceBundle\ArgumentResolver\CallbackValueResolver;
|
||||||
|
use RetailCrm\ServiceBundle\ArgumentResolver\ClientValueResolver;
|
||||||
|
use RetailCrm\ServiceBundle\Response\ErrorJsonResponseFactory;
|
||||||
|
use RetailCrm\ServiceBundle\Security\CallbackClientAuthenticator;
|
||||||
|
use RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RetailCrmServiceExtension
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\DependencyInjection
|
||||||
|
*/
|
||||||
|
class RetailCrmServiceExtension extends Extension
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array $configs
|
||||||
|
* @param ContainerBuilder $container
|
||||||
|
*/
|
||||||
|
public function load(array $configs, ContainerBuilder $container): void
|
||||||
|
{
|
||||||
|
$configuration = $this->getConfiguration($configs, $container);
|
||||||
|
$config = $this->processConfiguration($configuration, $configs);
|
||||||
|
|
||||||
|
$container->setParameter(
|
||||||
|
'retail_crm_service.request_schema.callback',
|
||||||
|
$config['request_schema']['callback']
|
||||||
|
);
|
||||||
|
|
||||||
|
$container->setParameter(
|
||||||
|
'retail_crm_service.request_schema.client',
|
||||||
|
$config['request_schema']['client']
|
||||||
|
);
|
||||||
|
|
||||||
|
$container
|
||||||
|
->register(CallbackValueResolver::class)
|
||||||
|
->setArgument('$requestSchema', '%retail_crm_service.request_schema.callback%')
|
||||||
|
->addTag('controller.argument_value_resolver', ['priority' => 50])
|
||||||
|
->setAutowired(true);
|
||||||
|
|
||||||
|
$container
|
||||||
|
->register(ClientValueResolver::class)
|
||||||
|
->setArgument('$requestSchema', '%retail_crm_service.request_schema.client%')
|
||||||
|
->addTag('controller.argument_value_resolver', ['priority' => 50])
|
||||||
|
->setAutowired(true);
|
||||||
|
|
||||||
|
$container
|
||||||
|
->register(ErrorJsonResponseFactory::class)
|
||||||
|
->setAutowired(true);
|
||||||
|
|
||||||
|
$container
|
||||||
|
->register(CallbackClientAuthenticator::class)
|
||||||
|
->setAutowired(true);
|
||||||
|
|
||||||
|
$container
|
||||||
|
->register(FrontApiClientAuthenticator::class)
|
||||||
|
->setAutowired(true);
|
||||||
|
}
|
||||||
|
}
|
38
Exceptions/InvalidRequestArgumentException.php
Normal file
38
Exceptions/InvalidRequestArgumentException.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Exceptions;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class InvalidRequestArgumentException
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Exceptions
|
||||||
|
*/
|
||||||
|
class InvalidRequestArgumentException extends InvalidArgumentException
|
||||||
|
{
|
||||||
|
private $validateErrors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InvalidRequestArgumentException constructor.
|
||||||
|
* @param string $message
|
||||||
|
* @param int $code
|
||||||
|
* @param array $errors
|
||||||
|
* @param Throwable|null $previous
|
||||||
|
*/
|
||||||
|
public function __construct($message = "", $code = 0, iterable $errors = [], Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
|
||||||
|
$this->validateErrors = $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable
|
||||||
|
*/
|
||||||
|
public function getValidateErrors(): iterable
|
||||||
|
{
|
||||||
|
return $this->validateErrors;
|
||||||
|
}
|
||||||
|
}
|
26
Models/Error.php
Normal file
26
Models/Error.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Error
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Models
|
||||||
|
*/
|
||||||
|
class Error
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $details;
|
||||||
|
}
|
148
Resources/doc/index.md
Normal file
148
Resources/doc/index.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
## Installation
|
||||||
|
|
||||||
|
`composer require retailcrm/service-bundle`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Enable bundle in `config/bundles.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
// other bundles
|
||||||
|
RetailCrm\ServiceBundle\RetailCrmServiceBundle::class => ['all' => true]
|
||||||
|
];
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Create bundle config file in `config/packages/retail_crm_service.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
retail_crm_service:
|
||||||
|
request_schema: ~
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deserializing incoming requests
|
||||||
|
|
||||||
|
#### Callbacks (form data)
|
||||||
|
|
||||||
|
To automatically get the callback request parameter
|
||||||
|
|
||||||
|
```php
|
||||||
|
|
||||||
|
class AppController extends AbstractController
|
||||||
|
{
|
||||||
|
public function activityAction(\App\Dto\Callback\Activity $activity): Response
|
||||||
|
{
|
||||||
|
// handle activity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
add to the config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
retail_crm_service:
|
||||||
|
request_schema:
|
||||||
|
callback:
|
||||||
|
- type: App\Dto\Callback\Activity
|
||||||
|
params: ["activity"]
|
||||||
|
```
|
||||||
|
|
||||||
|
request automatically will be deserialization to $activity.
|
||||||
|
|
||||||
|
#### Body json content
|
||||||
|
|
||||||
|
```php
|
||||||
|
|
||||||
|
class AppController extends AbstractController
|
||||||
|
{
|
||||||
|
public function someAction(\App\Dto\Body $activity): Response
|
||||||
|
{
|
||||||
|
// handle activity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
add to the config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
retail_crm_service:
|
||||||
|
request_schema:
|
||||||
|
client:
|
||||||
|
- App\Dto\Body
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
Example security configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
security:
|
||||||
|
providers:
|
||||||
|
client:
|
||||||
|
entity:
|
||||||
|
class: 'App\Entity\Connection' # must implements UserInterface
|
||||||
|
property: 'clientId'
|
||||||
|
firewalls:
|
||||||
|
api:
|
||||||
|
pattern: ^/api
|
||||||
|
provider: client
|
||||||
|
anonymous: ~
|
||||||
|
lazy: true
|
||||||
|
stateless: false
|
||||||
|
guard:
|
||||||
|
authenticators:
|
||||||
|
- RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator
|
||||||
|
callback:
|
||||||
|
pattern: ^/callback
|
||||||
|
provider: client
|
||||||
|
anonymous: ~
|
||||||
|
lazy: true
|
||||||
|
stateless: true
|
||||||
|
guard:
|
||||||
|
authenticators:
|
||||||
|
- RetailCrm\ServiceBundle\Security\CallbackClientAuthenticator
|
||||||
|
main:
|
||||||
|
anonymous: true
|
||||||
|
lazy: true
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } # login for programmatically authentication user
|
||||||
|
- { path: ^/api, roles: ROLE_USER }
|
||||||
|
- { path: ^/callback, roles: ROLE_USER }
|
||||||
|
```
|
||||||
|
|
||||||
|
To authenticate the user after creating it, you can use the following code
|
||||||
|
|
||||||
|
```php
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
|
||||||
|
use RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class AppController extends AbstractController
|
||||||
|
{
|
||||||
|
public function someAction(
|
||||||
|
Request $request,
|
||||||
|
GuardAuthenticatorHandler $guardAuthenticatorHandler,
|
||||||
|
FrontApiClientAuthenticator $frontApiClientAuthenticator,
|
||||||
|
ConnectionManager $manager
|
||||||
|
): Response {
|
||||||
|
$user = $manager->getUser(); // getting user
|
||||||
|
|
||||||
|
$guardAuthenticatorHandler->authenticateUserAndHandleSuccess(
|
||||||
|
$user,
|
||||||
|
$request,
|
||||||
|
$frontApiClientAuthenticator,
|
||||||
|
'api'
|
||||||
|
);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
44
Response/ErrorJsonResponseFactory.php
Normal file
44
Response/ErrorJsonResponseFactory.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Response;
|
||||||
|
|
||||||
|
use RetailCrm\ServiceBundle\Models\Error;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ErrorJsonResponseFactory
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Response
|
||||||
|
*/
|
||||||
|
class ErrorJsonResponseFactory
|
||||||
|
{
|
||||||
|
private $serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ErrorJsonResponseFactory constructor.
|
||||||
|
*
|
||||||
|
* @param SerializerInterface $serializer
|
||||||
|
*/
|
||||||
|
public function __construct(SerializerInterface $serializer)
|
||||||
|
{
|
||||||
|
$this->serializer = $serializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Error $error
|
||||||
|
* @param int $statusCode
|
||||||
|
* @param array $headers
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function create(Error $error, int $statusCode = Response::HTTP_BAD_REQUEST, array $headers = []): Response
|
||||||
|
{
|
||||||
|
return JsonResponse::fromJsonString(
|
||||||
|
$this->serializer->serialize($error, 'json'),
|
||||||
|
$statusCode,
|
||||||
|
$headers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
9
RetailCrmServiceBundle.php
Normal file
9
RetailCrmServiceBundle.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
|
|
||||||
|
class RetailCrmServiceBundle extends Bundle
|
||||||
|
{
|
||||||
|
}
|
89
Security/AbstractClientAuthenticator.php
Normal file
89
Security/AbstractClientAuthenticator.php
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Security;
|
||||||
|
|
||||||
|
use RetailCrm\ServiceBundle\Models\Error;
|
||||||
|
use RetailCrm\ServiceBundle\Response\ErrorJsonResponseFactory;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AbstractClientAuthenticator
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Security
|
||||||
|
*/
|
||||||
|
abstract class AbstractClientAuthenticator extends AbstractGuardAuthenticator
|
||||||
|
{
|
||||||
|
public const AUTH_FIELD = 'clientId';
|
||||||
|
|
||||||
|
private $errorResponseFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractClientAuthenticator constructor.
|
||||||
|
*
|
||||||
|
* @param ErrorJsonResponseFactory $errorResponseFactory
|
||||||
|
*/
|
||||||
|
public function __construct(ErrorJsonResponseFactory $errorResponseFactory)
|
||||||
|
{
|
||||||
|
$this->errorResponseFactory = $errorResponseFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function start(Request $request, AuthenticationException $authException = null): Response
|
||||||
|
{
|
||||||
|
$error = new Error();
|
||||||
|
$error->message = 'Authentication required';
|
||||||
|
|
||||||
|
return $this->errorResponseFactory->create($error,Response::HTTP_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function getCredentials(Request $request): string
|
||||||
|
{
|
||||||
|
return $request->get(static::AUTH_FIELD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function getUser($credentials, UserProviderInterface $userProvider): ?UserInterface
|
||||||
|
{
|
||||||
|
return $userProvider->loadUserByUsername($credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function checkCredentials($credentials, UserInterface $user): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
|
||||||
|
{
|
||||||
|
$error = new Error();
|
||||||
|
$error->message = $exception->getMessageKey();
|
||||||
|
|
||||||
|
return $this->errorResponseFactory->create($error,Response::HTTP_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
29
Security/CallbackClientAuthenticator.php
Normal file
29
Security/CallbackClientAuthenticator.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Security;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CallbackClientAuthenticator
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Security
|
||||||
|
*/
|
||||||
|
class CallbackClientAuthenticator extends AbstractClientAuthenticator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function supports(Request $request): bool
|
||||||
|
{
|
||||||
|
return $request->request->has(static::AUTH_FIELD) || $request->query->has(static::AUTH_FIELD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function supportsRememberMe(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
52
Security/FrontApiClientAuthenticator.php
Normal file
52
Security/FrontApiClientAuthenticator.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Security;
|
||||||
|
|
||||||
|
use RetailCrm\ServiceBundle\Response\ErrorJsonResponseFactory;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class FrontApiClientAuthenticator
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Security
|
||||||
|
*/
|
||||||
|
class FrontApiClientAuthenticator extends AbstractClientAuthenticator
|
||||||
|
{
|
||||||
|
private $security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FrontApiClientAuthenticator constructor.
|
||||||
|
*
|
||||||
|
* @param ErrorJsonResponseFactory $errorResponseFactory
|
||||||
|
* @param Security $security
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
ErrorJsonResponseFactory $errorResponseFactory,
|
||||||
|
Security $security
|
||||||
|
) {
|
||||||
|
parent::__construct($errorResponseFactory);
|
||||||
|
|
||||||
|
$this->security = $security;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function supports(Request $request): bool
|
||||||
|
{
|
||||||
|
if ($this->security->getUser()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->request->has(static::AUTH_FIELD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc }
|
||||||
|
*/
|
||||||
|
public function supportsRememberMe(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
113
Tests/ArgumentResolver/CallbackValueResolverTest.php
Normal file
113
Tests/ArgumentResolver/CallbackValueResolverTest.php
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Tests\ArgumentResolver;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RetailCrm\ServiceBundle\ArgumentResolver\CallbackValueResolver;
|
||||||
|
use RetailCrm\ServiceBundle\Exceptions\InvalidRequestArgumentException;
|
||||||
|
use RetailCrm\ServiceBundle\Tests\DataFixtures\RequestDto;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||||
|
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Serializer;
|
||||||
|
use Symfony\Component\Validator\Validation;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CallbackValueResolverTest
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Tests\ArgumentResolver
|
||||||
|
*/
|
||||||
|
class CallbackValueResolverTest extends TestCase
|
||||||
|
{
|
||||||
|
private $resolver;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
|
||||||
|
$this->resolver = new CallbackValueResolver(
|
||||||
|
$serializer,
|
||||||
|
Validation::createValidatorBuilder()
|
||||||
|
->enableAnnotationMapping()
|
||||||
|
->getValidator(),
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'type' => RequestDto::class,
|
||||||
|
'params' => ['request_parameter']
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupports(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', RequestDto::class, false, false, null);
|
||||||
|
$request = new Request(
|
||||||
|
[],
|
||||||
|
['request_parameter' => json_encode(['param' => 'parameter'], JSON_THROW_ON_ERROR)],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
['REQUEST_METHOD' => Request::METHOD_POST]
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->resolver->supports($request, $argument);
|
||||||
|
|
||||||
|
static::assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNotSupports(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', 'NotFoundRequestDto', false, false, null);
|
||||||
|
$request = new Request(
|
||||||
|
[],
|
||||||
|
['request_parameter' => json_encode(['param' => 'parameter'], JSON_THROW_ON_ERROR)],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
['REQUEST_METHOD' => Request::METHOD_POST]
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->resolver->supports($request, $argument);
|
||||||
|
|
||||||
|
static::assertFalse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResolve(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', RequestDto::class, false, false, null);
|
||||||
|
$request = new Request(
|
||||||
|
[],
|
||||||
|
['request_parameter' => json_encode(['param' => 'parameter'], JSON_THROW_ON_ERROR)],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
['REQUEST_METHOD' => Request::METHOD_POST]
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->resolver->resolve($request, $argument);
|
||||||
|
|
||||||
|
static::assertInstanceOf(Generator::class, $result);
|
||||||
|
static::assertInstanceOf(RequestDto::class, $result->current());
|
||||||
|
static::assertEquals('parameter', $result->current()->param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResolveFailure(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', RequestDto::class, false, false, null);
|
||||||
|
$request = new Request(
|
||||||
|
[],
|
||||||
|
['request_parameter' => json_encode(['param' => null], JSON_THROW_ON_ERROR)],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
['REQUEST_METHOD' => Request::METHOD_POST]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(InvalidRequestArgumentException::class);
|
||||||
|
|
||||||
|
$result = $this->resolver->resolve($request, $argument);
|
||||||
|
$result->current();
|
||||||
|
}
|
||||||
|
}
|
136
Tests/ArgumentResolver/ClientValueResolverTest.php
Normal file
136
Tests/ArgumentResolver/ClientValueResolverTest.php
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Tests\ArgumentResolver;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RetailCrm\ServiceBundle\ArgumentResolver\ClientValueResolver;
|
||||||
|
use RetailCrm\ServiceBundle\Exceptions\InvalidRequestArgumentException;
|
||||||
|
use RetailCrm\ServiceBundle\Tests\DataFixtures\RequestDto;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||||
|
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Serializer;
|
||||||
|
use Symfony\Component\Validator\Validation;
|
||||||
|
use Generator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ClientValueResolverTest
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Tests\ArgumentResolver
|
||||||
|
*/
|
||||||
|
class ClientValueResolverTest extends TestCase
|
||||||
|
{
|
||||||
|
private $resolver;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
$serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
|
||||||
|
$this->resolver = new ClientValueResolver(
|
||||||
|
Validation::createValidatorBuilder()
|
||||||
|
->enableAnnotationMapping()
|
||||||
|
->getValidator(),
|
||||||
|
$serializer,
|
||||||
|
$serializer,
|
||||||
|
[
|
||||||
|
RequestDto::class
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupports(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', RequestDto::class, false, false, null);
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
$result = $this->resolver->supports($request, $argument);
|
||||||
|
|
||||||
|
static::assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNotSupports(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', 'NotFoundRequestDto', false, false, null);
|
||||||
|
$request = new Request();
|
||||||
|
|
||||||
|
$result = $this->resolver->supports($request, $argument);
|
||||||
|
|
||||||
|
static::assertFalse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResolvePost(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', RequestDto::class, false, false, null);
|
||||||
|
$request = new Request(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
['REQUEST_METHOD' => Request::METHOD_POST],
|
||||||
|
json_encode(['param' => 'parameter'], JSON_THROW_ON_ERROR)
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->resolver->resolve($request, $argument);
|
||||||
|
|
||||||
|
static::assertInstanceOf(Generator::class, $result);
|
||||||
|
static::assertInstanceOf(RequestDto::class, $result->current());
|
||||||
|
static::assertEquals('parameter', $result->current()->param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResolvePostFailure(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', RequestDto::class, false, false, null);
|
||||||
|
$request = new Request(
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
['REQUEST_METHOD' => Request::METHOD_POST],
|
||||||
|
json_encode(['param' => null], JSON_THROW_ON_ERROR)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(InvalidRequestArgumentException::class);
|
||||||
|
|
||||||
|
$result = $this->resolver->resolve($request, $argument);
|
||||||
|
$result->current();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResolveGet(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', RequestDto::class, false, false, null);
|
||||||
|
$request = new Request(
|
||||||
|
['param' => 'parameter'],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->resolver->resolve($request, $argument);
|
||||||
|
|
||||||
|
static::assertInstanceOf(Generator::class, $result);
|
||||||
|
static::assertInstanceOf(RequestDto::class, $result->current());
|
||||||
|
static::assertEquals('parameter', $result->current()->param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResolveGetFailure(): void
|
||||||
|
{
|
||||||
|
$argument = new ArgumentMetadata('RequestDto', RequestDto::class, false, false, null);
|
||||||
|
$request = new Request(
|
||||||
|
['param' => null],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->expectException(InvalidRequestArgumentException::class);
|
||||||
|
|
||||||
|
$result = $this->resolver->resolve($request, $argument);
|
||||||
|
$result->current();
|
||||||
|
}
|
||||||
|
}
|
19
Tests/DataFixtures/RequestDto.php
Normal file
19
Tests/DataFixtures/RequestDto.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Tests\DataFixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RequestDto
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Tests\DataFixtures
|
||||||
|
*/
|
||||||
|
class RequestDto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @Assert\NotNull()
|
||||||
|
*/
|
||||||
|
public $param;
|
||||||
|
}
|
37
Tests/DataFixtures/User.php
Normal file
37
Tests/DataFixtures/User.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Tests\DataFixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class User
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Tests\DataFixtures
|
||||||
|
*/
|
||||||
|
class User implements UserInterface
|
||||||
|
{
|
||||||
|
public function getRoles(): array
|
||||||
|
{
|
||||||
|
return ["USER"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassword(): string
|
||||||
|
{
|
||||||
|
return "123";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSalt(): string
|
||||||
|
{
|
||||||
|
return "salt";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsername(): string
|
||||||
|
{
|
||||||
|
return "user";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eraseCredentials(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
77
Tests/DependencyInjection/ConfigurationTest.php
Normal file
77
Tests/DependencyInjection/ConfigurationTest.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Tests\DependencyInjection;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RetailCrm\ServiceBundle\DependencyInjection\Configuration;
|
||||||
|
use Symfony\Component\Config\Definition\Processor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ConfigurationTest
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Tests\DependencyInjection
|
||||||
|
*/
|
||||||
|
class ConfigurationTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testConfig(): void
|
||||||
|
{
|
||||||
|
$processor = new Processor();
|
||||||
|
|
||||||
|
$configs = [
|
||||||
|
[
|
||||||
|
'request_schema' => [
|
||||||
|
'callback' => [
|
||||||
|
[
|
||||||
|
'type' => 'type',
|
||||||
|
'params' => ['param']
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'client' => [
|
||||||
|
'type1',
|
||||||
|
'type2'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$config = $processor->processConfiguration(new Configuration(), $configs);
|
||||||
|
|
||||||
|
static::assertArrayHasKey('request_schema', $config);
|
||||||
|
static::assertArrayHasKey('callback', $config['request_schema']);
|
||||||
|
static::assertArrayHasKey('client', $config['request_schema']);
|
||||||
|
static::assertEquals(
|
||||||
|
[
|
||||||
|
'type' => 'type',
|
||||||
|
'params' => ['param']
|
||||||
|
],
|
||||||
|
$config['request_schema']['callback'][0]
|
||||||
|
);
|
||||||
|
static::assertEquals(
|
||||||
|
[
|
||||||
|
'type1',
|
||||||
|
'type2'
|
||||||
|
],
|
||||||
|
$config['request_schema']['client']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPartConfig(): void
|
||||||
|
{
|
||||||
|
$processor = new Processor();
|
||||||
|
|
||||||
|
$configs = [
|
||||||
|
[
|
||||||
|
'request_schema' => [
|
||||||
|
'client' => [
|
||||||
|
'type',
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$config = $processor->processConfiguration(new Configuration(), $configs);
|
||||||
|
|
||||||
|
static::assertArrayHasKey('client', $config['request_schema']);
|
||||||
|
static::assertEquals(['type'], $config['request_schema']['client']);
|
||||||
|
}
|
||||||
|
}
|
55
Tests/DependencyInjection/RetailCrmServiceExtensionTest.php
Normal file
55
Tests/DependencyInjection/RetailCrmServiceExtensionTest.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Tests\DependencyInjection;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RetailCrm\ServiceBundle\ArgumentResolver\CallbackValueResolver;
|
||||||
|
use RetailCrm\ServiceBundle\ArgumentResolver\ClientValueResolver;
|
||||||
|
use RetailCrm\ServiceBundle\DependencyInjection\RetailCrmServiceExtension;
|
||||||
|
use RetailCrm\ServiceBundle\Response\ErrorJsonResponseFactory;
|
||||||
|
use RetailCrm\ServiceBundle\Security\CallbackClientAuthenticator;
|
||||||
|
use RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RetailCrmServiceExtensionTest
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Tests\DependencyInjection
|
||||||
|
*/
|
||||||
|
class RetailCrmServiceExtensionTest extends TestCase
|
||||||
|
{
|
||||||
|
private $container;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder(new EnvPlaceholderParameterBag());
|
||||||
|
$container->getCompilerPassConfig()->setOptimizationPasses([]);
|
||||||
|
$container->getCompilerPassConfig()->setRemovingPasses([]);
|
||||||
|
|
||||||
|
$extension = new RetailCrmServiceExtension();
|
||||||
|
$extension->load(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
'request_schema' => []
|
||||||
|
]
|
||||||
|
],
|
||||||
|
$container
|
||||||
|
);
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoad(): void
|
||||||
|
{
|
||||||
|
static::assertTrue($this->container->hasParameter('retail_crm_service.request_schema.callback'));
|
||||||
|
static::assertTrue($this->container->hasParameter('retail_crm_service.request_schema.client'));
|
||||||
|
static::assertTrue($this->container->hasDefinition(CallbackValueResolver::class));
|
||||||
|
static::assertTrue($this->container->hasDefinition(ClientValueResolver::class));
|
||||||
|
static::assertTrue($this->container->hasDefinition(ErrorJsonResponseFactory::class));
|
||||||
|
static::assertTrue($this->container->hasDefinition(CallbackClientAuthenticator::class));
|
||||||
|
static::assertTrue($this->container->hasDefinition(FrontApiClientAuthenticator::class));
|
||||||
|
}
|
||||||
|
}
|
35
Tests/Response/ErrorJsonResponseFactoryTest.php
Normal file
35
Tests/Response/ErrorJsonResponseFactoryTest.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Tests\Response;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RetailCrm\ServiceBundle\Models\Error;
|
||||||
|
use RetailCrm\ServiceBundle\Response\ErrorJsonResponseFactory;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Serializer;
|
||||||
|
|
||||||
|
class ErrorJsonResponseFactoryTest extends TestCase
|
||||||
|
{
|
||||||
|
private $serializer;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->serializer = new Serializer([new ObjectNormalizer()], [new JsonEncoder()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreate(): void
|
||||||
|
{
|
||||||
|
$factory = new ErrorJsonResponseFactory($this->serializer);
|
||||||
|
$error = new Error();
|
||||||
|
$error->message = 'Test error message';
|
||||||
|
|
||||||
|
$result = $factory->create($error);
|
||||||
|
|
||||||
|
static::assertInstanceOf(JsonResponse::class, $result);
|
||||||
|
static::assertEquals(Response::HTTP_BAD_REQUEST, $result->getStatusCode());
|
||||||
|
static::assertEquals('{"code":null,"message":"Test error message","details":null}', $result->getContent());
|
||||||
|
}
|
||||||
|
}
|
135
Tests/Security/CallbackClientAuthenticatorTest.php
Normal file
135
Tests/Security/CallbackClientAuthenticatorTest.php
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Tests\Security;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RetailCrm\ServiceBundle\Response\ErrorJsonResponseFactory;
|
||||||
|
use RetailCrm\ServiceBundle\Security\CallbackClientAuthenticator;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CallbackClientAuthenticatorTest
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Tests\Security
|
||||||
|
*/
|
||||||
|
class CallbackClientAuthenticatorTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testStart(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
$errorResponseFactory
|
||||||
|
->expects(static::once())
|
||||||
|
->method('create')
|
||||||
|
->willReturn(
|
||||||
|
new JsonResponse(['message' => 'Authentication required'], Response::HTTP_UNAUTHORIZED)
|
||||||
|
);
|
||||||
|
|
||||||
|
$auth = new CallbackClientAuthenticator($errorResponseFactory);
|
||||||
|
$result = $auth->start(new Request(), new AuthenticationException());
|
||||||
|
|
||||||
|
static::assertInstanceOf(JsonResponse::class, $result);
|
||||||
|
static::assertEquals(Response::HTTP_UNAUTHORIZED, $result->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetCredentials(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
|
||||||
|
$auth = new CallbackClientAuthenticator($errorResponseFactory);
|
||||||
|
$result = $auth->getCredentials(new Request([], [CallbackClientAuthenticator::AUTH_FIELD => '123']));
|
||||||
|
|
||||||
|
static::assertEquals('123', $result);
|
||||||
|
|
||||||
|
$result = $auth->getCredentials(new Request([CallbackClientAuthenticator::AUTH_FIELD => '123']));
|
||||||
|
|
||||||
|
static::assertEquals('123', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCheckCredentials(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
|
||||||
|
$user = new class implements UserInterface {
|
||||||
|
public function getRoles(): array
|
||||||
|
{
|
||||||
|
return ["USER"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassword(): string
|
||||||
|
{
|
||||||
|
return "123";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSalt(): string
|
||||||
|
{
|
||||||
|
return "salt";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsername(): string
|
||||||
|
{
|
||||||
|
return "user";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function eraseCredentials(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$auth = new CallbackClientAuthenticator($errorResponseFactory);
|
||||||
|
$result = $auth->checkCredentials(new Request(), $user);
|
||||||
|
|
||||||
|
static::assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnAuthenticationFailure(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
$errorResponseFactory
|
||||||
|
->expects(static::once())
|
||||||
|
->method('create')
|
||||||
|
->willReturn(
|
||||||
|
new JsonResponse(
|
||||||
|
['message' => 'An authentication exception occurred.'],
|
||||||
|
Response::HTTP_FORBIDDEN
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$auth = new CallbackClientAuthenticator($errorResponseFactory);
|
||||||
|
$result = $auth->start(new Request(), new AuthenticationException());
|
||||||
|
|
||||||
|
static::assertInstanceOf(JsonResponse::class, $result);
|
||||||
|
static::assertEquals(Response::HTTP_FORBIDDEN, $result->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupports(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
|
||||||
|
$auth = new CallbackClientAuthenticator($errorResponseFactory);
|
||||||
|
$result = $auth->supports(new Request([], [CallbackClientAuthenticator::AUTH_FIELD => '123']));
|
||||||
|
|
||||||
|
static::assertTrue($result);
|
||||||
|
|
||||||
|
$result = $auth->supports(new Request([CallbackClientAuthenticator::AUTH_FIELD => '123']));
|
||||||
|
|
||||||
|
static::assertTrue($result);
|
||||||
|
|
||||||
|
$result = $auth->supports(new Request());
|
||||||
|
|
||||||
|
static::assertFalse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupportsRememberMe(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
|
||||||
|
$auth = new CallbackClientAuthenticator($errorResponseFactory);
|
||||||
|
$result = $auth->supportsRememberMe();
|
||||||
|
|
||||||
|
static::assertFalse($result);
|
||||||
|
}
|
||||||
|
}
|
123
Tests/Security/FrontApiClientAuthenticatorTest.php
Normal file
123
Tests/Security/FrontApiClientAuthenticatorTest.php
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RetailCrm\ServiceBundle\Tests\Security;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RetailCrm\ServiceBundle\Response\ErrorJsonResponseFactory;
|
||||||
|
use RetailCrm\ServiceBundle\Security\CallbackClientAuthenticator;
|
||||||
|
use RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator;
|
||||||
|
use RetailCrm\ServiceBundle\Tests\DataFixtures\User;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class FrontApiClientAuthenticatorTest
|
||||||
|
*
|
||||||
|
* @package RetailCrm\ServiceBundle\Tests\Security
|
||||||
|
*/
|
||||||
|
class FrontApiClientAuthenticatorTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testStart(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
$errorResponseFactory
|
||||||
|
->expects(static::once())
|
||||||
|
->method('create')
|
||||||
|
->willReturn(
|
||||||
|
new JsonResponse(['message' => 'Authentication required'], Response::HTTP_UNAUTHORIZED)
|
||||||
|
);
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
|
||||||
|
$auth = new FrontApiClientAuthenticator($errorResponseFactory, $security);
|
||||||
|
$result = $auth->start(new Request(), new AuthenticationException());
|
||||||
|
|
||||||
|
static::assertInstanceOf(JsonResponse::class, $result);
|
||||||
|
static::assertEquals(Response::HTTP_UNAUTHORIZED, $result->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetCredentials(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
|
||||||
|
$auth = new FrontApiClientAuthenticator($errorResponseFactory, $security);
|
||||||
|
$result = $auth->getCredentials(new Request([], [CallbackClientAuthenticator::AUTH_FIELD => '123']));
|
||||||
|
|
||||||
|
static::assertEquals('123', $result);
|
||||||
|
|
||||||
|
$result = $auth->getCredentials(new Request([CallbackClientAuthenticator::AUTH_FIELD => '123']));
|
||||||
|
|
||||||
|
static::assertEquals('123', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCheckCredentials(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
|
||||||
|
$auth = new FrontApiClientAuthenticator($errorResponseFactory, $security);
|
||||||
|
$result = $auth->checkCredentials(new Request(), new User());
|
||||||
|
|
||||||
|
static::assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnAuthenticationFailure(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
$errorResponseFactory
|
||||||
|
->expects(static::once())
|
||||||
|
->method('create')
|
||||||
|
->willReturn(
|
||||||
|
new JsonResponse(
|
||||||
|
['message' => 'An authentication exception occurred.'],
|
||||||
|
Response::HTTP_FORBIDDEN
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
|
||||||
|
$auth = new FrontApiClientAuthenticator($errorResponseFactory, $security);
|
||||||
|
$result = $auth->start(new Request(), new AuthenticationException());
|
||||||
|
|
||||||
|
static::assertInstanceOf(JsonResponse::class, $result);
|
||||||
|
static::assertEquals(Response::HTTP_FORBIDDEN, $result->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupportsFalse(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->method('getUser')->willReturn(new User());
|
||||||
|
|
||||||
|
$auth = new FrontApiClientAuthenticator($errorResponseFactory, $security);
|
||||||
|
$result = $auth->supports(new Request());
|
||||||
|
|
||||||
|
static::assertFalse($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupportsTrue(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
$security->method('getUser')->willReturn(null);
|
||||||
|
|
||||||
|
$auth = new FrontApiClientAuthenticator($errorResponseFactory, $security);
|
||||||
|
$result = $auth->supports(new Request([], [FrontApiClientAuthenticator::AUTH_FIELD => '123']));
|
||||||
|
|
||||||
|
static::assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSupportsRememberMe(): void
|
||||||
|
{
|
||||||
|
$errorResponseFactory = $this->createMock(ErrorJsonResponseFactory::class);
|
||||||
|
$security = $this->createMock(Security::class);
|
||||||
|
|
||||||
|
$auth = new FrontApiClientAuthenticator($errorResponseFactory, $security);
|
||||||
|
$result = $auth->supportsRememberMe();
|
||||||
|
|
||||||
|
static::assertTrue($result);
|
||||||
|
}
|
||||||
|
}
|
43
composer.json
Normal file
43
composer.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "retailcrm/service-bundle",
|
||||||
|
"description": "Core bundle for RetailCRM integration services",
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "RetailCRM",
|
||||||
|
"email": "support@retailcrm.pro"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.3",
|
||||||
|
"symfony/framework-bundle": "^4.0|^5.0",
|
||||||
|
"symfony/serializer": "^5.2",
|
||||||
|
"symfony/http-kernel": "^4.0|^5.0",
|
||||||
|
"symfony/validator": "^4.0|^5.0",
|
||||||
|
"symfony/security-guard": "^4.0|^5.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"RetailCrm\\ServiceBundle\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"RetailCrm\\ServiceBundle\\Tests\\": "Tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"phpunit/phpunit": "^8.0 || ^9.0",
|
||||||
|
"doctrine/annotations": "^1.11",
|
||||||
|
"doctrine/cache": "^1.10"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"tests": "./vendor/bin/phpunit -c phpunit.xml.dist"
|
||||||
|
}
|
||||||
|
}
|
33
phpunit.xml.dist
Normal file
33
phpunit.xml.dist
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
>
|
||||||
|
<coverage>
|
||||||
|
<include>
|
||||||
|
<directory>./</directory>
|
||||||
|
</include>
|
||||||
|
<exclude>
|
||||||
|
<directory>./Tests</directory>
|
||||||
|
<directory>./vendor</directory>
|
||||||
|
</exclude>
|
||||||
|
<report>
|
||||||
|
<clover outputFile="coverage.xml"/>
|
||||||
|
</report>
|
||||||
|
</coverage>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="RetailCRM Service Bundle Tests">
|
||||||
|
<directory>./Tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<logging>
|
||||||
|
<junit outputFile="test-report.xml"/>
|
||||||
|
</logging>
|
||||||
|
</phpunit>
|
Loading…
Reference in New Issue
Block a user