1+ <?php
2+
3+ namespace Tests \TestSuite ;
4+
5+ use GuzzleHttp \Client ;
6+ use GuzzleHttp \Exception \ClientException ;
7+ use GuzzleHttp \Handler \MockHandler ;
8+ use GuzzleHttp \HandlerStack ;
9+ use GuzzleHttp \Psr7 \Response ;
10+ use GuzzleHttp \Psr7 \Request ;
11+ use PHPUnit \Framework \TestCase ;
12+ use Tests \Fixtures \Downloader \CachedHttpDownloader ;
13+
14+ class CachedHttpDownloaderTest extends TestCase
15+ {
16+ private string $ tempCacheDir ;
17+
18+ protected function setUp (): void
19+ {
20+ $ this ->tempCacheDir = sys_get_temp_dir () . '/test_cache_ ' . uniqid ();
21+ mkdir ($ this ->tempCacheDir , 0777 , true );
22+ }
23+
24+ protected function tearDown (): void
25+ {
26+ $ this ->removeDirectory ($ this ->tempCacheDir );
27+ }
28+
29+ public function testRetryOn429Error (): void
30+ {
31+ // Mock handler that returns 429 twice, then succeeds
32+ $ mock = new MockHandler ([
33+ new ClientException ('Too Many Requests ' , new Request ('GET ' , 'test ' ), new Response (429 )),
34+ new ClientException ('Too Many Requests ' , new Request ('GET ' , 'test ' ), new Response (429 )),
35+ new Response (200 , [], 'success content ' ),
36+ ]);
37+
38+ $ handlerStack = HandlerStack::create ($ mock );
39+ $ client = new Client (['handler ' => $ handlerStack ]);
40+
41+ $ downloader = new CachedHttpDownloader ('test ' , $ this ->tempCacheDir , 10 , 3 ); // 10ms delay, 3 max retries
42+
43+ // Use reflection to replace the client
44+ $ reflection = new \ReflectionClass ($ downloader );
45+ $ clientProperty = $ reflection ->getProperty ('client ' );
46+ $ clientProperty ->setAccessible (true );
47+ $ clientProperty ->setValue ($ downloader , $ client );
48+
49+ $ result = $ downloader ->fetch ('http://test.com ' , true );
50+
51+ $ this ->assertEquals ('success content ' , $ result );
52+ }
53+
54+ public function testRetryFailsAfterMaxAttempts (): void
55+ {
56+ // Mock handler that always returns 429
57+ $ mock = new MockHandler ([
58+ new ClientException ('Too Many Requests ' , new Request ('GET ' , 'test ' ), new Response (429 )),
59+ new ClientException ('Too Many Requests ' , new Request ('GET ' , 'test ' ), new Response (429 )),
60+ new ClientException ('Too Many Requests ' , new Request ('GET ' , 'test ' ), new Response (429 )),
61+ new ClientException ('Too Many Requests ' , new Request ('GET ' , 'test ' ), new Response (429 )),
62+ ]);
63+
64+ $ handlerStack = HandlerStack::create ($ mock );
65+ $ client = new Client (['handler ' => $ handlerStack ]);
66+
67+ $ downloader = new CachedHttpDownloader ('test ' , $ this ->tempCacheDir , 10 , 3 ); // 10ms delay, 3 max retries
68+
69+ // Use reflection to replace the client
70+ $ reflection = new \ReflectionClass ($ downloader );
71+ $ clientProperty = $ reflection ->getProperty ('client ' );
72+ $ clientProperty ->setAccessible (true );
73+ $ clientProperty ->setValue ($ downloader , $ client );
74+
75+ $ this ->expectException (ClientException::class);
76+ $ downloader ->fetch ('http://test.com ' , true );
77+ }
78+
79+ public function testSuccessfulRequest (): void
80+ {
81+ // Mock handler that returns success immediately
82+ $ mock = new MockHandler ([
83+ new Response (200 , [], 'success content ' ),
84+ ]);
85+
86+ $ handlerStack = HandlerStack::create ($ mock );
87+ $ client = new Client (['handler ' => $ handlerStack ]);
88+
89+ $ downloader = new CachedHttpDownloader ('test ' , $ this ->tempCacheDir , 10 , 3 );
90+
91+ // Use reflection to replace the client
92+ $ reflection = new \ReflectionClass ($ downloader );
93+ $ clientProperty = $ reflection ->getProperty ('client ' );
94+ $ clientProperty ->setAccessible (true );
95+ $ clientProperty ->setValue ($ downloader , $ client );
96+
97+ $ result = $ downloader ->fetch ('http://test.com ' , true );
98+
99+ $ this ->assertEquals ('success content ' , $ result );
100+ }
101+
102+ public function testCachedResponse (): void
103+ {
104+ // First call
105+ $ mock1 = new MockHandler ([
106+ new Response (200 , ['ETag ' => '"test-etag" ' ], 'cached content ' ),
107+ ]);
108+ $ handlerStack1 = HandlerStack::create ($ mock1 );
109+ $ client1 = new Client (['handler ' => $ handlerStack1 ]);
110+
111+ $ downloader = new CachedHttpDownloader ('test ' , $ this ->tempCacheDir , 10 , 3 );
112+
113+ $ reflection = new \ReflectionClass ($ downloader );
114+ $ clientProperty = $ reflection ->getProperty ('client ' );
115+ $ clientProperty ->setAccessible (true );
116+ $ clientProperty ->setValue ($ downloader , $ client1 );
117+
118+ $ result1 = $ downloader ->fetch ('http://test.com ' , true );
119+ $ this ->assertEquals ('cached content ' , $ result1 );
120+
121+ // Second call should use cache and return 304
122+ $ mock2 = new MockHandler ([
123+ new Response (304 ),
124+ ]);
125+ $ handlerStack2 = HandlerStack::create ($ mock2 );
126+ $ client2 = new Client (['handler ' => $ handlerStack2 ]);
127+ $ clientProperty ->setValue ($ downloader , $ client2 );
128+
129+ $ result2 = $ downloader ->fetch ('http://test.com ' , false );
130+ $ this ->assertEquals ('cached content ' , $ result2 );
131+ }
132+
133+ private function removeDirectory (string $ dir ): void
134+ {
135+ if (!is_dir ($ dir )) {
136+ return ;
137+ }
138+
139+ $ files = new \RecursiveIteratorIterator (
140+ new \RecursiveDirectoryIterator ($ dir , \RecursiveDirectoryIterator::SKIP_DOTS ),
141+ \RecursiveIteratorIterator::CHILD_FIRST
142+ );
143+
144+ foreach ($ files as $ fileinfo ) {
145+ $ todo = ($ fileinfo ->isDir () ? 'rmdir ' : 'unlink ' );
146+ $ todo ($ fileinfo ->getRealPath ());
147+ }
148+
149+ rmdir ($ dir );
150+ }
151+ }
0 commit comments