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