Skip to content

Commit be140a3

Browse files
committed
Add utilities for HTTP testing: FileManager, NetworkSimulator, RequestExecutor, RequestMatcher, RequestRecorder, and ResponseFactory
- Implemented FileManager for temporary file and directory management during tests. - Created NetworkSimulator to simulate various network conditions and failures. - Developed RequestExecutor to handle sending requests and managing responses, including caching and retries. - Introduced RequestMatcher to find matching mocks for requests. - Added RequestRecorder to keep track of recorded requests during tests. - Built ResponseFactory to create mocked responses and handle network simulation. - Updated test14.php to utilize the new utilities for cookie handling and session management.
1 parent 795b3c5 commit be140a3

12 files changed

+240
-106
lines changed

src/Api/HttpTestingAssistant.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use Rcalicdan\FiberAsync\Http\Interfaces\CookieJarInterface;
66
use Rcalicdan\FiberAsync\Http\Testing\MockRequestBuilder;
7-
use Rcalicdan\FiberAsync\Http\Testing\Services\CookieManager;
7+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\CookieManager;
88
use Rcalicdan\FiberAsync\Http\Testing\TestingHttpHandler;
99

1010
/**

src/Http/Testing/MockRequestBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Rcalicdan\FiberAsync\Http\Testing;
44

5-
use Rcalicdan\FiberAsync\Http\Testing\Services\CookieManager;
5+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\CookieManager;
66

77
/**
88
* Builder for creating mocked requests with fluent interface.

src/Http/Testing/TestingHttpHandler.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
use Rcalicdan\FiberAsync\Http\Interfaces\CookieJarInterface;
88
use Rcalicdan\FiberAsync\Http\RetryConfig;
99
use Rcalicdan\FiberAsync\Http\Testing\Exceptions\MockAssertionException;
10-
use Rcalicdan\FiberAsync\Http\Testing\Services\CacheManager;
11-
use Rcalicdan\FiberAsync\Http\Testing\Services\CookieManager;
12-
use Rcalicdan\FiberAsync\Http\Testing\Services\FileManager;
13-
use Rcalicdan\FiberAsync\Http\Testing\Services\NetworkSimulator;
14-
use Rcalicdan\FiberAsync\Http\Testing\Services\RequestExecutor;
15-
use Rcalicdan\FiberAsync\Http\Testing\Services\RequestMatcher;
16-
use Rcalicdan\FiberAsync\Http\Testing\Services\RequestRecorder;
17-
use Rcalicdan\FiberAsync\Http\Testing\Services\ResponseFactory;
10+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\CacheManager;
11+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\CookieManager;
12+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\FileManager;
13+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\NetworkSimulator;
14+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\RequestExecutor;
15+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\RequestMatcher;
16+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\RequestRecorder;
17+
use Rcalicdan\FiberAsync\Http\Testing\Utilities\ResponseFactory;
1818
use Rcalicdan\FiberAsync\Http\Traits\FetchOptionTrait;
1919
use Rcalicdan\FiberAsync\Promise\Interfaces\CancellablePromiseInterface;
2020
use Rcalicdan\FiberAsync\Promise\Interfaces\PromiseInterface;

src/Http/Testing/Services/CacheManager.php renamed to src/Http/Testing/Utilities/CacheManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Rcalicdan\FiberAsync\Http\Testing\Services;
3+
namespace Rcalicdan\FiberAsync\Http\Testing\Utilities;
44

55
use Psr\SimpleCache\CacheInterface;
66
use Rcalicdan\FiberAsync\Http\CacheConfig;

src/Http/Testing/Services/CookieManager.php renamed to src/Http/Testing/Utilities/CookieManager.php

Lines changed: 146 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Rcalicdan\FiberAsync\Http\Testing\Services;
3+
namespace Rcalicdan\FiberAsync\Http\Testing\Utilities;
44

55
use Rcalicdan\FiberAsync\Http\Cookie;
66
use Rcalicdan\FiberAsync\Http\CookieJar;
@@ -162,16 +162,16 @@ public function mockSetCookies(MockedRequest $mock, array $cookies): void
162162
if (is_string($config)) {
163163
$mock->addResponseHeader('Set-Cookie', "{$name}={$config}; Path=/");
164164
} elseif (is_array($config)) {
165-
$setCookieValue = $name.'='.($config['value'] ?? '');
165+
$setCookieValue = $name . '=' . ($config['value'] ?? '');
166166

167167
if (isset($config['path'])) {
168-
$setCookieValue .= '; Path='.$config['path'];
168+
$setCookieValue .= '; Path=' . $config['path'];
169169
}
170170
if (isset($config['domain'])) {
171-
$setCookieValue .= '; Domain='.$config['domain'];
171+
$setCookieValue .= '; Domain=' . $config['domain'];
172172
}
173173
if (isset($config['expires'])) {
174-
$setCookieValue .= '; Expires='.gmdate('D, d M Y H:i:s T', $config['expires']);
174+
$setCookieValue .= '; Expires=' . gmdate('D, d M Y H:i:s T', $config['expires']);
175175
}
176176
if ($config['secure'] ?? false) {
177177
$setCookieValue .= '; Secure';
@@ -180,7 +180,7 @@ public function mockSetCookies(MockedRequest $mock, array $cookies): void
180180
$setCookieValue .= '; HttpOnly';
181181
}
182182
if (isset($config['sameSite'])) {
183-
$setCookieValue .= '; SameSite='.$config['sameSite'];
183+
$setCookieValue .= '; SameSite=' . $config['sameSite'];
184184
}
185185

186186
$mock->addResponseHeader('Set-Cookie', $setCookieValue);
@@ -260,7 +260,7 @@ public function assertCookieSent(string $name, array $curlOptions): void
260260
}
261261

262262
if (! isset($cookies[$name])) {
263-
throw new MockAssertionException("Cookie '{$name}' was not sent in request. Sent cookies: ".implode(', ', array_keys($cookies)));
263+
throw new MockAssertionException("Cookie '{$name}' was not sent in request. Sent cookies: " . implode(', ', array_keys($cookies)));
264264
}
265265
}
266266

@@ -311,15 +311,15 @@ public function applyCookiesToCurlOptions(array &$curlOptions, string $url, stri
311311
$cookieHeaderExists = false;
312312
foreach ($curlOptions[CURLOPT_HTTPHEADER] as &$header) {
313313
if (str_starts_with(strtolower($header), 'cookie:')) {
314-
$header .= '; '.$cookieHeader;
314+
$header .= '; ' . $cookieHeader;
315315
$cookieHeaderExists = true;
316316

317317
break;
318318
}
319319
}
320320

321321
if (! $cookieHeaderExists) {
322-
$curlOptions[CURLOPT_HTTPHEADER][] = 'Cookie: '.$cookieHeader;
322+
$curlOptions[CURLOPT_HTTPHEADER][] = 'Cookie: ' . $cookieHeader;
323323
}
324324
}
325325
}
@@ -358,7 +358,7 @@ public function processSetCookieHeaders(array $headers, string $jarName = 'defau
358358
*/
359359
public function createTempCookieFile(string $prefix = 'test_cookies_'): string
360360
{
361-
$filename = sys_get_temp_dir().DIRECTORY_SEPARATOR.$prefix.uniqid().'.json';
361+
$filename = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $prefix . uniqid() . '.json';
362362

363363
if ($this->autoManage) {
364364
$this->createdCookieFiles[] = $filename;
@@ -415,4 +415,140 @@ public function getDebugInfo(): array
415415

416416
return $info;
417417
}
418+
419+
/**
420+
* Apply cookies to curl options, honoring a custom jar in $curlOptions['_cookie_jar'] or falling back to the default jar.
421+
*/
422+
public function applyCookiesForRequestOptions(array &$curlOptions, string $url): void
423+
{
424+
$jarOption = $curlOptions['_cookie_jar'] ?? null;
425+
426+
if ($jarOption instanceof CookieJarInterface) {
427+
$uri = new Uri($url);
428+
$cookieHeader = $jarOption->getCookieHeader(
429+
$uri->getHost(),
430+
$uri->getPath() !== '' ? $uri->getPath() : '/',
431+
$uri->getScheme() === 'https'
432+
);
433+
434+
if ($cookieHeader === '') {
435+
return;
436+
}
437+
438+
$curlOptions[CURLOPT_HTTPHEADER] = $curlOptions[CURLOPT_HTTPHEADER] ?? [];
439+
440+
foreach ($curlOptions[CURLOPT_HTTPHEADER] as &$header) {
441+
if (str_starts_with(strtolower($header), 'cookie:')) {
442+
$header .= '; ' . $cookieHeader;
443+
return;
444+
}
445+
}
446+
447+
$curlOptions[CURLOPT_HTTPHEADER][] = 'Cookie: ' . $cookieHeader;
448+
return;
449+
}
450+
451+
// If jar name provided, use stored jar if available
452+
if (is_string($jarOption)) {
453+
$jar = $this->getCookieJar($jarOption);
454+
if ($jar === null) {
455+
return;
456+
}
457+
458+
$uri = new Uri($url);
459+
$cookieHeader = $jar->getCookieHeader(
460+
$uri->getHost(),
461+
$uri->getPath() !== '' ? $uri->getPath() : '/',
462+
$uri->getScheme() === 'https'
463+
);
464+
465+
if ($cookieHeader === '') {
466+
return;
467+
}
468+
469+
$curlOptions[CURLOPT_HTTPHEADER] = $curlOptions[CURLOPT_HTTPHEADER] ?? [];
470+
471+
foreach ($curlOptions[CURLOPT_HTTPHEADER] as &$header) {
472+
if (str_starts_with(strtolower($header), 'cookie:')) {
473+
$header .= '; ' . $cookieHeader;
474+
return;
475+
}
476+
}
477+
478+
$curlOptions[CURLOPT_HTTPHEADER][] = 'Cookie: ' . $cookieHeader;
479+
return;
480+
}
481+
482+
$this->applyCookiesToCurlOptions($curlOptions, $url);
483+
}
484+
485+
/**
486+
* Process Set-Cookie headers and apply them to both the shared manager (default jar)
487+
* and to a custom jar passed in $curlOptions['_cookie_jar'] (instance or jar name).
488+
*
489+
* Keeps the same domain-inference behavior as before: if the parsed cookie lacks a domain,
490+
* it will be set to the request host (if available).
491+
*/
492+
public function processResponseCookiesForOptions(array $headers, array $curlOptions, string $url): void
493+
{
494+
$this->processSetCookieHeaders($headers, 'default');
495+
496+
$jarOption = $curlOptions['_cookie_jar'] ?? null;
497+
if ($jarOption === null) {
498+
return;
499+
}
500+
501+
$customJar = null;
502+
if ($jarOption instanceof CookieJarInterface) {
503+
$customJar = $jarOption;
504+
} elseif (is_string($jarOption)) {
505+
$customJar = $this->getCookieJar($jarOption);
506+
}
507+
508+
if ($customJar === null) {
509+
return;
510+
}
511+
512+
$setCookieHeaders = [];
513+
foreach ($headers as $name => $value) {
514+
if (strtolower($name) !== 'set-cookie') {
515+
continue;
516+
}
517+
if (is_array($value)) {
518+
$setCookieHeaders = array_merge($setCookieHeaders, $value);
519+
continue;
520+
}
521+
$setCookieHeaders[] = $value;
522+
}
523+
524+
if (empty($setCookieHeaders)) {
525+
return;
526+
}
527+
528+
$uri = new Uri($url);
529+
$requestDomain = $uri->getHost() ?? '';
530+
531+
foreach ($setCookieHeaders as $setCookieHeader) {
532+
$cookie = Cookie::fromSetCookieHeader($setCookieHeader);
533+
if ($cookie === null) {
534+
continue;
535+
}
536+
537+
if (empty($cookie->getDomain()) && $requestDomain !== '') {
538+
$cookie = new Cookie(
539+
$cookie->getName(),
540+
$cookie->getValue(),
541+
$cookie->getExpires(),
542+
$requestDomain,
543+
$cookie->getPath(),
544+
$cookie->isSecure(),
545+
$cookie->isHttpOnly(),
546+
$cookie->getMaxAge(),
547+
$cookie->getSameSite()
548+
);
549+
}
550+
551+
$customJar->setCookie($cookie);
552+
}
553+
}
418554
}

