22
33namespace spec \Http \Client \Common \Plugin ;
44
5+ use Prophecy \Argument ;
56use Http \Message \StreamFactory ;
67use Http \Promise \FulfilledPromise ;
78use PhpSpec \ObjectBehavior ;
@@ -15,7 +16,10 @@ class CachePluginSpec extends ObjectBehavior
1516{
1617 function let (CacheItemPoolInterface $ pool , StreamFactory $ streamFactory )
1718 {
18- $ this ->beConstructedWith ($ pool , $ streamFactory , ['default_ttl ' =>60 ]);
19+ $ this ->beConstructedWith ($ pool , $ streamFactory , [
20+ 'default_ttl ' => 60 ,
21+ 'cache_lifetime ' => 1000
22+ ]);
1923 }
2024
2125 function it_is_initializable (CacheItemPoolInterface $ pool )
@@ -39,14 +43,22 @@ function it_caches_responses(CacheItemPoolInterface $pool, CacheItemInterface $i
3943 $ request ->getUri ()->willReturn ('/ ' );
4044 $ response ->getStatusCode ()->willReturn (200 );
4145 $ response ->getBody ()->willReturn ($ stream );
42- $ response ->getHeader ('Cache-Control ' )->willReturn (array ());
43- $ response ->getHeader ('Expires ' )->willReturn (array ());
46+ $ response ->getHeader ('Cache-Control ' )->willReturn (array ())->shouldBeCalled ();
47+ $ response ->getHeader ('Expires ' )->willReturn (array ())->shouldBeCalled ();
48+ $ response ->getHeader ('ETag ' )->willReturn (array ())->shouldBeCalled ();
4449
4550 $ pool ->getItem ('d20f64acc6e70b6079845f2fe357732929550ae1 ' )->shouldBeCalled ()->willReturn ($ item );
4651 $ item ->isHit ()->willReturn (false );
47- $ item ->set (['response ' => $ response , 'body ' => $ httpBody ])->willReturn ($ item )->shouldBeCalled ();
48- $ item ->expiresAfter (60 )->willReturn ($ item )->shouldBeCalled ();
49- $ pool ->save ($ item )->shouldBeCalled ();
52+ $ item ->expiresAfter (1060 )->willReturn ($ item )->shouldBeCalled ();
53+
54+ $ item ->set ($ this ->getCacheItemMatcher ([
55+ 'response ' => $ response ->getWrappedObject (),
56+ 'body ' => $ httpBody ,
57+ 'expiresAt ' => 0 ,
58+ 'createdAt ' => 0 ,
59+ 'etag ' => []
60+ ]))->willReturn ($ item )->shouldBeCalled ();
61+ $ pool ->save (Argument::any ())->shouldBeCalled ();
5062
5163 $ next = function (RequestInterface $ request ) use ($ response ) {
5264 return new FulfilledPromise ($ response ->getWrappedObject ());
@@ -100,13 +112,20 @@ function it_calculate_age_from_response(CacheItemPoolInterface $pool, CacheItemI
100112 $ response ->getHeader ('Cache-Control ' )->willReturn (array ('max-age=40 ' ));
101113 $ response ->getHeader ('Age ' )->willReturn (array ('15 ' ));
102114 $ response ->getHeader ('Expires ' )->willReturn (array ());
115+ $ response ->getHeader ('ETag ' )->willReturn (array ());
103116
104117 $ pool ->getItem ('d20f64acc6e70b6079845f2fe357732929550ae1 ' )->shouldBeCalled ()->willReturn ($ item );
105118 $ item ->isHit ()->willReturn (false );
106119
107- // 40-15 should be 25
108- $ item ->set (['response ' => $ response , 'body ' => $ httpBody ])->willReturn ($ item )->shouldBeCalled ();
109- $ item ->expiresAfter (25 )->willReturn ($ item )->shouldBeCalled ();
120+ $ item ->set ($ this ->getCacheItemMatcher ([
121+ 'response ' => $ response ->getWrappedObject (),
122+ 'body ' => $ httpBody ,
123+ 'expiresAt ' => 0 ,
124+ 'createdAt ' => 0 ,
125+ 'etag ' => []
126+ ]))->willReturn ($ item )->shouldBeCalled ();
127+ // 40-15 should be 25 + the default 1000
128+ $ item ->expiresAfter (1025 )->willReturn ($ item )->shouldBeCalled ();
110129 $ pool ->save ($ item )->shouldBeCalled ();
111130
112131 $ next = function (RequestInterface $ request ) use ($ response ) {
@@ -115,4 +134,171 @@ function it_calculate_age_from_response(CacheItemPoolInterface $pool, CacheItemI
115134
116135 $ this ->handleRequest ($ request , $ next , function () {});
117136 }
137+
138+ function it_saves_etag (CacheItemPoolInterface $ pool , CacheItemInterface $ item , RequestInterface $ request , ResponseInterface $ response , StreamInterface $ stream )
139+ {
140+ $ httpBody = 'body ' ;
141+ $ stream ->__toString ()->willReturn ($ httpBody );
142+ $ stream ->isSeekable ()->willReturn (true );
143+ $ stream ->rewind ()->shouldBeCalled ();
144+
145+ $ request ->getMethod ()->willReturn ('GET ' );
146+ $ request ->getUri ()->willReturn ('/ ' );
147+ $ response ->getStatusCode ()->willReturn (200 );
148+ $ response ->getBody ()->willReturn ($ stream );
149+ $ response ->getHeader ('Cache-Control ' )->willReturn (array ());
150+ $ response ->getHeader ('Expires ' )->willReturn (array ());
151+ $ response ->getHeader ('ETag ' )->willReturn (array ('foo_etag ' ));
152+
153+ $ pool ->getItem ('d20f64acc6e70b6079845f2fe357732929550ae1 ' )->shouldBeCalled ()->willReturn ($ item );
154+ $ item ->isHit ()->willReturn (false );
155+ $ item ->expiresAfter (1060 )->willReturn ($ item );
156+
157+ $ item ->set ($ this ->getCacheItemMatcher ([
158+ 'response ' => $ response ->getWrappedObject (),
159+ 'body ' => $ httpBody ,
160+ 'expiresAt ' => 0 ,
161+ 'createdAt ' => 0 ,
162+ 'etag ' => ['foo_etag ' ]
163+ ]))->willReturn ($ item )->shouldBeCalled ();
164+ $ pool ->save (Argument::any ())->shouldBeCalled ();
165+
166+ $ next = function (RequestInterface $ request ) use ($ response ) {
167+ return new FulfilledPromise ($ response ->getWrappedObject ());
168+ };
169+
170+ $ this ->handleRequest ($ request , $ next , function () {});
171+ }
172+
173+ function it_adds_etag_and_modfied_since_to_request (CacheItemPoolInterface $ pool , CacheItemInterface $ item , RequestInterface $ request , ResponseInterface $ response , StreamInterface $ stream )
174+ {
175+ $ httpBody = 'body ' ;
176+
177+ $ request ->getMethod ()->willReturn ('GET ' );
178+ $ request ->getUri ()->willReturn ('/ ' );
179+
180+ $ request ->withHeader ('If-Modified-Since ' , 'Thursday, 01-Jan-70 01:18:31 GMT ' )->shouldBeCalled ()->willReturn ($ request );
181+ $ request ->withHeader ('If-None-Match ' , 'foo_etag ' )->shouldBeCalled ()->willReturn ($ request );
182+
183+ $ response ->getStatusCode ()->willReturn (304 );
184+
185+ $ pool ->getItem ('d20f64acc6e70b6079845f2fe357732929550ae1 ' )->shouldBeCalled ()->willReturn ($ item );
186+ $ item ->isHit ()->willReturn (true , false );
187+ $ item ->get ()->willReturn ([
188+ 'response ' => $ response ,
189+ 'body ' => $ httpBody ,
190+ 'expiresAt ' => 0 ,
191+ 'createdAt ' => 4711 ,
192+ 'etag ' => ['foo_etag ' ]
193+ ])->shouldBeCalled ();
194+
195+ $ next = function (RequestInterface $ request ) use ($ response ) {
196+ return new FulfilledPromise ($ response ->getWrappedObject ());
197+ };
198+
199+ $ this ->handleRequest ($ request , $ next , function () {});
200+ }
201+
202+ function it_servces_a_cached_response (CacheItemPoolInterface $ pool , CacheItemInterface $ item , RequestInterface $ request , ResponseInterface $ response , StreamInterface $ stream , StreamFactory $ streamFactory )
203+ {
204+ $ httpBody = 'body ' ;
205+
206+ $ request ->getMethod ()->willReturn ('GET ' );
207+ $ request ->getUri ()->willReturn ('/ ' );
208+
209+ $ pool ->getItem ('d20f64acc6e70b6079845f2fe357732929550ae1 ' )->shouldBeCalled ()->willReturn ($ item );
210+ $ item ->isHit ()->willReturn (true );
211+ $ item ->get ()->willReturn ([
212+ 'response ' => $ response ,
213+ 'body ' => $ httpBody ,
214+ 'expiresAt ' => time ()+1000000 , //It is in the future
215+ 'createdAt ' => 4711 ,
216+ 'etag ' => []
217+ ])->shouldBeCalled ();
218+
219+ // Make sure we add back the body
220+ $ response ->withBody ($ stream )->willReturn ($ response )->shouldBeCalled ();
221+ $ streamFactory ->createStream ($ httpBody )->shouldBeCalled ()->willReturn ($ stream );
222+
223+ $ next = function (RequestInterface $ request ) use ($ response ) {
224+ return new FulfilledPromise ($ response ->getWrappedObject ());
225+ };
226+
227+ $ this ->handleRequest ($ request , $ next , function () {});
228+ }
229+
230+ function it_serves_and_resaved_expired_response (CacheItemPoolInterface $ pool , CacheItemInterface $ item , RequestInterface $ request , ResponseInterface $ response , StreamInterface $ stream , StreamFactory $ streamFactory )
231+ {
232+ $ httpBody = 'body ' ;
233+
234+ $ request ->getMethod ()->willReturn ('GET ' );
235+ $ request ->getUri ()->willReturn ('/ ' );
236+
237+ $ request ->withHeader (Argument::any (), Argument::any ())->willReturn ($ request );
238+ $ request ->withHeader (Argument::any (), Argument::any ())->willReturn ($ request );
239+
240+ $ response ->getStatusCode ()->willReturn (304 );
241+ $ response ->getHeader ('Cache-Control ' )->willReturn (array ());
242+ $ response ->getHeader ('Expires ' )->willReturn (array ())->shouldBeCalled ();
243+
244+ // Make sure we add back the body
245+ $ response ->withBody ($ stream )->willReturn ($ response )->shouldBeCalled ();
246+
247+ $ pool ->getItem ('d20f64acc6e70b6079845f2fe357732929550ae1 ' )->shouldBeCalled ()->willReturn ($ item );
248+ $ item ->isHit ()->willReturn (true , true );
249+ $ item ->expiresAfter (1060 )->willReturn ($ item )->shouldBeCalled ();
250+ $ item ->get ()->willReturn ([
251+ 'response ' => $ response ,
252+ 'body ' => $ httpBody ,
253+ 'expiresAt ' => 0 ,
254+ 'createdAt ' => 4711 ,
255+ 'etag ' => ['foo_etag ' ]
256+ ])->shouldBeCalled ();
257+
258+ $ item ->set ($ this ->getCacheItemMatcher ([
259+ 'response ' => $ response ->getWrappedObject (),
260+ 'body ' => $ httpBody ,
261+ 'expiresAt ' => 0 ,
262+ 'createdAt ' => 0 ,
263+ 'etag ' => ['foo_etag ' ]
264+ ]))->willReturn ($ item )->shouldBeCalled ();
265+ $ pool ->save (Argument::any ())->shouldBeCalled ();
266+
267+ $ streamFactory ->createStream ($ httpBody )->shouldBeCalled ()->willReturn ($ stream );
268+
269+ $ next = function (RequestInterface $ request ) use ($ response ) {
270+ return new FulfilledPromise ($ response ->getWrappedObject ());
271+ };
272+
273+ $ this ->handleRequest ($ request , $ next , function () {});
274+ }
275+
276+
277+ /**
278+ * Private function to match cache item data.
279+ *
280+ * @param array $expectedData
281+ *
282+ * @return \Closure
283+ */
284+ private function getCacheItemMatcher (array $ expectedData )
285+ {
286+ return Argument::that (function (array $ actualData ) use ($ expectedData ) {
287+ foreach ($ expectedData as $ key => $ value ) {
288+ if (!isset ($ actualData [$ key ])) {
289+ return false ;
290+ }
291+
292+ if ($ key === 'expiresAt ' || $ key === 'createdAt ' ) {
293+ // We do not need to validate the value of these fields.
294+ continue ;
295+ }
296+
297+ if ($ actualData [$ key ] !== $ value ) {
298+ return false ;
299+ }
300+ }
301+ return true ;
302+ });
303+ }
118304}
0 commit comments