Skip to content

Commit 959bf8d

Browse files
Stephan Wentzpl-github
authored andcommitted
fix: Add more robust query array parameter handling
1 parent 00c0f24 commit 959bf8d

File tree

9 files changed

+124
-21
lines changed

9 files changed

+124
-21
lines changed

src/HttpClientMock/Matcher/Hit.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Brainbits\FunctionalTestHelpers\HttpClientMock\Matcher;
66

7+
use function is_array;
8+
use function Safe\json_encode;
9+
710
final readonly class Hit
811
{
912
private function __construct(
@@ -34,9 +37,10 @@ public static function matchesHeader(string $key, string $value): self
3437
return new self('header', $key, 5, $value);
3538
}
3639

37-
public static function matchesQueryParam(string $key, string $value): self
40+
/** @param string|mixed[] $value */
41+
public static function matchesQueryParam(string $key, string|array $value): self
3842
{
39-
return new self('queryParam', $key, 5, $value);
43+
return new self('queryParam', $key, 5, is_array($value) ? json_encode($value) : $value);
4044
}
4145

4246
public static function matchesRequestParam(string $key, string $value): self

src/HttpClientMock/Matcher/Mismatch.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use SebastianBergmann\Diff\Differ;
99
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
1010

11+
use function is_array;
1112
use function Safe\json_encode;
1213

1314
use const PHP_EOL;
@@ -131,13 +132,17 @@ public static function mismatchingHeader(string $key, mixed $value, mixed $other
131132
);
132133
}
133134

134-
public static function mismatchingQueryParam(string $key, string $value, string $otherValue): self
135+
/**
136+
* @param string|mixed[] $value
137+
* @param string|mixed[] $otherValue
138+
*/
139+
public static function mismatchingQueryParam(string $key, string|array $value, string|array $otherValue): self
135140
{
136141
return new self(
137142
'queryParam',
138143
$key,
139-
$value,
140-
$otherValue,
144+
is_array($value) ? json_encode($value) : $value,
145+
is_array($otherValue) ? json_encode($otherValue) : $otherValue,
141146
);
142147
}
143148

src/HttpClientMock/Matcher/Missing.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Brainbits\FunctionalTestHelpers\HttpClientMock\Matcher;
66

7+
use function is_array;
8+
use function Safe\json_encode;
9+
710
final readonly class Missing
811
{
912
public int $score;
@@ -22,12 +25,13 @@ public static function missingHeader(string $key, string $expected): self
2225
);
2326
}
2427

25-
public static function missingQueryParam(string $key, string $expected): self
28+
/** @param string|mixed[] $expected */
29+
public static function missingQueryParam(string $key, string|array $expected): self
2630
{
2731
return new self(
2832
'queryParam',
2933
$key,
30-
$expected,
34+
is_array($expected) ? json_encode($expected) : $expected,
3135
);
3236
}
3337

src/HttpClientMock/Matcher/QueryParamMatcher.php

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,42 @@
66

77
use Brainbits\FunctionalTestHelpers\HttpClientMock\RealRequest;
88

9+
use function in_array;
10+
use function is_array;
11+
use function Safe\json_encode;
912
use function sprintf;
13+
use function str_ends_with;
14+
use function substr;
1015
use function vsprintf;
1116

