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