xref: /expo/apps/test-suite/tests/FileSystem.js (revision f05c8ccb)
1'use strict';
2
3import { Asset } from 'expo-asset';
4import * as FS from 'expo-file-system';
5import Constants from 'expo-constants';
6import { Platform } from '@unimodules/core';
7
8export const name = 'FileSystem';
9
10export async function test({ describe, expect, it, ...t }) {
11  describe('FileSystem', () => {
12    const throws = async run => {
13      let error = null;
14      try {
15        await run();
16      } catch (e) {
17        error = e;
18      }
19      expect(error).toBeTruthy();
20    };
21
22    if (Constants.appOwnership === 'expo') {
23      describe('managed workflow', () => {
24        it('throws out-of-scope exceptions', async () => {
25          const p = FS.documentDirectory;
26
27          await throws(() => FS.readAsStringAsync(p + '../hello/world'));
28          await throws(() => FS.writeAsStringAsync(p + '../hello/world', ''));
29          await throws(() => FS.deleteAsync(p + '../hello/world'));
30          await throws(() => FS.deleteAsync(p));
31          await throws(() => FS.deleteAsync(FS.cacheDirectory));
32          await throws(() => FS.moveAsync({ from: p + '../a/b', to: 'c' }));
33          await throws(() => FS.moveAsync({ from: 'c', to: p + '../a/b' }));
34          await throws(() => FS.copyAsync({ from: p + '../a/b', to: 'c' }));
35          await throws(() => FS.copyAsync({ from: 'c', to: p + '../a/b' }));
36          await throws(() => FS.makeDirectoryAsync(p + '../hello/world'));
37          await throws(() => FS.readDirectoryAsync(p + '../hello/world'));
38          await throws(() => FS.downloadAsync('http://www.google.com', p + '../hello/world'));
39          await throws(() => FS.readDirectoryAsync(p + '../'));
40          await throws(() => FS.downloadAsync('http://www.google.com', p + '../hello/world'));
41        });
42      });
43    }
44
45    if (Platform.OS === 'web') {
46      // Web doesn't support FileSystem
47      return;
48    }
49
50    it(
51      'delete(idempotent) -> !exists -> download(md5, uri) -> exists ' + '-> delete -> !exists',
52      async () => {
53        const localUri = FS.documentDirectory + 'download1.png';
54
55        const assertExists = async expectedToExist => {
56          let { exists } = await FS.getInfoAsync(localUri);
57          if (expectedToExist) {
58            expect(exists).toBeTruthy();
59          } else {
60            expect(exists).not.toBeTruthy();
61          }
62        };
63
64        await FS.deleteAsync(localUri, { idempotent: true });
65        await assertExists(false);
66
67        const {
68          md5,
69          headers,
70        } = await FS.downloadAsync(
71          'https://s3-us-west-1.amazonaws.com/test-suite-data/avatar2.png',
72          localUri,
73          { md5: true }
74        );
75        expect(md5).toBe('1e02045c10b8f1145edc7c8375998f87');
76        await assertExists(true);
77        expect(headers['Content-Type']).toBe('image/png');
78
79        await FS.deleteAsync(localUri);
80        await assertExists(false);
81      },
82      9000
83    );
84
85    it('Can read/write Base64', async () => {
86      const asset = await Asset.fromModule(require('../assets/icons/app.png'));
87      await asset.downloadAsync();
88
89      for (let startingPosition = 0; startingPosition < 3; startingPosition++) {
90        const options = {
91          encoding: FS.EncodingType.Base64,
92          position: startingPosition,
93          length: startingPosition + 1,
94        };
95
96        const b64 = await FS.readAsStringAsync(asset.localUri, options);
97        expect(b64).toBeDefined();
98        expect(typeof b64).toBe('string');
99        expect(b64.length % 4).toBe(0);
100
101        const localUri = FS.documentDirectory + 'b64.png';
102
103        await FS.writeAsStringAsync(localUri, b64, { encoding: FS.EncodingType.Base64 });
104
105        expect(await FS.readAsStringAsync(localUri, { encoding: FS.EncodingType.Base64 })).toBe(
106          b64
107        );
108      }
109    });
110
111    it('delete(idempotent) -> delete[error]', async () => {
112      const localUri = FS.documentDirectory + 'willDelete.png';
113
114      await FS.deleteAsync(localUri, { idempotent: true });
115
116      let error;
117      try {
118        await FS.deleteAsync(localUri);
119      } catch (e) {
120        error = e;
121      }
122      expect(error.message).toMatch(/not.*found/);
123    });
124
125    it('download(md5, uri) -> read -> delete -> !exists -> read[error]', async () => {
126      const localUri = FS.documentDirectory + 'download1.txt';
127
128      const {
129        md5,
130      } = await FS.downloadAsync(
131        'https://s3-us-west-1.amazonaws.com/test-suite-data/text-file.txt',
132        localUri,
133        { md5: true }
134      );
135      expect(md5).toBe('86d73d2f11e507365f7ea8e7ec3cc4cb');
136
137      const string = await FS.readAsStringAsync(localUri);
138      expect(string).toBe('hello, world\nthis is a test file\n');
139
140      await FS.deleteAsync(localUri, { idempotent: true });
141
142      let error;
143      try {
144        await FS.readAsStringAsync(localUri);
145      } catch (e) {
146        error = e;
147      }
148      expect(error).toBeTruthy();
149    }, 9000);
150
151    it('delete(idempotent) -> !exists -> write -> read -> write -> read', async () => {
152      const localUri = FS.documentDirectory + 'write1.txt';
153
154      await FS.deleteAsync(localUri, { idempotent: true });
155
156      const { exists } = await FS.getInfoAsync(localUri);
157      expect(exists).not.toBeTruthy();
158
159      const writeAndVerify = async expected => {
160        await FS.writeAsStringAsync(localUri, expected);
161        const string = await FS.readAsStringAsync(localUri);
162        expect(string).toBe(expected);
163      };
164
165      await writeAndVerify('hello, world');
166      await writeAndVerify('hello, world!!!!!!');
167    });
168
169    it('delete(new) -> 2 * [write -> move -> !exists(orig) -> read(new)]', async () => {
170      const from = FS.documentDirectory + 'from.txt';
171      const to = FS.documentDirectory + 'to.txt';
172      const contents = ['contents 1', 'contents 2'];
173
174      await FS.deleteAsync(to, { idempotent: true });
175
176      // Move twice to make sure we can overwrite
177      for (let i = 0; i < 2; ++i) {
178        await FS.writeAsStringAsync(from, contents[i]);
179
180        await FS.moveAsync({ from, to });
181
182        const { exists } = await FS.getInfoAsync(from);
183        expect(exists).not.toBeTruthy();
184
185        expect(await FS.readAsStringAsync(to)).toBe(contents[i]);
186      }
187    });
188
189    it('delete(new) -> 2 * [write -> copy -> exists(orig) -> read(new)]', async () => {
190      const from = FS.documentDirectory + 'from.txt';
191      const to = FS.documentDirectory + 'to.txt';
192      const contents = ['contents 1', 'contents 2'];
193
194      await FS.deleteAsync(to, { idempotent: true });
195
196      // Copy twice to make sure we can overwrite
197      for (let i = 0; i < 2; ++i) {
198        await FS.writeAsStringAsync(from, contents[i]);
199
200        await FS.copyAsync({ from, to });
201
202        const { exists } = await FS.getInfoAsync(from);
203        expect(exists).toBeTruthy();
204
205        expect(await FS.readAsStringAsync(to)).toBe(contents[i]);
206      }
207    });
208
209    it(
210      'delete(dir) -> write(dir/file)[error] -> mkdir(dir) ->' +
211        'mkdir(dir)[error] -> write(dir/file) -> read',
212      async () => {
213        let error;
214        const path = FS.documentDirectory + 'dir/file';
215        const dir = FS.documentDirectory + 'dir';
216        const contents = 'hello, world';
217
218        await FS.deleteAsync(dir, { idempotent: true });
219
220        error = null;
221        try {
222          await FS.writeAsStringAsync(path, contents);
223        } catch (e) {
224          error = e;
225        }
226        expect(error).toBeTruthy();
227
228        await FS.makeDirectoryAsync(dir);
229
230        error = null;
231        try {
232          await FS.makeDirectoryAsync(dir);
233        } catch (e) {
234          error = e;
235        }
236        expect(error).toBeTruthy();
237
238        await FS.writeAsStringAsync(path, contents);
239
240        expect(await FS.readAsStringAsync(path)).toBe(contents);
241      }
242    );
243
244    it(
245      'delete(dir) -> write(dir/dir2/file)[error] -> ' +
246        'mkdir(dir/dir2, intermediates) -> ' +
247        'mkdir(dir/dir2, intermediates) -> write(dir/dir2/file) -> read',
248      async () => {
249        let error;
250        const path = FS.documentDirectory + 'dir/dir2/file';
251        const dir = FS.documentDirectory + 'dir/dir2';
252        const contents = 'hello, world';
253
254        await FS.deleteAsync(dir, { idempotent: true });
255
256        error = null;
257        try {
258          await FS.writeAsStringAsync(path, contents);
259        } catch (e) {
260          error = e;
261        }
262        expect(error).toBeTruthy();
263
264        await FS.makeDirectoryAsync(dir, {
265          intermediates: true,
266        });
267
268        error = null;
269        try {
270          await FS.makeDirectoryAsync(dir);
271        } catch (e) {
272          error = e;
273        }
274        expect(error).toBeTruthy();
275
276        error = null;
277        try {
278          await FS.makeDirectoryAsync(dir, {
279            intermediates: true,
280          });
281        } catch (e) {
282          error = e;
283        }
284        expect(error).toBe(null);
285
286        await FS.writeAsStringAsync(path, contents);
287
288        expect(await FS.readAsStringAsync(path)).toBe(contents);
289      }
290    );
291
292    it('getInfo(dirPath)', async () => {
293      const dir = FS.documentDirectory + 'dir';
294      const path = FS.documentDirectory + 'dir/file.txt';
295
296      await FS.deleteAsync(dir, { idempotent: true });
297      await FS.makeDirectoryAsync(dir, {
298        intermediates: true,
299      });
300      await FS.writeAsStringAsync(path, 'Expo is awesome ������');
301      const info = await FS.getInfoAsync(dir);
302
303      expect(info).toBeDefined();
304      expect(info.exists).toBe(true);
305      expect(info.isDirectory).toBe(true);
306      expect(info.size).toBe(28);
307    });
308
309    /*
310    This test fails in CI because of an exception being thrown by deleteAsync in the nativeModule.
311    I traced it down to the FileUtils.forceDelete call here:
312    https://github.com/expo/expo/blob/bcd136b096df84e0b0f72a15acbda45491de8201/packages/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.java#L294
313    it(
314      'delete(dir, idempotent) -> make tree -> check contents ' +
315        '-> check directory listings' +
316        '-> move -> check directory listings' +
317        '-> copy -> check directory listings',
318      async () => {
319        let error;
320        const dir = FS.documentDirectory + 'dir';
321
322        await FS.deleteAsync(dir, { idempotent: true });
323
324        await FS.makeDirectoryAsync(dir + '/child1', {
325          intermediates: true,
326        });
327        await FS.makeDirectoryAsync(dir + '/child2', {
328          intermediates: true,
329        });
330
331        await FS.writeAsStringAsync(dir + '/file1', 'contents1');
332        await FS.writeAsStringAsync(dir + '/file2', 'contents2');
333
334        await FS.writeAsStringAsync(dir + '/child1/file3', 'contents3');
335
336        await FS.writeAsStringAsync(dir + '/child2/file4', 'contents4');
337        await FS.writeAsStringAsync(dir + '/child2/file5', 'contents5');
338
339        const checkContents = async (path, contents) =>
340          expect(await FS.readAsStringAsync(path)).toBe(contents);
341
342        await checkContents(dir + '/file1', 'contents1');
343        await checkContents(dir + '/file2', 'contents2');
344        await checkContents(dir + '/child1/file3', 'contents3');
345        await checkContents(dir + '/child2/file4', 'contents4');
346        await checkContents(dir + '/child2/file5', 'contents5');
347
348        const checkDirectory = async (path, expected) => {
349          const list = await FS.readDirectoryAsync(path);
350          expect(list.sort()).toEqual(expected.sort());
351        };
352
353        const checkRoot = async root => {
354          await checkDirectory(root, ['file1', 'file2', 'child1', 'child2']);
355          await checkDirectory(root + '/child1', ['file3']);
356          await checkDirectory(root + '/child2', ['file4', 'file5']);
357
358          error = null;
359          try {
360            await checkDirectory(root + '/file1', ['nope']);
361          } catch (e) {
362            error = e;
363          }
364          expect(error).toBeTruthy();
365        };
366
367        await checkRoot(dir);
368
369        await FS.deleteAsync(FS.documentDirectory + 'moved', {
370          idempotent: true,
371        });
372        await FS.moveAsync({ from: dir, to: FS.documentDirectory + 'moved' });
373        await checkRoot(FS.documentDirectory + 'moved');
374        await FS.copyAsync({
375          from: FS.documentDirectory + 'moved',
376          to: FS.documentDirectory + 'copied',
377        });
378        await checkRoot(FS.documentDirectory + 'copied');
379      }
380    );
381    */
382
383    it('delete(idempotent) -> download(md5) -> getInfo(size)', async () => {
384      const localUri = FS.documentDirectory + 'download1.png';
385
386      await FS.deleteAsync(localUri, { idempotent: true });
387
388      const {
389        md5,
390      } = await FS.downloadAsync(
391        'https://s3-us-west-1.amazonaws.com/test-suite-data/avatar2.png',
392        localUri,
393        { md5: true }
394      );
395      expect(md5).toBe('1e02045c10b8f1145edc7c8375998f87');
396
397      const { size, modificationTime } = await FS.getInfoAsync(localUri);
398      expect(size).toBe(3230);
399      const nowTime = 0.001 * new Date().getTime();
400      expect(nowTime - modificationTime).toBeLessThan(3600);
401
402      await FS.deleteAsync(localUri);
403    }, 30000);
404
405    it('missing parameters', async () => {
406      const p = FS.documentDirectory + 'test';
407
408      await throws(() => FS.moveAsync({ from: p }));
409      await throws(() => FS.moveAsync({ to: p }));
410      await throws(() => FS.copyAsync({ from: p }));
411      await throws(() => FS.copyAsync({ to: p }));
412    });
413
414    it('can read root directories', async () => {
415      await FS.readDirectoryAsync(FS.documentDirectory);
416      await FS.readDirectoryAsync(FS.cacheDirectory);
417    });
418
419    it('download(network failure)', async () => {
420      const localUri = FS.documentDirectory + 'download1.png';
421
422      const assertExists = async expectedToExist => {
423        let { exists } = await FS.getInfoAsync(localUri);
424        if (expectedToExist) {
425          expect(exists).toBeTruthy();
426        } else {
427          expect(exists).not.toBeTruthy();
428        }
429      };
430
431      await FS.deleteAsync(localUri, { idempotent: true });
432      await assertExists(false);
433
434      let error;
435      try {
436        await FS.downloadAsync('https://nonexistent-subdomain.expo.io', localUri, {
437          md5: true,
438          sessionType: FS.FileSystemSessionType.FOREGROUND,
439        });
440      } catch (e) {
441        error = e;
442      }
443      expect(error).toBeTruthy();
444      await assertExists(false);
445      await FS.deleteAsync(localUri, { idempotent: true });
446    }, 30000);
447
448    it('download(404)', async () => {
449      const localUri = FS.documentDirectory + 'download1.png';
450
451      const assertExists = async expectedToExist => {
452        let { exists } = await FS.getInfoAsync(localUri);
453        if (expectedToExist) {
454          expect(exists).toBeTruthy();
455        } else {
456          expect(exists).not.toBeTruthy();
457        }
458      };
459
460      await FS.deleteAsync(localUri, { idempotent: true });
461      await assertExists(false);
462
463      const { status } = await FS.downloadAsync('https://github.com/omg1231sdfaljs', localUri, {
464        md5: true,
465      });
466      await assertExists(true);
467      expect(status).toBe(404);
468
469      await FS.deleteAsync(localUri);
470      await assertExists(false);
471    }, 30000);
472
473    it('download(nonexistent local path)', async () => {
474      try {
475        const remoteUrl = 'https://s3-us-west-1.amazonaws.com/test-suite-data/avatar2.png';
476        const localUri = FS.documentDirectory + 'doesnt/exists/download1.png';
477        await FS.downloadAsync(remoteUrl, localUri);
478      } catch (err) {
479        expect(err.message).toMatch(/Directory for .* doesn't exist/);
480      }
481    }, 30000);
482
483    it('mkdir(multi-level) + download(multi-level local path)', async () => {
484      const remoteUrl = 'https://s3-us-west-1.amazonaws.com/test-suite-data/avatar2.png';
485      const localDirUri = FS.documentDirectory + 'foo/bar/baz';
486      const localFileUri = localDirUri + 'download1.png';
487
488      await FS.makeDirectoryAsync(localDirUri, { intermediates: true });
489
490      await FS.downloadAsync(remoteUrl, localFileUri);
491    }, 30000);
492  });
493}
494