1217
final readonly class QueryParamMatcher implements Matcher
1318
{
14-
private string $value;
19+
private string $key;
1520

16-
/** @param array<string> $placeholders */
17-
public function __construct(private string $key, string $value, array $placeholders)
21+
/** @var string|mixed[] */
22+
private string|array $value;
23+
24+
private bool $isArray;
25+
26+
/**
27+
* @param string|mixed[] $value
28+
* @param array<string> $placeholders
29+
*/
30+
public function __construct(string $key, string|array $value, array $placeholders)
1831
{
19-
$this->value = vsprintf($value, $placeholders);
32+
$isArray = false;
33+
if (str_ends_with($key, '[]')) {
34+
$key = substr($key, 0, -2);
35+
$isArray = true;
36+
}
37+
38+
if (!is_array($value)) {
39+
$value = vsprintf($value, $placeholders);
40+
}
41+
42+
$this->key = $key;
43+
$this->value = $value;
44+
$this->isArray = $isArray;
2045
}
2146

2247
public function __invoke(RealRequest $realRequest): Hit|Mismatch|Missing
@@ -28,7 +53,10 @@ public function __invoke(RealRequest $realRequest): Hit|Mismatch|Missing
2853
$expectedValue = $this->value;
2954
$realValue = $realRequest->getQueryParam($this->key);
3055

31-
if ($expectedValue !== $realValue) {
56+
if (
57+
(!$this->isArray && $expectedValue !== $realValue) ||
58+
($this->isArray && !in_array($expectedValue, $realValue, true))
59+
) {
3260
return Mismatch::mismatchingQueryParam($this->key, $expectedValue, $realValue);
3361
}
3462

@@ -37,6 +65,10 @@ public function __invoke(RealRequest $realRequest): Hit|Mismatch|Missing
3765

3866
public function __toString(): string
3967
{
40-
return sprintf('request.queryParams["%s"] === "%s"', $this->key, $this->value);
68+
return sprintf(
69+
'request.queryParams["%s"] === "%s"',
70+
$this->key,
71+
is_array($this->value) ? json_encode($this->value) : $this->value,
72+
);
4173
}
4274
}

src/HttpClientMock/MockRequestBuilder.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,19 @@ public function xml(string|callable $xml): self
187187
return $this;
188188
}
189189

190-
public function queryParam(string $key, string $value, string ...$placeholders): self
190+
/** @param string|mixed[] $value */
191+
public function queryParam(string $key, string|array $value, string ...$placeholders): self
191192
{
192193
$this->matchers['queryParams'] ??= [];
193-
$this->matchers['queryParams'][$key] = new QueryParamMatcher($key, $value, $placeholders);
194+
$this->matchers['queryParams'][] = new QueryParamMatcher($key, $value, $placeholders);
194195

195196
return $this;
196197
}
197198

198199
public function requestParam(string $key, string $value): self
199200
{
200201
$this->matchers['requestParams'] ??= [];
201-
$this->matchers['requestParams'][$key] = new RequestParamMatcher($key, $value);
202+
$this->matchers['requestParams'][] = new RequestParamMatcher($key, $value);
202203

203204
/*
204205
if ((string) $this->content !== '') {
@@ -218,7 +219,7 @@ public function multipart(
218219
string|null $content = null,
219220
): self {
220221
$this->matchers['multiparts'] ??= [];
221-
$this->matchers['multiparts'][$name] = new MultipartMatcher($name, $mimetype, $filename, $content);
222+
$this->matchers['multiparts'][] = new MultipartMatcher($name, $mimetype, $filename, $content);
222223

223224
return $this;
224225
}

src/HttpClientMock/RealRequest.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ final class RealRequest
2222
/**
2323
* @param array<string, string> $headers
2424
* @param mixed[] $json
25-
* @param array<string, string> $queryParams
25+
* @param array<string, string|mixed[]> $queryParams
2626
* @param array<string, string> $requestParams
2727
* @param array<string, array{name: string, filename?: string, mimetype?: string, content?: string}> $multiparts
2828
*/
@@ -84,7 +84,8 @@ public function hasQueryParam(string $key): bool
8484
return array_key_exists($key, $this->queryParams);
8585
}
8686

87-
public function getQueryParam(string $key): string|null
87+
/** @return string|mixed[]|null */
88+
public function getQueryParam(string $key): string|array|null
8889
{
8990
return $this->queryParams[$key] ?? null;
9091
}

tests/HttpClientMock/Matcher/QueryParamMatcherTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,45 @@ public function testMatchQueryParamWithZeroValue(): void
4747
self::assertMatcher('queryParam', $result);
4848
}
4949

