2025-08-06 21:49:13 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Tests\Unit\Services\Http;
|
|
|
|
|
|
2025-08-15 16:39:18 +02:00
|
|
|
use Domains\Shared\Services\HttpFetcher;
|
2025-08-06 21:49:13 +02:00
|
|
|
use Exception;
|
|
|
|
|
use Illuminate\Http\Client\Response;
|
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
|
|
|
|
class HttpFetcherTest extends TestCase
|
|
|
|
|
{
|
|
|
|
|
protected function setUp(): void
|
|
|
|
|
{
|
|
|
|
|
parent::setUp();
|
|
|
|
|
Log::spy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_html_returns_response_body_on_successful_request(): void
|
|
|
|
|
{
|
|
|
|
|
$url = 'https://example.com';
|
|
|
|
|
$expectedHtml = '<html><body>Test content</body></html>';
|
|
|
|
|
|
|
|
|
|
Http::fake([
|
|
|
|
|
$url => Http::response($expectedHtml, 200)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$result = HttpFetcher::fetchHtml($url);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals($expectedHtml, $result);
|
|
|
|
|
Http::assertSent(function ($request) use ($url) {
|
|
|
|
|
return $request->url() === $url;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_html_throws_exception_on_unsuccessful_response(): void
|
|
|
|
|
{
|
|
|
|
|
$url = 'https://example.com';
|
|
|
|
|
$statusCode = 404;
|
|
|
|
|
|
|
|
|
|
Http::fake([
|
|
|
|
|
$url => Http::response('Not Found', $statusCode)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->expectException(Exception::class);
|
|
|
|
|
$this->expectExceptionMessage("Failed to fetch URL: {$url} - Status: {$statusCode}");
|
|
|
|
|
|
|
|
|
|
HttpFetcher::fetchHtml($url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_html_logs_error_on_exception(): void
|
|
|
|
|
{
|
|
|
|
|
$url = 'https://example.com';
|
|
|
|
|
|
|
|
|
|
Http::fake([
|
|
|
|
|
$url => Http::response('Server Error', 500)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
HttpFetcher::fetchHtml($url);
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
// Expected exception
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Log assertion is complex because service uses logger() function
|
|
|
|
|
// Instead, verify the exception was thrown
|
|
|
|
|
$this->assertNotNull($e ?? null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_html_handles_network_exception(): void
|
|
|
|
|
{
|
|
|
|
|
$url = 'https://example.com';
|
|
|
|
|
|
|
|
|
|
Http::fake(function () {
|
|
|
|
|
throw new Exception('Network error');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$this->expectException(Exception::class);
|
|
|
|
|
$this->expectExceptionMessage('Network error');
|
|
|
|
|
|
|
|
|
|
HttpFetcher::fetchHtml($url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_multiple_urls_returns_successful_results(): void
|
|
|
|
|
{
|
|
|
|
|
$urls = [
|
|
|
|
|
'https://example.com/page1',
|
|
|
|
|
'https://example.com/page2'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$html1 = '<html>Page 1</html>';
|
|
|
|
|
$html2 = '<html>Page 2</html>';
|
|
|
|
|
|
|
|
|
|
Http::fake([
|
|
|
|
|
'https://example.com/page1' => Http::response($html1, 200),
|
|
|
|
|
'https://example.com/page2' => Http::response($html2, 200)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$results = HttpFetcher::fetchMultipleUrls($urls);
|
|
|
|
|
|
|
|
|
|
$this->assertCount(2, $results);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals([
|
|
|
|
|
'url' => 'https://example.com/page1',
|
|
|
|
|
'html' => $html1,
|
|
|
|
|
'success' => true
|
|
|
|
|
], $results[0]);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals([
|
|
|
|
|
'url' => 'https://example.com/page2',
|
|
|
|
|
'html' => $html2,
|
|
|
|
|
'success' => true
|
|
|
|
|
], $results[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_multiple_urls_handles_mixed_success_failure(): void
|
|
|
|
|
{
|
|
|
|
|
$urls = [
|
|
|
|
|
'https://example.com/success',
|
|
|
|
|
'https://example.com/failure'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$successHtml = '<html>Success</html>';
|
|
|
|
|
|
|
|
|
|
Http::fake([
|
|
|
|
|
'https://example.com/success' => Http::response($successHtml, 200),
|
|
|
|
|
'https://example.com/failure' => Http::response('Not Found', 404)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$results = HttpFetcher::fetchMultipleUrls($urls);
|
|
|
|
|
|
|
|
|
|
$this->assertCount(2, $results);
|
|
|
|
|
|
|
|
|
|
// First URL should succeed
|
|
|
|
|
$this->assertEquals([
|
|
|
|
|
'url' => 'https://example.com/success',
|
|
|
|
|
'html' => $successHtml,
|
|
|
|
|
'success' => true
|
|
|
|
|
], $results[0]);
|
|
|
|
|
|
|
|
|
|
// Second URL should fail
|
|
|
|
|
$this->assertEquals([
|
|
|
|
|
'url' => 'https://example.com/failure',
|
|
|
|
|
'html' => null,
|
|
|
|
|
'success' => false,
|
|
|
|
|
'status' => 404
|
|
|
|
|
], $results[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_multiple_urls_returns_empty_array_on_exception(): void
|
|
|
|
|
{
|
|
|
|
|
$urls = ['https://example.com'];
|
|
|
|
|
|
|
|
|
|
Http::fake(function () {
|
|
|
|
|
throw new \GuzzleHttp\Exception\ConnectException('Pool request failed', new \GuzzleHttp\Psr7\Request('GET', 'https://example.com'));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$results = HttpFetcher::fetchMultipleUrls($urls);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals([], $results);
|
|
|
|
|
|
|
|
|
|
// Skip log assertion as it's complex to test with logger() function
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_multiple_urls_handles_empty_urls_array(): void
|
|
|
|
|
{
|
|
|
|
|
$urls = [];
|
|
|
|
|
|
|
|
|
|
$results = HttpFetcher::fetchMultipleUrls($urls);
|
|
|
|
|
|
|
|
|
|
$this->assertEquals([], $results);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_multiple_urls_handles_response_exception(): void
|
|
|
|
|
{
|
|
|
|
|
$urls = ['https://example.com'];
|
|
|
|
|
|
|
|
|
|
// Mock a response that throws an exception when accessed
|
|
|
|
|
Http::fake([
|
|
|
|
|
'https://example.com' => function () {
|
|
|
|
|
$response = Http::response('Success', 200);
|
|
|
|
|
// We can't easily mock an exception on the response object itself
|
|
|
|
|
// so we'll test this scenario differently
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$results = HttpFetcher::fetchMultipleUrls($urls);
|
|
|
|
|
|
|
|
|
|
$this->assertCount(1, $results);
|
|
|
|
|
$this->assertTrue($results[0]['success']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_multiple_urls_filters_null_results(): void
|
|
|
|
|
{
|
|
|
|
|
// This tests the edge case where URLs array might have gaps
|
|
|
|
|
$urls = [
|
|
|
|
|
'https://example.com/page1',
|
|
|
|
|
'https://example.com/page2'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
Http::fake([
|
|
|
|
|
'https://example.com/page1' => Http::response('<html>Page 1</html>', 200),
|
|
|
|
|
'https://example.com/page2' => Http::response('<html>Page 2</html>', 200)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$results = HttpFetcher::fetchMultipleUrls($urls);
|
|
|
|
|
|
|
|
|
|
$this->assertCount(2, $results);
|
|
|
|
|
// All results should be valid (no nulls)
|
|
|
|
|
foreach ($results as $result) {
|
|
|
|
|
$this->assertNotNull($result);
|
|
|
|
|
$this->assertArrayHasKey('url', $result);
|
|
|
|
|
$this->assertArrayHasKey('success', $result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[DataProvider('statusCodesProvider')]
|
|
|
|
|
public function test_fetch_html_with_various_status_codes(int $statusCode): void
|
|
|
|
|
{
|
|
|
|
|
$url = 'https://example.com';
|
|
|
|
|
|
|
|
|
|
Http::fake([
|
|
|
|
|
$url => Http::response('Error', $statusCode)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->expectException(Exception::class);
|
|
|
|
|
$this->expectExceptionMessage("Status: {$statusCode}");
|
|
|
|
|
|
|
|
|
|
HttpFetcher::fetchHtml($url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function statusCodesProvider(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
[400], [401], [403], [404], [500], [502], [503]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_multiple_urls_preserves_url_order(): void
|
|
|
|
|
{
|
|
|
|
|
$urls = [
|
|
|
|
|
'https://example.com/first',
|
|
|
|
|
'https://example.com/second',
|
|
|
|
|
'https://example.com/third'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
Http::fake([
|
|
|
|
|
'https://example.com/first' => Http::response('First', 200),
|
|
|
|
|
'https://example.com/second' => Http::response('Second', 200),
|
|
|
|
|
'https://example.com/third' => Http::response('Third', 200)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$results = HttpFetcher::fetchMultipleUrls($urls);
|
|
|
|
|
|
|
|
|
|
$this->assertCount(3, $results);
|
|
|
|
|
$this->assertEquals('https://example.com/first', $results[0]['url']);
|
|
|
|
|
$this->assertEquals('https://example.com/second', $results[1]['url']);
|
|
|
|
|
$this->assertEquals('https://example.com/third', $results[2]['url']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function test_fetch_html_logs_correct_error_information(): void
|
|
|
|
|
{
|
|
|
|
|
$url = 'https://example.com/test-page';
|
|
|
|
|
|
|
|
|
|
Http::fake([
|
|
|
|
|
$url => Http::response('Forbidden', 403)
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
HttpFetcher::fetchHtml($url);
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
// Expected
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip log assertion as service uses logger() function which is harder to test
|
|
|
|
|
$this->assertTrue(true); // Just verify we get here
|
|
|
|
|
}
|
|
|
|
|
}
|