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