Skip to content

Commit

Permalink
Merge pull request #12 from Vartanchik/feat/runnable_request_parameters
Browse files Browse the repository at this point in the history
add runnable parameters convertor
  • Loading branch information
Yozhef authored Oct 24, 2023
2 parents eb14cb0 + 9f61214 commit 587e409
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/node_modules/
/.idea/
/package-lock.json
.DS_Store
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,44 @@ Step 2: Configure Behat
Go to `behat.yml`

```yaml
...
# ...
contexts:
- BehatApiContext\Context\ApiContext
...
# ...
```

Usage
=============

Runnable request parameters
----------------------------------
Main use case when tests need to use current date.
Instead of static data in some `testCaseName.feature`, like this:
```feature
"""
{
"dateTo": 1680360081,
"dateFrom": 1680532881,
}
"""
```
Can use, for example:
```feature
"""
{
"dateTo": "<(new DateTimeImmutable())->add(new DateInterval('P6D'))->getTimeStamp()>",
"dateFrom": "<(new DateTimeImmutable())->add(new DateInterval('P2D'))->getTimeStamp()>",
}
"""
```

#### To accomplish this, several conditions must be met:
- Runnable code must be a string and placed in `<>`
- Should not add `return` keyword at the beginning, otherwise will get RuntimeException
- Should not add `;` keyword at the end, otherwise will get RuntimeException
- Should not use the code that returns `null`, otherwise will get RuntimeException


[master Build Status]: https://github.com/macpaw/behat-api-context/actions?query=workflow%3ACI+branch%3Amaster
[master Build Status Image]: https://github.com/macpaw/behat-api-context/workflows/CI/badge.svg?branch=master
[develop Build Status]: https://github.com/macpaw/behat-api-context/actions?query=workflow%3ACI+branch%3Adevelop
Expand Down
8 changes: 6 additions & 2 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ parameters:
count: 1
path: ./src/DependencyInjection
-
message: '#Cannot call method (.*)? on object|null.*#'
count: 2
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
54 changes: 54 additions & 0 deletions src/Context/ApiContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\RouterInterface;
use Throwable;

class ApiContext implements Context
{
Expand Down Expand Up @@ -114,6 +115,7 @@ 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);
}
Expand Down Expand Up @@ -299,6 +301,53 @@ protected function compareStructureResponse(string $variableFields, PyStringNode
}
}

protected function convertRunnableCodeParams(array $requestParams): array
{
foreach ($requestParams as $key => $value) {
if (is_array($value)) {
$requestParams[$key] = $this->convertRunnableCodeParams($value);
continue;
}

if (!is_string($value)) {
continue;
}

$pregMatchValue = preg_match('/^<.*>$/', trim($value));

if ($pregMatchValue === 0 || $pregMatchValue === false) {
continue;
}

$command = substr(trim($value), 1, -1);

try {
$resultValue = eval('return ' . $command . ';');
} catch (Throwable $exception) {
throw new RuntimeException(
sprintf(
'Failed run your code %s, error message: %s',
$value,
$exception->getMessage()
)
);
}

if (is_null($resultValue)) {
throw new \RuntimeException(
sprintf(
'Running code: %s - should not return the null',
$command
)
);
}

$requestParams[$key] = $resultValue;
}

return $requestParams;
}

private function resetRequestOptions(): void
{
$this->headers = [];
Expand All @@ -314,4 +363,9 @@ protected function getResponse(): Response

return $this->response;
}

public function geRequestParams(): array
{
return $this->requestParams;
}
}
182 changes: 182 additions & 0 deletions tests/Unit/Context/ApiContextTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php

declare(strict_types=1);

namespace BehatApiContext\Tests\Unit\Context;

use Behat\Gherkin\Node\PyStringNode;
use BehatApiContext\Context\ApiContext;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\RouterInterface;