50+
public function testMatchQueryParamWithFirstArrayValue(): void
51+
{
52+
$matcher = new QueryParamMatcher('name[]', 'abc', []);
53+
54+
$realRequest = $this->createRealRequest(queryParams: ['name' => ['abc', 'def']]);
55+
56+
$result = $matcher($realRequest);
57+
58+
self::assertInstanceOf(Hit::class, $result);
59+
self::assertScore(5, $result);
60+
self::assertMatcher('queryParam', $result);
61+
}
62+
63+
public function testMatchQueryParamWithSecondArrayValue(): void
64+
{
65+
$matcher = new QueryParamMatcher('name[]', 'def', []);
66+
67+
$realRequest = $this->createRealRequest(queryParams: ['name' => ['abc', 'def']]);
68+
69+
$result = $matcher($realRequest);
70+
71+
self::assertInstanceOf(Hit::class, $result);
72+
self::assertScore(5, $result);
73+
self::assertMatcher('queryParam', $result);
74+
}
75+
76+
public function testMatchQueryParamWithArrayValue(): void
77+
{
78+
$matcher = new QueryParamMatcher('name', ['abc', 'def'], []);
79+
80+
$realRequest = $this->createRealRequest(queryParams: ['name' => ['abc', 'def']]);
81+
82+
$result = $matcher($realRequest);
83+
84+
self::assertInstanceOf(Hit::class, $result);
85+
self::assertScore(5, $result);
86+
self::assertMatcher('queryParam', $result);
87+
}
88+
5089
public function testMatchQueryParamWithPlaceholder(): void
5190
{
5291
$matcher = new QueryParamMatcher('filter', '%s%s', ['last', 'name']);

tests/HttpClientMock/MockRequestBuilderCollectionTest.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ public function setUp(): void
7373
->queryParam('two', '2')
7474
->willRespond(new MockResponseBuilder()),
7575

76+
'get_uri_array_param1' => (new MockRequestBuilder())
77+
->name('get_uri_array_param1')
78+
->method('GET')
79+
->uri('/uri')
80+
->queryParam('name[]', 'abc')
81+
->queryParam('name[]', 'def')
82+
->willRespond(new MockResponseBuilder()),
83+
84+
'get_uri_array_param2' => (new MockRequestBuilder())
85+
->name('get_uri_array_param2')
86+
->method('GET')
87+
->uri('/uri')
88+
->queryParam('test', ['123', '456'])
89+
->willRespond(new MockResponseBuilder()),
90+
7691
'post_uri_json' => (new MockRequestBuilder())
7792
->name('post_uri_json')
7893
->method('POST')
@@ -127,7 +142,7 @@ public function setUp(): void
127142
#[DataProvider('requests')]
128143
public function testRequestMatching(string $method, string $uri, array $options, string $index): void
129144
{
130-
$x = ($this->collection)($method, $uri, $options);
145+
($this->collection)($method, $uri, $options);
131146

132147
$expectedMockRequestBuilder = $this->builders[$index];
133148

@@ -236,6 +251,8 @@ public static function requests(): array
236251
'getUri' => ['GET', '/uri', [], 'get_uri'],
237252
'getUriWithOneParam' => ['GET', '/uri?one=1', [], 'get_uri_param'],
238253
'getUriWithTwoParams' => ['GET', '/uri?one=1&two=2', [], 'get_uri_params'],
254+
'getUriWithArrayParam1' => ['GET', '/uri?name[]=abc&name[]=def', [], 'get_uri_array_param1'],
255+
'getUriWithArrayParam2' => ['GET', '/uri?test[]=123&test[]=456', [], 'get_uri_array_param2'],
239256
'postUri' => ['POST', '/uri', [], 'post'],
240257
'postUriJson' => ['POST', '/uri', ['json' => ['json' => 'data']], 'post_uri_json'],
241258
'postUriXml' => [

tests/HttpClientMock/RealRequestTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ trait RealRequestTrait
1111
/**
1212
* @param array<string, string> $headers
1313
* @param mixed[] $json
14-
* @param array<string, string> $queryParams
14+
* @param array<string, string|mixed[]> $queryParams
1515
* @param array<string, string> $requestParams
1616
* @param array<string, array{name: string, filename?: string, mimetype?: string, content?: string}> $multiparts
1717
*/

0 commit comments

Comments
 (0)