1<?php
2
3namespace spec\League\Flysystem\Cached;
4
5use League\Flysystem\AdapterInterface;
6use League\Flysystem\Cached\CacheInterface;
7use League\Flysystem\Config;
8use PhpSpec\ObjectBehavior;
9
10class CachedAdapterSpec extends ObjectBehavior
11{
12    /**
13     * @var AdapterInterface
14     */
15    private $adapter;
16
17    /**
18     * @var CacheInterface
19     */
20    private $cache;
21
22    public function let(AdapterInterface $adapter, CacheInterface $cache)
23    {
24        $this->adapter = $adapter;
25        $this->cache = $cache;
26        $this->cache->load()->shouldBeCalled();
27        $this->beConstructedWith($adapter, $cache);
28    }
29
30    public function it_is_initializable()
31    {
32        $this->shouldHaveType('League\Flysystem\Cached\CachedAdapter');
33        $this->shouldHaveType('League\Flysystem\AdapterInterface');
34    }
35
36    public function it_should_forward_read_streams()
37    {
38        $path = 'path.txt';
39        $response = ['path' => $path];
40        $this->adapter->readStream($path)->willReturn($response);
41        $this->readStream($path)->shouldbe($response);
42    }
43
44    public function it_should_cache_writes()
45    {
46        $type = 'file';
47        $path = 'path.txt';
48        $contents = 'contents';
49        $config = new Config();
50        $response = compact('path', 'contents', 'type');
51        $this->adapter->write($path, $contents, $config)->willReturn($response);
52        $this->cache->updateObject($path, $response, true)->shouldBeCalled();
53        $this->write($path, $contents, $config)->shouldBe($response);
54    }
55
56    public function it_should_cache_streamed_writes()
57    {
58        $type = 'file';
59        $path = 'path.txt';
60        $stream = tmpfile();
61        $config = new Config();
62        $response = compact('path', 'stream', 'type');
63        $this->adapter->writeStream($path, $stream, $config)->willReturn($response);
64        $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled();
65        $this->writeStream($path, $stream, $config)->shouldBe($response);
66        fclose($stream);
67    }
68
69    public function it_should_cache_streamed_updates()
70    {
71        $type = 'file';
72        $path = 'path.txt';
73        $stream = tmpfile();
74        $config = new Config();
75        $response = compact('path', 'stream', 'type');
76        $this->adapter->updateStream($path, $stream, $config)->willReturn($response);
77        $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled();
78        $this->updateStream($path, $stream, $config)->shouldBe($response);
79        fclose($stream);
80    }
81
82    public function it_should_ignore_failed_writes()
83    {
84        $path = 'path.txt';
85        $contents = 'contents';
86        $config = new Config();
87        $this->adapter->write($path, $contents, $config)->willReturn(false);
88        $this->write($path, $contents, $config)->shouldBe(false);
89    }
90
91    public function it_should_ignore_failed_streamed_writes()
92    {
93        $path = 'path.txt';
94        $contents = tmpfile();
95        $config = new Config();
96        $this->adapter->writeStream($path, $contents, $config)->willReturn(false);
97        $this->writeStream($path, $contents, $config)->shouldBe(false);
98        fclose($contents);
99    }
100
101    public function it_should_cache_updated()
102    {
103        $type = 'file';
104        $path = 'path.txt';
105        $contents = 'contents';
106        $config = new Config();
107        $response = compact('path', 'contents', 'type');
108        $this->adapter->update($path, $contents, $config)->willReturn($response);
109        $this->cache->updateObject($path, $response, true)->shouldBeCalled();
110        $this->update($path, $contents, $config)->shouldBe($response);
111    }
112
113    public function it_should_ignore_failed_updates()
114    {
115        $path = 'path.txt';
116        $contents = 'contents';
117        $config = new Config();
118        $this->adapter->update($path, $contents, $config)->willReturn(false);
119        $this->update($path, $contents, $config)->shouldBe(false);
120    }
121
122    public function it_should_ignore_failed_streamed_updates()
123    {
124        $path = 'path.txt';
125        $contents = tmpfile();
126        $config = new Config();
127        $this->adapter->updateStream($path, $contents, $config)->willReturn(false);
128        $this->updateStream($path, $contents, $config)->shouldBe(false);
129        fclose($contents);
130    }
131
132    public function it_should_cache_renames()
133    {
134        $old = 'old.txt';
135        $new = 'new.txt';
136        $this->adapter->rename($old, $new)->willReturn(true);
137        $this->cache->rename($old, $new)->shouldBeCalled();
138        $this->rename($old, $new)->shouldBe(true);
139    }
140
141    public function it_should_ignore_rename_fails()
142    {
143        $old = 'old.txt';
144        $new = 'new.txt';
145        $this->adapter->rename($old, $new)->willReturn(false);
146        $this->rename($old, $new)->shouldBe(false);
147    }
148
149    public function it_should_cache_copies()
150    {
151        $old = 'old.txt';
152        $new = 'new.txt';
153        $this->adapter->copy($old, $new)->willReturn(true);
154        $this->cache->copy($old, $new)->shouldBeCalled();
155        $this->copy($old, $new)->shouldBe(true);
156    }
157
158    public function it_should_ignore_copy_fails()
159    {
160        $old = 'old.txt';
161        $new = 'new.txt';
162        $this->adapter->copy($old, $new)->willReturn(false);
163        $this->copy($old, $new)->shouldBe(false);
164    }
165
166    public function it_should_cache_deletes()
167    {
168        $delete = 'delete.txt';
169        $this->adapter->delete($delete)->willReturn(true);
170        $this->cache->delete($delete)->shouldBeCalled();
171        $this->delete($delete)->shouldBe(true);
172    }
173
174    public function it_should_ignore_delete_fails()
175    {
176        $delete = 'delete.txt';
177        $this->adapter->delete($delete)->willReturn(false);
178        $this->delete($delete)->shouldBe(false);
179    }
180
181    public function it_should_cache_dir_deletes()
182    {
183        $delete = 'delete';
184        $this->adapter->deleteDir($delete)->willReturn(true);
185        $this->cache->deleteDir($delete)->shouldBeCalled();
186        $this->deleteDir($delete)->shouldBe(true);
187    }
188
189    public function it_should_ignore_delete_dir_fails()
190    {
191        $delete = 'delete';
192        $this->adapter->deleteDir($delete)->willReturn(false);
193        $this->deleteDir($delete)->shouldBe(false);
194    }
195
196    public function it_should_cache_dir_creates()
197    {
198        $dirname = 'dirname';
199        $config = new Config();
200        $response = ['path' => $dirname, 'type' => 'dir'];
201        $this->adapter->createDir($dirname, $config)->willReturn($response);
202        $this->cache->updateObject($dirname, $response, true)->shouldBeCalled();
203        $this->createDir($dirname, $config)->shouldBe($response);
204    }
205
206    public function it_should_ignore_create_dir_fails()
207    {
208        $dirname = 'dirname';
209        $config = new Config();
210        $this->adapter->createDir($dirname, $config)->willReturn(false);
211        $this->createDir($dirname, $config)->shouldBe(false);
212    }
213
214    public function it_should_cache_set_visibility()
215    {
216        $path = 'path.txt';
217        $visibility = AdapterInterface::VISIBILITY_PUBLIC;
218        $this->adapter->setVisibility($path, $visibility)->willReturn(true);
219        $this->cache->updateObject($path, ['path' => $path, 'visibility' => $visibility], true)->shouldBeCalled();
220        $this->setVisibility($path, $visibility)->shouldBe(true);
221    }
222
223    public function it_should_ignore_set_visibility_fails()
224    {
225        $dirname = 'delete';
226        $visibility = AdapterInterface::VISIBILITY_PUBLIC;
227        $this->adapter->setVisibility($dirname, $visibility)->willReturn(false);
228        $this->setVisibility($dirname, $visibility)->shouldBe(false);
229    }
230
231    public function it_should_indicate_missing_files()
232    {
233        $this->cache->has($path = 'path.txt')->willReturn(false);
234        $this->has($path)->shouldBe(false);
235    }
236
237    public function it_should_indicate_file_existance()
238    {
239        $this->cache->has($path = 'path.txt')->willReturn(true);
240        $this->has($path)->shouldBe(true);
241    }
242
243    public function it_should_cache_missing_files()
244    {
245        $this->cache->has($path = 'path.txt')->willReturn(null);
246        $this->adapter->has($path)->willReturn(false);
247        $this->cache->storeMiss($path)->shouldBeCalled();
248        $this->has($path)->shouldBe(false);
249    }
250
251    public function it_should_delete_when_metadata_is_missing()
252    {
253        $path = 'path.txt';
254        $this->cache->has($path)->willReturn(true);
255        $this->cache->getSize($path)->willReturn(['path' => $path]);
256        $this->adapter->getSize($path)->willReturn($response = ['path' => $path, 'size' => 1024]);
257        $this->cache->updateObject($path, $response, true)->shouldBeCalled();
258        $this->getSize($path)->shouldBe($response);
259    }
260
261    public function it_should_cache_has()
262    {
263        $this->cache->has($path = 'path.txt')->willReturn(null);
264        $this->adapter->has($path)->willReturn(true);
265        $this->cache->updateObject($path, compact('path'), true)->shouldBeCalled();
266        $this->has($path)->shouldBe(true);
267    }
268
269    public function it_should_list_cached_contents()
270    {
271        $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(true);
272        $response = [['path' => 'path.txt']];
273        $this->cache->listContents($dirname, $recursive)->willReturn($response);
274        $this->listContents($dirname, $recursive)->shouldBe($response);
275    }
276
277    public function it_should_ignore_failed_list_contents()
278    {
279        $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false);
280        $this->adapter->listContents($dirname, $recursive)->willReturn(false);
281        $this->listContents($dirname, $recursive)->shouldBe(false);
282    }
283
284    public function it_should_cache_contents_listings()
285    {
286        $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false);
287        $response = [['path' => 'path.txt']];
288        $this->adapter->listContents($dirname, $recursive)->willReturn($response);
289        $this->cache->storeContents($dirname, $response, $recursive)->shouldBeCalled();
290        $this->listContents($dirname, $recursive)->shouldBe($response);
291    }
292
293    public function it_should_use_cached_visibility()
294    {
295        $this->make_it_use_getter_cache('getVisibility', 'path.txt', [
296            'path' => 'path.txt',
297            'visibility' => AdapterInterface::VISIBILITY_PUBLIC,
298        ]);
299    }
300
301    public function it_should_cache_get_visibility()
302    {
303        $path = 'path.txt';
304        $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path];
305        $this->make_it_cache_getter('getVisibility', $path, $response);
306    }
307
308    public function it_should_ignore_failed_get_visibility()
309    {
310        $path = 'path.txt';
311        $this->make_it_ignore_failed_getter('getVisibility', $path);
312    }
313
314    public function it_should_use_cached_timestamp()
315    {
316        $this->make_it_use_getter_cache('getTimestamp', 'path.txt', [
317            'path' => 'path.txt',
318            'timestamp' => 1234,
319        ]);
320    }
321
322    public function it_should_cache_timestamps()
323    {
324        $this->make_it_cache_getter('getTimestamp', 'path.txt', [
325            'path' => 'path.txt',
326            'timestamp' => 1234,
327        ]);
328    }
329
330    public function it_should_ignore_failed_get_timestamps()
331    {
332        $this->make_it_ignore_failed_getter('getTimestamp', 'path.txt');
333    }
334
335    public function it_should_cache_get_metadata()
336    {
337        $path = 'path.txt';
338        $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path];
339        $this->make_it_cache_getter('getMetadata', $path, $response);
340    }
341
342    public function it_should_use_cached_metadata()
343    {
344        $this->make_it_use_getter_cache('getMetadata', 'path.txt', [
345            'path' => 'path.txt',
346            'timestamp' => 1234,
347        ]);
348    }
349
350    public function it_should_ignore_failed_get_metadata()
351    {
352        $this->make_it_ignore_failed_getter('getMetadata', 'path.txt');
353    }
354
355    public function it_should_cache_get_size()
356    {
357        $path = 'path.txt';
358        $response = ['size' => 1234, 'path' => $path];
359        $this->make_it_cache_getter('getSize', $path, $response);
360    }
361
362    public function it_should_use_cached_size()
363    {
364        $this->make_it_use_getter_cache('getSize', 'path.txt', [
365            'path' => 'path.txt',
366            'size' => 1234,
367        ]);
368    }
369
370    public function it_should_ignore_failed_get_size()
371    {
372        $this->make_it_ignore_failed_getter('getSize', 'path.txt');
373    }
374
375    public function it_should_cache_get_mimetype()
376    {
377        $path = 'path.txt';
378        $response = ['mimetype' => 'text/plain', 'path' => $path];
379        $this->make_it_cache_getter('getMimetype', $path, $response);
380    }
381
382    public function it_should_use_cached_mimetype()
383    {
384        $this->make_it_use_getter_cache('getMimetype', 'path.txt', [
385            'path' => 'path.txt',
386            'mimetype' => 'text/plain',
387        ]);
388    }
389
390    public function it_should_ignore_failed_get_mimetype()
391    {
392        $this->make_it_ignore_failed_getter('getMimetype', 'path.txt');
393    }
394
395    public function it_should_cache_reads()
396    {
397        $path = 'path.txt';
398        $response = ['path' => $path, 'contents' => 'contents'];
399        $this->make_it_cache_getter('read', $path, $response);
400    }
401
402    public function it_should_use_cached_file_contents()
403    {
404        $this->make_it_use_getter_cache('read', 'path.txt', [
405            'path' => 'path.txt',
406            'contents' => 'contents'
407        ]);
408    }
409
410    public function it_should_ignore_failed_reads()
411    {
412        $this->make_it_ignore_failed_getter('read', 'path.txt');
413    }
414
415    protected function make_it_use_getter_cache($method, $path, $response)
416    {
417        $this->cache->{$method}($path)->willReturn($response);
418        $this->{$method}($path)->shouldBe($response);
419    }
420
421    protected function make_it_cache_getter($method, $path, $response)
422    {
423        $this->cache->{$method}($path)->willReturn(false);
424        $this->adapter->{$method}($path)->willReturn($response);
425        $this->cache->updateObject($path, $response, true)->shouldBeCalled();
426        $this->{$method}($path)->shouldBe($response);
427    }
428
429    protected function make_it_ignore_failed_getter($method, $path)
430    {
431        $this->cache->{$method}($path)->willReturn(false);
432        $this->adapter->{$method}($path)->willReturn(false);
433        $this->{$method}($path)->shouldBe(false);
434    }
435}
436