class ApiContextTest extends TestCase
{
private const PARAMS_VALUES = 'paramsValues';
private const INITIAL_PARAM_VALUE = 'initialParamValue';
private ApiContext $apiContext;

protected function setUp(): void
{
$routerMock = $this->createMock(RouterInterface::class);
$requestStackMock = $this->createMock(RequestStack::class);
$kernelMock = $this->createMock(KernelInterface::class);

$this->apiContext = new ApiContext($routerMock, $requestStackMock, $kernelMock);
}

/**
* @param PyStringNode $paramsValues
* @param string $initialParamValue
*
* @dataProvider getTheRequestContainsParamsSuccess
*/
public function testTheRequestContainsParamsSuccess(PyStringNode $paramsValues, string $initialParamValue): void
{
$this->assertTrue(str_contains($paramsValues->getStrings()[3], $initialParamValue));
$this->assertTrue(str_contains($paramsValues->getStrings()[6], $initialParamValue));
$this->assertTrue(str_contains($paramsValues->getStrings()[9], $initialParamValue));

$this->apiContext->theRequestContainsParams($paramsValues);

$this->assertIsInt($this->apiContext->geRequestParams()['dateFrom']);
$this->assertIsInt($this->apiContext->geRequestParams()['levelOne']['dateFrom']);
$this->assertIsInt($this->apiContext->geRequestParams()['levelOne']['levelTwo']['dateFrom']);

$this->assertEquals(
10,
strlen((string)$this->apiContext->geRequestParams()['dateFrom'])
);
$this->assertEquals(
10,
strlen((string)$this->apiContext->geRequestParams()['levelOne']['dateFrom'])
);
$this->assertEquals(
10,
strlen((string)$this->apiContext->geRequestParams()['levelOne']['levelTwo']['dateFrom'])
);

$this->assertTrue(
str_contains(
$paramsValues->getStrings()[1],
$this->apiContext->geRequestParams()['tripId']
)
);
$this->assertTrue(
str_contains(
$paramsValues->getStrings()[2],
strval($this->apiContext->geRequestParams()['dateTo'])
)
);
$this->assertTrue(
str_contains(
$paramsValues->getStrings()[5],
strval($this->apiContext->geRequestParams()['levelOne']['dateTo'])
)
);
$this->assertTrue(
str_contains(
$paramsValues->getStrings()[8],
strval($this->apiContext->geRequestParams()['levelOne']['levelTwo']['dateTo'])
)
);
}

/**
* @param PyStringNode $paramsValues
*
* @dataProvider getTheRequestContainsParamsRuntimeException
*/
public function testTheRequestContainsParamsRuntimeException(PyStringNode $paramsValues): void
{
$this->expectException(RuntimeException::class);
$this->apiContext->theRequestContainsParams($paramsValues);
}

public function getTheRequestContainsParamsSuccess(): array
{
return [
[
self::PARAMS_VALUES => new PyStringNode(
[
'{',
' "tripId": "26e185b9-a233-470e-b2d4-2818908a075f",',
' "dateTo": 1680361181,',
' "dateFrom": "<(new DateTimeImmutable())->getTimestamp()>",',
' "levelOne": {',
' "dateTo": 1680343281,',
' "dateFrom": "<(new DateTimeImmutable())->getTimestamp()>",',
' "levelTwo": {',
' "dateTo": 1680343281,',
' "dateFrom": "<(new DateTimeImmutable())->getTimestamp()>"',
' }',
' }',
'}',
],
12
),
self::INITIAL_PARAM_VALUE => '<(new DateTimeImmutable())->getTimestamp()>',
],
];
}

public function getTheRequestContainsParamsRuntimeException(): array
{
return [
[
self::PARAMS_VALUES => new PyStringNode(
[
'{',
' "tripId": "26e185b9-a233-470e-b2d4-2818908a075f",',
' "dateTo": 1680361181,',
' "dateFrom": "<(new DateTimeImutable())->getTimestamp()>"',
'}',
],
12
)
],
[
self::PARAMS_VALUES => new PyStringNode(
[
'{',
' "tripId": "26e185b9-a233-470e-b2d4-2818908a075f",',
' "dateTo": 1680361181,',
' "dateFrom": "<(DateTimeImmutable)->getTimestamp()>"',
'}',
],
12
)
],
[
self::PARAMS_VALUES => new PyStringNode(
[
'{',
' "tripId": "26e185b9-a233-470e-b2d4-2818908a075f",',
' "levelOne": {',
' "levelTwo": {',
' "dateTo": 1680343281,',
' "dateFrom": "<(ne DateTimeImmutable())->getTimestamp()>"',
' }',
' }',
'}',
],
12
),
],
[
self::PARAMS_VALUES => new PyStringNode(
[
'{',
' "tripId": "26e185b9-a233-470e-b2d4-2818908a075f",',
' "dateTo": 1680361181,',
' "dateFrom": "<>"',
'}',
],
12
)
],
];
}
}

0 comments on commit 587e409

Please sign in to comment.