diff --git a/src/HttpClientMock/Matcher/Hit.php b/src/HttpClientMock/Matcher/Hit.php index 3a018c4..250de86 100644 --- a/src/HttpClientMock/Matcher/Hit.php +++ b/src/HttpClientMock/Matcher/Hit.php @@ -4,6 +4,9 @@ namespace Brainbits\FunctionalTestHelpers\HttpClientMock\Matcher; +use function is_array; +use function Safe\json_encode; + final readonly class Hit { private function __construct( @@ -34,9 +37,10 @@ public static function matchesHeader(string $key, string $value): self return new self('header', $key, 5, $value); } - public static function matchesQueryParam(string $key, string $value): self + /** @param string|mixed[] $value */ + public static function matchesQueryParam(string $key, string|array $value): self { - return new self('queryParam', $key, 5, $value); + return new self('queryParam', $key, 5, is_array($value) ? json_encode($value) : $value); } public static function matchesRequestParam(string $key, string $value): self diff --git a/src/HttpClientMock/Matcher/Mismatch.php b/src/HttpClientMock/Matcher/Mismatch.php index 1964b37..d279418 100644 --- a/src/HttpClientMock/Matcher/Mismatch.php +++ b/src/HttpClientMock/Matcher/Mismatch.php @@ -8,6 +8,7 @@ use SebastianBergmann\Diff\Differ; use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; +use function is_array; use function Safe\json_encode; use const PHP_EOL; @@ -131,13 +132,17 @@ public static function mismatchingHeader(string $key, mixed $value, mixed $other ); } - public static function mismatchingQueryParam(string $key, string $value, string $otherValue): self + /** + * @param string|mixed[] $value + * @param string|mixed[] $otherValue + */ + public static function mismatchingQueryParam(string $key, string|array $value, string|array $otherValue): self { return new self( 'queryParam', $key, - $value, - $otherValue, + is_array($value) ? json_encode($value) : $value, + is_array($otherValue) ? json_encode($otherValue) : $otherValue, ); } diff --git a/src/HttpClientMock/Matcher/Missing.php b/src/HttpClientMock/Matcher/Missing.php index 31dd299..8032619 100644 --- a/src/HttpClientMock/Matcher/Missing.php +++ b/src/HttpClientMock/Matcher/Missing.php @@ -4,6 +4,9 @@ namespace Brainbits\FunctionalTestHelpers\HttpClientMock\Matcher; +use function is_array; +use function Safe\json_encode; + final readonly class Missing { public int $score; @@ -22,12 +25,13 @@ public static function missingHeader(string $key, string $expected): self ); } - public static function missingQueryParam(string $key, string $expected): self + /** @param string|mixed[] $expected */ + public static function missingQueryParam(string $key, string|array $expected): self { return new self( 'queryParam', $key, - $expected, + is_array($expected) ? json_encode($expected) : $expected, ); } diff --git a/src/HttpClientMock/Matcher/QueryParamMatcher.php b/src/HttpClientMock/Matcher/QueryParamMatcher.php index e718ac5..f9d5558 100644 --- a/src/HttpClientMock/Matcher/QueryParamMatcher.php +++ b/src/HttpClientMock/Matcher/QueryParamMatcher.php @@ -6,17 +6,42 @@ use Brainbits\FunctionalTestHelpers\HttpClientMock\RealRequest; +use function in_array; +use function is_array; +use function Safe\json_encode; use function sprintf; +use function str_ends_with; +use function substr; use function vsprintf; final readonly class QueryParamMatcher implements Matcher { - private string $value; + private string $key; - /** @param array $placeholders */ - public function __construct(private string $key, string $value, array $placeholders) + /** @var string|mixed[] */ + private string|array $value; + + private bool $isArray; + + /** + * @param string|mixed[] $value + * @param array $placeholders + */ + public function __construct(string $key, string|array $value, array $placeholders) { - $this->value = vsprintf($value, $placeholders); + $isArray = false; + if (str_ends_with($key, '[]')) { + $key = substr($key, 0, -2); + $isArray = true; + } + + if (!is_array($value)) { + $value = vsprintf($value, $placeholders); + } + + $this->key = $key; + $this->value = $value; + $this->isArray = $isArray; } public function __invoke(RealRequest $realRequest): Hit|Mismatch|Missing @@ -28,7 +53,10 @@ public function __invoke(RealRequest $realRequest): Hit|Mismatch|Missing $expectedValue = $this->value; $realValue = $realRequest->getQueryParam($this->key); - if ($expectedValue !== $realValue) { + if ( + (!$this->isArray && $expectedValue !== $realValue) || + ($this->isArray && !in_array($expectedValue, $realValue, true)) + ) { return Mismatch::mismatchingQueryParam($this->key, $expectedValue, $realValue); } @@ -37,6 +65,10 @@ public function __invoke(RealRequest $realRequest): Hit|Mismatch|Missing public function __toString(): string { - return sprintf('request.queryParams["%s"] === "%s"', $this->key, $this->value); + return sprintf( + 'request.queryParams["%s"] === "%s"', + $this->key, + is_array($this->value) ? json_encode($this->value) : $this->value, + ); } } diff --git a/src/HttpClientMock/MockRequestBuilder.php b/src/HttpClientMock/MockRequestBuilder.php index 841f024..94a21c9 100644 --- a/src/HttpClientMock/MockRequestBuilder.php +++ b/src/HttpClientMock/MockRequestBuilder.php @@ -187,10 +187,11 @@ public function xml(string|callable $xml): self return $this; } - public function queryParam(string $key, string $value, string ...$placeholders): self + /** @param string|mixed[] $value */ + public function queryParam(string $key, string|array $value, string ...$placeholders): self { $this->matchers['queryParams'] ??= []; - $this->matchers['queryParams'][$key] = new QueryParamMatcher($key, $value, $placeholders); + $this->matchers['queryParams'][] = new QueryParamMatcher($key, $value, $placeholders); return $this; } @@ -198,7 +199,7 @@ public function queryParam(string $key, string $value, string ...$placeholders): public function requestParam(string $key, string $value): self { $this->matchers['requestParams'] ??= []; - $this->matchers['requestParams'][$key] = new RequestParamMatcher($key, $value); + $this->matchers['requestParams'][] = new RequestParamMatcher($key, $value); /* if ((string) $this->content !== '') { @@ -218,7 +219,7 @@ public function multipart( string|null $content = null, ): self { $this->matchers['multiparts'] ??= []; - $this->matchers['multiparts'][$name] = new MultipartMatcher($name, $mimetype, $filename, $content); + $this->matchers['multiparts'][] = new MultipartMatcher($name, $mimetype, $filename, $content); return $this; } diff --git a/src/HttpClientMock/RealRequest.php b/src/HttpClientMock/RealRequest.php index f26c289..57ca8d8 100644 --- a/src/HttpClientMock/RealRequest.php +++ b/src/HttpClientMock/RealRequest.php @@ -22,7 +22,7 @@ final class RealRequest /** * @param array $headers * @param mixed[] $json - * @param array $queryParams + * @param array $queryParams * @param array $requestParams * @param array $multiparts */ @@ -84,7 +84,8 @@ public function hasQueryParam(string $key): bool return array_key_exists($key, $this->queryParams); } - public function getQueryParam(string $key): string|null + /** @return string|mixed[]|null */ + public function getQueryParam(string $key): string|array|null { return $this->queryParams[$key] ?? null; } diff --git a/tests/HttpClientMock/Matcher/QueryParamMatcherTest.php b/tests/HttpClientMock/Matcher/QueryParamMatcherTest.php index c08ae4d..7f6126e 100644 --- a/tests/HttpClientMock/Matcher/QueryParamMatcherTest.php +++ b/tests/HttpClientMock/Matcher/QueryParamMatcherTest.php @@ -47,6 +47,45 @@ public function testMatchQueryParamWithZeroValue(): void self::assertMatcher('queryParam', $result); } + public function testMatchQueryParamWithFirstArrayValue(): void + { + $matcher = new QueryParamMatcher('name[]', 'abc', []); + + $realRequest = $this->createRealRequest(queryParams: ['name' => ['abc', 'def']]); + + $result = $matcher($realRequest); + + self::assertInstanceOf(Hit::class, $result); + self::assertScore(5, $result); + self::assertMatcher('queryParam', $result); + } + + public function testMatchQueryParamWithSecondArrayValue(): void + { + $matcher = new QueryParamMatcher('name[]', 'def', []); + + $realRequest = $this->createRealRequest(queryParams: ['name' => ['abc', 'def']]); + + $result = $matcher($realRequest); + + self::assertInstanceOf(Hit::class, $result); + self::assertScore(5, $result); + self::assertMatcher('queryParam', $result); + } + + public function testMatchQueryParamWithArrayValue(): void + { + $matcher = new QueryParamMatcher('name', ['abc', 'def'], []); + + $realRequest = $this->createRealRequest(queryParams: ['name' => ['abc', 'def']]); + + $result = $matcher($realRequest); + + self::assertInstanceOf(Hit::class, $result); + self::assertScore(5, $result); + self::assertMatcher('queryParam', $result); + } + public function testMatchQueryParamWithPlaceholder(): void { $matcher = new QueryParamMatcher('filter', '%s%s', ['last', 'name']); diff --git a/tests/HttpClientMock/MockRequestBuilderCollectionTest.php b/tests/HttpClientMock/MockRequestBuilderCollectionTest.php index c4f407b..4dca0b1 100644 --- a/tests/HttpClientMock/MockRequestBuilderCollectionTest.php +++ b/tests/HttpClientMock/MockRequestBuilderCollectionTest.php @@ -73,6 +73,21 @@ public function setUp(): void ->queryParam('two', '2') ->willRespond(new MockResponseBuilder()), + 'get_uri_array_param1' => (new MockRequestBuilder()) + ->name('get_uri_array_param1') + ->method('GET') + ->uri('/uri') + ->queryParam('name[]', 'abc') + ->queryParam('name[]', 'def') + ->willRespond(new MockResponseBuilder()), + + 'get_uri_array_param2' => (new MockRequestBuilder()) + ->name('get_uri_array_param2') + ->method('GET') + ->uri('/uri') + ->queryParam('test', ['123', '456']) + ->willRespond(new MockResponseBuilder()), + 'post_uri_json' => (new MockRequestBuilder()) ->name('post_uri_json') ->method('POST') @@ -127,7 +142,7 @@ public function setUp(): void #[DataProvider('requests')] public function testRequestMatching(string $method, string $uri, array $options, string $index): void { - $x = ($this->collection)($method, $uri, $options); + ($this->collection)($method, $uri, $options); $expectedMockRequestBuilder = $this->builders[$index]; @@ -236,6 +251,8 @@ public static function requests(): array 'getUri' => ['GET', '/uri', [], 'get_uri'], 'getUriWithOneParam' => ['GET', '/uri?one=1', [], 'get_uri_param'], 'getUriWithTwoParams' => ['GET', '/uri?one=1&two=2', [], 'get_uri_params'], + 'getUriWithArrayParam1' => ['GET', '/uri?name[]=abc&name[]=def', [], 'get_uri_array_param1'], + 'getUriWithArrayParam2' => ['GET', '/uri?test[]=123&test[]=456', [], 'get_uri_array_param2'], 'postUri' => ['POST', '/uri', [], 'post'], 'postUriJson' => ['POST', '/uri', ['json' => ['json' => 'data']], 'post_uri_json'], 'postUriXml' => [ diff --git a/tests/HttpClientMock/RealRequestTrait.php b/tests/HttpClientMock/RealRequestTrait.php index 76038f3..34fe666 100644 --- a/tests/HttpClientMock/RealRequestTrait.php +++ b/tests/HttpClientMock/RealRequestTrait.php @@ -11,7 +11,7 @@ trait RealRequestTrait /** * @param array $headers * @param mixed[] $json - * @param array $queryParams + * @param array $queryParams * @param array $requestParams * @param array $multiparts */