Skip to content
Merged
28 changes: 7 additions & 21 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,25 @@ jobs:
- '8.3'
coverage: ['none']
symfony-versions:
- '4.4.*'
- '5.4.*'
- '6.0.*'
- '6.2.*'
- '7.0.*'
- '6.4.*'
- '7.1.*'
exclude:
- php: '8.1'
symfony-versions: '7.0.*'
symfony-versions: '7.1.*'
include:
- php: '7.4'
symfony-versions: '^4.4'
coverage: 'none'
- php: '7.4'
symfony-versions: '^5.4'
coverage: 'none'
- php: '8.0'
symfony-versions: '^4.4'
coverage: 'none'
- php: '8.0'
- php: '8.1'
symfony-versions: '^5.4'
coverage: 'none'
- description: 'Log Code Coverage'
php: '8.2'
php: '8.4'
coverage: 'xdebug'
symfony-versions: '^7.0'
symfony-versions: '^7.1'

name: PHP ${{ matrix.php }} Symfony ${{ matrix.symfony-versions }} ${{ matrix.description }}
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- uses: actions/cache@v4
with:
Expand Down Expand Up @@ -83,9 +72,6 @@ jobs:
- name: Install dependencies
run: composer install

- name: Add doctrine/orm
run: composer require --no-progress --no-interaction --prefer-dist doctrine/orm:^2.0

- name: Run PHPUnit tests
run: composer phpunit
if: matrix.coverage == 'none'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand Down
12 changes: 3 additions & 9 deletions .github/workflows/static-analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2

- name: Install dependencies
run: composer install --no-progress --no-interaction --prefer-dist

- name: Add doctrine/orm
run: composer require --no-progress --no-interaction --prefer-dist doctrine/orm:^2.0

- name: Run script
run: composer code-style

Expand All @@ -30,17 +27,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2

- name: Install dependencies
run: composer install --no-progress --no-interaction --prefer-dist

- name: Add doctrine/orm
run: composer require --no-progress --no-interaction --prefer-dist doctrine/orm:^2.0

- name: Run script
run: composer phpstan