src/Http/Testing/Services/FileManager.php renamed to src/Http/Testing/Utilities/FileManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Rcalicdan\FiberAsync\Http\Testing\Services;
3+
namespace Rcalicdan\FiberAsync\Http\Testing\Utilities;
44

55
use Exception;
66

src/Http/Testing/Services/NetworkSimulator.php renamed to src/Http/Testing/Utilities/NetworkSimulator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Rcalicdan\FiberAsync\Http\Testing\Services;
3+
namespace Rcalicdan\FiberAsync\Http\Testing\Utilities;
44

55
class NetworkSimulator
66
{

src/Http/Testing/Services/RequestExecutor.php renamed to src/Http/Testing/Utilities/RequestExecutor.php

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Rcalicdan\FiberAsync\Http\Testing\Services;
3+
namespace Rcalicdan\FiberAsync\Http\Testing\Utilities;
44

55
use Rcalicdan\FiberAsync\Http\CacheConfig;
66
use Rcalicdan\FiberAsync\Http\Handlers\HttpHandler;
@@ -50,18 +50,14 @@ public function executeSendRequest(
5050
?RetryConfig $retryConfig = null,
5151
?callable $parentSendRequest = null
5252
): PromiseInterface {
53-
if (! isset($curlOptions['_cookie_jar'])) {
54-
$this->cookieManager->applyCookiesToCurlOptions($curlOptions, $url);
55-
}
53+
$this->cookieManager->applyCookiesForRequestOptions($curlOptions, $url);
5654

5755
$method = $curlOptions[CURLOPT_CUSTOMREQUEST] ?? 'GET';
5856

59-
// Handle caching for GET requests
6057
if ($cacheConfig !== null && $method === 'GET') {
6158
$cachedResponse = $this->cacheManager->getCachedResponse($url, $cacheConfig);
6259
if ($cachedResponse !== null) {
6360
$this->requestRecorder->recordRequest('GET (FROM CACHE)', $url, $curlOptions);
64-
6561
return Promise::resolved($cachedResponse);
6662
}
6763
}
@@ -75,31 +71,30 @@ public function executeSendRequest(
7571
$parentSendRequest
7672
);
7773

78-
// Process cookies from response
79-
if ($promise instanceof PromiseInterface) {
80-
$promise = $promise->then(function ($response) {
81-
if ($response instanceof Response) {
82-
$this->cookieManager->processSetCookieHeaders($response->getHeaders());
83-
}
84-
85-
return $response;
86-
});
74+
if (!($promise instanceof PromiseInterface)) {
75+
$promise = Promise::resolved($promise);
8776
}
8877

89-
// Cache successful responses
90-
if ($cacheConfig !== null) {
91-
return $promise->then(function ($response) use ($cacheConfig, $url) {
92-
if ($response instanceof Response && $response->ok()) {
93-
$this->cacheManager->cacheResponse($url, $response, $cacheConfig);
94-
}
78+
$promise = $promise->then(function ($response) use ($curlOptions, $url) {
79+
if ($response instanceof Response) {
80+
$this->cookieManager->processResponseCookiesForOptions($response->getHeaders(), $curlOptions, $url);
81+
}
82+
return $response;
83+
});
9584

96-
return $response;
97-
});
85+
if ($cacheConfig === null) {
86+
return $promise;
9887
}
9988

100-
return $promise;
89+
return $promise->then(function ($response) use ($cacheConfig, $url) {
90+
if ($response instanceof Response && $response->ok()) {
91+
$this->cacheManager->cacheResponse($url, $response, $cacheConfig);
92+
}
93+
return $response;
94+
});
10195
}
10296

97+
10398
public function executeFetch(
10499
string $url,
105100
array $options,
@@ -266,11 +261,11 @@ function ($successfulResponse) use ($options, $finalPromise) {
266261
$finalPromise->resolve($successfulResponse);
267262
}
268263
},
269-
fn ($reason) => $finalPromise->reject($reason)
264+
fn($reason) => $finalPromise->reject($reason)
270265
);
271266

272267
if ($retryPromise instanceof CancellablePromiseInterface) {
273-
$finalPromise->setCancelHandler(fn () => $retryPromise->cancel());
268+
$finalPromise->setCancelHandler(fn() => $retryPromise->cancel());
274269
}
275270

276271
return $finalPromise;

src/Http/Testing/Services/RequestMatcher.php renamed to src/Http/Testing/Utilities/RequestMatcher.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Rcalicdan\FiberAsync\Http\Testing\Services;
3+
namespace Rcalicdan\FiberAsync\Http\Testing\Utilities;
44

55
use Rcalicdan\FiberAsync\Http\Testing\RecordedRequest;
66

src/Http/Testing/Services/RequestRecorder.php renamed to src/Http/Testing/Utilities/RequestRecorder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace Rcalicdan\FiberAsync\Http\Testing\Services;
3+
namespace Rcalicdan\FiberAsync\Http\Testing\Utilities;
44

55
use Rcalicdan\FiberAsync\Http\Testing\RecordedRequest;
66

0 commit comments

Comments
 (0)