packages/requirements/tests/Unit/Source/DownloadTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Source;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\Small;
10use PHPUnit\Framework\TestCase;
11use Requirements\Source\Download;
12use RuntimeException;
13use Symfony\Component\HttpClient\MockHttpClient;
14use Symfony\Component\HttpClient\Response\MockResponse;
15use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
16use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
17
18#[CoversClass(Download::class)]
19#[Small]
20final class DownloadTest extends TestCase
21{
22 /**
23 * @throws ExceptionInterface
24 */
25 public function testGetReturnsTheBody(): void
26 {
27 $download = new Download(new MockHttpClient(new MockResponse(['Names shall ', 'start with a letter.'])));
28
29 self::assertSame('Names shall start with a letter.', $download->get('https://example.org/manual'));
30 }
31
32 /**
33 * @throws ExceptionInterface
34 */
35 public function testGetSendsABoundedRequest(): void
36 {
37 $response = new MockResponse('Source text.');
38 (new Download(new MockHttpClient($response)))->get('https://example.org/manual');
39 $options = $response->getRequestOptions();
40
41 self::assertSame('GET', $response->getRequestMethod());
42 self::assertSame('https://example.org/manual', $response->getRequestUrl());
43 self::assertEquals(20, $options['timeout']);
44 self::assertEquals(20, $options['max_duration']);
45 self::assertSame(5, $options['max_redirects']);
46 self::assertFalse($options['buffer']);
47 self::assertIsArray($options['headers']);
48 self::assertContains('User-Agent: requirements/1', $options['headers']);
49 }
50
51 /**
52 * @throws ExceptionInterface
53 */
54 #[DataProvider('providerGetSuccessfulStatus')]
55 public function testGetAcceptsSuccessfulStatus(int $status): void
56 {
57 self::assertSame('Source text.', (new Download(new MockHttpClient(new MockResponse('Source text.', ['http_code' => $status]))))->get('https://example.org/manual'));
58 }
59
60 /**
61 * @return array<string, array{int}>
62 */
63 public static function providerGetSuccessfulStatus(): array
64 {
65 return [
66 'ok' => [200],
67 'non-authoritative' => [203],
68 'last success' => [299],
69 ];
70 }
71
72 /**
73 * @throws ExceptionInterface
74 */
75 #[DataProvider('providerGetErrorStatus')]
76 public function testGetRejectsOtherStatus(int $status): void
77 {
78 $this->expectException(RuntimeException::class);
79 $this->expectExceptionMessage("HTTP $status");
80 (new Download(new MockHttpClient(new MockResponse('Names shall start with a letter.', ['http_code' => $status]))))->get('https://example.org/manual');
81 }
82
83 /**
84 * @return array<string, array{int}>
85 */
86 public static function providerGetErrorStatus(): array
87 {
88 return [
89 'redirect' => [300],
90 'not modified' => [304],
91 'not found' => [404],
92 'server error' => [500],
93 ];
94 }
95
96 /**
97 * @throws ExceptionInterface
98 */
99 public function testGetAcceptsTheSizeLimit(): void
100 {
101 self::assertSame(16777216, strlen((new Download(new MockHttpClient(new MockResponse(str_repeat('x', 16777216)))))->get('https://example.org/manual')));
102 }
103
104 /**
105 * @throws ExceptionInterface
106 */
107 public function testGetRejectsBodiesOverTheSizeLimit(): void
108 {
109 $this->expectException(RuntimeException::class);
110 $this->expectExceptionMessage('16 MiB size limit exceeded.');
111 (new Download(new MockHttpClient(new MockResponse([str_repeat('x', 16777216), 'x']))))->get('https://example.org/manual');
112 }
113
114 /**
115 * @throws ExceptionInterface
116 */
117 public function testGetPropagatesTransportFailures(): void
118 {
119 $this->expectException(TransportExceptionInterface::class);
120 (new Download(new MockHttpClient(new MockResponse('', ['error' => 'Connection refused.']))))->get('https://example.org/manual');
121 }
122
123 /**
124 * @throws ExceptionInterface
125 */
126 public function testGetUsesADefaultClient(): void
127 {
128 $this->expectException(TransportExceptionInterface::class);
129 (new Download())->get('http://127.0.0.1:1/manual');
130 }
131}
132