Expand All @@ -49,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand Down
21 changes: 11 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,21 @@
"license": "MIT",
"require": {
"ext-json": "*",
"php": "^7.4 || ^8.0",
"php": "^8.1",
"behat/behat": "^3.0",
"symfony/config": "^4.4 || ^5.4 || ^6.0 || ^7.0",
"symfony/dependency-injection": "^4.4 || ^5.4.34 || ^6.0 || ^7.0.2",
"symfony/http-client": "^4.4 || ^5.4 || ^6.0 || ^7.0",
"symfony/http-kernel": "^4.4 || ^5.4 || ^6.0 || ^7.0",
"symfony/routing": "^4.4 || ^5.4 || ^6.0 || ^7.0",
"symfony/config": "^5.4 || ^6.4 || ^7.0",
"symfony/dependency-injection": "^5.4.34 || ^6.4 || ^7.1",
"symfony/http-client": "^5.4 || ^6.4 || ^7.1",
"symfony/http-kernel": "^5.4 || ^6.4 || ^7.1",
"symfony/routing": "^5.4 || ^6.4 || ^7.1",
"macpaw/similar-arrays": "^1.0"
},
"require-dev": {
"phpstan/phpstan": "^1.4",
"phpunit/phpunit": "^9.3",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^10.0",
"slevomat/coding-standard": "^7.0",
"squizlabs/php_codesniffer": "^3.6"
"squizlabs/php_codesniffer": "^3.6",
"doctrine/orm": "^2.0"
},
"autoload": {
"psr-4": {
Expand All @@ -57,7 +58,7 @@
"phpstan": "./vendor/bin/phpstan analyse -l max",
"code-style": "./vendor/bin/phpcs",
"code-style-fix": "./vendor/bin/phpcbf",
"phpunit": "./vendor/bin/phpunit",
"phpunit": "./vendor/bin/phpunit --no-coverage",
"phpunit-html-coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html=coverage",
"dev-checks": [
"composer validate",
Expand Down
21 changes: 0 additions & 21 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
parameters:
excludes_analyse:
paths:
- src
level: max
Expand All @@ -9,29 +8,9 @@ parameters:
message: '#Parameter \#1 \$json of function json_decode expects string, string\|false given.*#'
count: 3
path: ./src/Context/ApiContext.php
-
message: '#Call to an undefined method Symfony\\Component\\HttpKernel\\KernelInterface::terminate\(\).*#'
count: 1
path: ./src/Context/ApiContext.php
-
message: '#Parameter \#3 \$actualJSON of method BehatApiContext\\Context\\ApiContext::compareStructureResponse\(\) expects string, string\|false given.*#'
count: 1
path: ./src/Context/ApiContext.php
-
message: '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::children\(\).#'
count: 1
path: ./src/DependencyInjection
-
message: '#.*NodeParentInterface|null.*#'
count: 1
path: ./src/DependencyInjection
-
message: '#Call to an undefined method object::clear().*#'
count: 1
path: ./src/Service/ResetManager/DoctrineResetManager
-
message: '#Call to an undefined method object::getConnection().*#'
count: 1
path: ./src/Service/ResetManager/DoctrineResetManager
-
identifier: missingType.iterableValue
74 changes: 50 additions & 24 deletions src/Context/ApiContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\Routing\RouterInterface;
use Throwable;

Expand All @@ -23,8 +24,12 @@ class ApiContext implements Context
private StringManager $stringManager;
private RouterInterface $router;
private RequestStack $requestStack;
private ?Response $response;
private KernelInterface $kernel;
private Response $response;
private KernelInterface&TerminableInterface $kernel;

/**
* @var list<ResetManagerInterface>
*/
private array $resetManagers = [];

/**
Expand All @@ -38,19 +43,19 @@ class ApiContext implements Context
protected array $serverParams = [];

/**
* @var array<mixed> $requestParams
* @var array<string, mixed> $requestParams
*/
protected array $requestParams = [];

/**
* @var array<mixed> $savedValues
* @var array<string, string|list<string>> $savedValues
*/
protected array $savedValues = [];

public function __construct(
RouterInterface $router,
RequestStack $requestStack,
KernelInterface $kernel
KernelInterface&TerminableInterface $kernel
) {
$this->router = $router;
$this->requestStack = $requestStack;
Expand Down Expand Up @@ -116,8 +121,13 @@ public function theRequestContainsParams(PyStringNode $params): void

$newRequestParams = (array) json_decode($processedParams, true, 512, JSON_THROW_ON_ERROR);
$newRequestParams = $this->convertRunnableCodeParams($newRequestParams);
$this->requestParams = array_merge($this->requestParams, $newRequestParams);
$this->savedValues = array_merge($this->savedValues, $newRequestParams);
/** @var array<string, mixed> $requestParams */
$requestParams = array_merge($this->requestParams, $newRequestParams);
$this->requestParams = $requestParams;

/** @var array<string, mixed> $savedValues */
$savedValues = array_merge($this->savedValues, $newRequestParams);
$this->savedValues = $savedValues;
}

/**
Expand All @@ -130,19 +140,32 @@ public function iSendRequestToRoute(
$routeParams = $this->popRouteAttributesFromRequestParams($route, $this->requestParams);
$postFields = [];
$queryString = '';
$content = null;

$url = $this->router->generate($route, $routeParams);
$url = preg_replace('|^/app[^\.]*\.php|', '', $url);
$url = preg_replace('|^/app[^.]*\.php|', '', $url);

if (Request::METHOD_GET === $method) {
$queryString = http_build_query($this->requestParams);
}

if (in_array($method, [Request::METHOD_POST, Request::METHOD_PATCH, Request::METHOD_PUT], true)) {
$postFields = $this->requestParams;
$isJsonRequest = array_key_exists('Content-Type', $this->headers) &&
str_contains(strtolower($this->headers['Content-Type']), 'application/json');

if ($isJsonRequest) {
$content = json_encode($this->requestParams, JSON_THROW_ON_ERROR);
} else {
$postFields = $this->requestParams;
}
}

$request = Request::create($url . '?' . $queryString, $method, $postFields);
$request = Request::create(
uri: $url . '?' . $queryString,
method: $method,
parameters: $postFields,
content: $content
);
$request->headers->add($this->headers);
$request->server->add($this->serverParams);

Expand Down Expand Up @@ -171,19 +194,21 @@ private function handleRequestWithKernel(Request $request): Response
}

/**
* @param array<string,string> $requestParams
* @param array<string, mixed> $requestParams
*
* @return array<string,string>
* @return array<string, mixed>
*/
private function popRouteAttributesFromRequestParams(string $route, array &$requestParams): array
{
$routeParams = [];
$routeDecl = $this->router->getRouteCollection()->get($route);

if (is_array($requestParams) && ($routeDecl = $this->router->getRouteCollection()->get($route))) {
if ($routeDecl !== null) {
/** @var array<string, string> $requirements */
$requirements = $routeDecl->getRequirements();

foreach ($requirements as $attribute => $requirement) {
if (isset($requestParams[$attribute]) && strpos($attribute, '_') !== 0) {
if (isset($requestParams[$attribute]) && !str_starts_with($attribute, '_')) {
$routeParams[$attribute] = $requestParams[$attribute];
unset($requestParams[$attribute]);
}
Expand Down Expand Up @@ -282,15 +307,20 @@ public function responseShouldBeJsonWithVariableFields(string $variableFields, P
$this->compareStructureResponse($variableFields, $string, $this->getResponse()->getContent());
}

protected function compareStructureResponse(string $variableFields, PyStringNode $string, string $actualJSON): void
{
protected function compareStructureResponse(
string $variableFieldsString,
PyStringNode $string,
string $actualJSON
): void {
if ($actualJSON === '') {
throw new RuntimeException('Response is not JSON');
}

$expectedResponse = (array) json_decode(trim($string->getRaw()), true);
$actualResponse = (array) json_decode($actualJSON, true);
$variableFields = $variableFields ? array_map('trim', explode(',', $variableFields)) : [];
$expectedResponse = json_decode(trim($string->getRaw()), true, 512, JSON_THROW_ON_ERROR);
$actualResponse = json_decode($actualJSON, true, 512, JSON_THROW_ON_ERROR);
$variableFields = $variableFieldsString
? array_map('trim', explode(',', $variableFieldsString))
: [];

if (!$this->similarArrayManager->isArraysSimilar($expectedResponse, $actualResponse, $variableFields)) {
$prettyJSON = json_encode($actualResponse, JSON_PRETTY_PRINT);
Expand Down Expand Up @@ -343,7 +373,7 @@ protected function checkResponseHeader(string $headerName, string $headerValue):

$responseHeaderValue = $response->headers->get($givenHeaderName);

if (null === $responseHeaderValue || !substr_count($responseHeaderValue, $givenHeaderValue) > 0) {
if (null === $responseHeaderValue || substr_count($responseHeaderValue, $givenHeaderValue) < 1) {
$message = sprintf(
'Response header %s does not match. Expected: %s, given value: %s',
$givenHeaderName,
Expand Down Expand Up @@ -413,10 +443,6 @@ private function resetRequestOptions(): void

protected function getResponse(): Response
{
if ($this->response === null) {
throw new RuntimeException('Response is null.');
}

return $this->response;
}

Expand Down
12 changes: 6 additions & 6 deletions src/DependencyInjection/BehatApiContextExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@

class BehatApiContextExtension extends Extension
{
/**
* @param array<array> $configs
*
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
/** @var array<string, mixed> $config */
$config = $this->processConfiguration($configuration, $configs);

$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
Expand All @@ -28,7 +24,7 @@ public function load(array $configs, ContainerBuilder $container): void
}

/**
* @param array<array> $config
* @param array<string, mixed> $config
*/
private function loadApiContext(
array $config,
Expand All @@ -44,6 +40,10 @@ private function loadApiContext(
);
}

/**
* @param array<string, mixed> $config
* @param class-string $contextClass
*/
private function configureKernelResetManagers(
array $config,
ContainerBuilder $container,
Expand Down
Loading