Browse Source

0.7.0 Android code refactor

Ben Hsieh 8 years ago
parent
commit
1538401e15

+ 7
- 2
src/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java View File

151
     }
151
     }
152
 
152
 
153
     @ReactMethod
153
     @ReactMethod
154
-    public void cancel(String taskId) {
155
-        RNFetchBlobReq.cancelTask(taskId);
154
+    public void cancelRequest(String taskId, Callback callback) {
155
+        try {
156
+            RNFetchBlobReq.cancelTask(taskId);
157
+            callback.invoke(null, taskId);
158
+        } catch (Exception ex) {
159
+            callback.invoke(ex.getLocalizedMessage(), null);
160
+        }
156
     }
161
     }
157
 
162
 
158
     @ReactMethod
163
     @ReactMethod

+ 150
- 92
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobBody.java View File

1
 package com.RNFetchBlob;
1
 package com.RNFetchBlob;
2
 
2
 
3
-import android.net.Uri;
4
 import android.util.Base64;
3
 import android.util.Base64;
5
 import android.util.Log;
4
 import android.util.Log;
6
 
5
 
8
 import com.facebook.react.bridge.ReactApplicationContext;
7
 import com.facebook.react.bridge.ReactApplicationContext;
9
 import com.facebook.react.bridge.ReadableArray;
8
 import com.facebook.react.bridge.ReadableArray;
10
 import com.facebook.react.bridge.ReadableMap;
9
 import com.facebook.react.bridge.ReadableMap;
11
-import com.facebook.react.bridge.WritableArray;
12
 import com.facebook.react.bridge.WritableMap;
10
 import com.facebook.react.bridge.WritableMap;
13
 import com.facebook.react.modules.core.DeviceEventManagerModule;
11
 import com.facebook.react.modules.core.DeviceEventManagerModule;
14
 
12
 
13
+import java.io.ByteArrayInputStream;
15
 import java.io.File;
14
 import java.io.File;
16
 import java.io.FileInputStream;
15
 import java.io.FileInputStream;
17
-import java.io.FileNotFoundException;
18
 import java.io.IOException;
16
 import java.io.IOException;
19
 import java.io.InputStream;
17
 import java.io.InputStream;
20
 import java.util.ArrayList;
18
 import java.util.ArrayList;
24
 import okhttp3.RequestBody;
22
 import okhttp3.RequestBody;
25
 import okio.Buffer;
23
 import okio.Buffer;
26
 import okio.BufferedSink;
24
 import okio.BufferedSink;
27
-import okio.ByteString;
28
 import okio.ForwardingSink;
25
 import okio.ForwardingSink;
29
 import okio.Okio;
26
 import okio.Okio;
30
 import okio.Sink;
27
 import okio.Sink;
39
     long bytesWritten = 0;
36
     long bytesWritten = 0;
40
     ReadableArray form;
37
     ReadableArray form;
41
     String mTaskId;
38
     String mTaskId;
39
+    String rawBody;
42
     RNFetchBlobReq.RequestType requestType;
40
     RNFetchBlobReq.RequestType requestType;
43
     MediaType mime;
41
     MediaType mime;
44
 
42
 
45
-    public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, InputStream stream, long size, MediaType contentType) {
43
+    public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, ReadableArray form, MediaType contentType) {
46
         this.mTaskId = taskId;
44
         this.mTaskId = taskId;
47
         this.form = form;
45
         this.form = form;
48
-        requestStream = stream;
49
-        contentLength = size;
50
         requestType = type;
46
         requestType = type;
51
         mime = contentType;
47
         mime = contentType;
52
     }
48
     }
53
 
49
 
50
+    public RNFetchBlobBody(String taskId, RNFetchBlobReq.RequestType type, String rawBody, MediaType contentType) {
51
+        this.mTaskId = taskId;
52
+        requestType = type;
53
+        this.rawBody = rawBody;
54
+        mime = contentType;
55
+    }
56
+
54
     @Override
57
     @Override
55
     public MediaType contentType() {
58
     public MediaType contentType() {
56
         return mime;
59
         return mime;
59
     @Override
62
     @Override
60
     public void writeTo(BufferedSink sink) throws IOException {
63
     public void writeTo(BufferedSink sink) throws IOException {
61
 
64
 
62
-        ProgressReportingSource source = new ProgressReportingSource(sink, mTaskId, contentLength());
65
+        ProgressReportingSource source = new ProgressReportingSource(sink, mTaskId);
63
         BufferedSink buffer = Okio.buffer(source);
66
         BufferedSink buffer = Okio.buffer(source);
64
         switch (requestType) {
67
         switch (requestType) {
65
             case Form:
68
             case Form:
66
-                String boundary = "RNFetchBlob-" + mTaskId;
67
-                ArrayList<FormField> fields = countFormDataLength();
68
-                ReactApplicationContext ctx = RNFetchBlob.RCTContext;
69
-                for(int i = 0;i < fields.size(); i++) {
70
-                    FormField field = fields.get(i);
71
-                    String data = field.data;
72
-                    String name = field.name;
73
-                    // skip invalid fields
74
-                    if(name == null || data == null)
75
-                        continue;
76
-                    // form begin
77
-                    String header = "--" + boundary + "\r\n";
78
-                    if (field.filename != null) {
79
-                        header += "Content-Disposition: form-data; name=" + name + "; filename=" + field.filename + "\r\n";
80
-                        header += "Content-Type: " + field.mime+ "\r\n\r\n";
81
-                        sink.write(header.getBytes());
82
-                        // file field header end
83
-                        // upload from storage
84
-                        if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
85
-                            String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
86
-                            orgPath = RNFetchBlobFS.normalizePath(orgPath);
87
-                            // path starts with content://
88
-                            if (RNFetchBlobFS.isAsset(orgPath)) {
89
-                                try {
90
-                                    String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
91
-                                    InputStream in = ctx.getAssets().open(assetName);
92
-                                    pipeStreamToSink(in, sink);
93
-                                } catch (IOException e) {
94
-                                    Log.e("RNFetchBlob", "Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
95
-                                }
96
-                            }
97
-                            // data from normal files
98
-                            else {
99
-                                File file = new File(RNFetchBlobFS.normalizePath(orgPath));
100
-                                if(file.exists()) {
101
-                                    FileInputStream fs = new FileInputStream(file);
102
-                                    pipeStreamToSink(fs, sink);
103
-                                }
104
-                                else {
105
-                                    Log.e("RNFetchBlob", "Failed to create form data from path :" + orgPath + "file not exists.");
106
-                                }
107
-                            }
108
-                        }
109
-                        // base64 embedded file content
110
-                        else {
111
-                            byte[] b = Base64.decode(data, 0);
112
-                            sink.write(b);
113
-                            bytesWritten += b.length;
114
-                            emitUploadProgress(bytesWritten, contentLength);
115
-                        }
69
+                writeFormData(sink);
70
+                break;
71
+            case SingleFile:
72
+                writeOctetData(sink);
73
+                break;
74
+        }
75
+        buffer.flush();
76
+    }
116
 
77
 
78
+    private void writeFormData(BufferedSink sink) throws IOException {
79
+        String boundary = "RNFetchBlob-" + mTaskId;
80
+        ArrayList<FormField> fields = countFormDataLength();
81
+        ReactApplicationContext ctx = RNFetchBlob.RCTContext;
82
+        for(int i = 0;i < fields.size(); i++) {
83
+            FormField field = fields.get(i);
84
+            String data = field.data;
85
+            String name = field.name;
86
+            // skip invalid fields
87
+            if(name == null || data == null)
88
+                continue;
89
+            // form begin
90
+            String header = "--" + boundary + "\r\n";
91
+            if (field.filename != null) {
92
+                header += "Content-Disposition: form-data; name=" + name + "; filename=" + field.filename + "\r\n";
93
+                header += "Content-Type: " + field.mime+ "\r\n\r\n";
94
+                sink.write(header.getBytes());
95
+                // file field header end
96
+                // upload from storage
97
+                if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
98
+                    String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
99
+                    orgPath = RNFetchBlobFS.normalizePath(orgPath);
100
+                    // path starts with content://
101
+                    if (RNFetchBlobFS.isAsset(orgPath)) {
102
+                        try {
103
+                            String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
104
+                            InputStream in = ctx.getAssets().open(assetName);
105
+                            pipeStreamToSink(in, sink);
106
+                        } catch (IOException e) {
107
+                            Log.e("RNFetchBlob", "Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
108
+                        }
117
                     }
109
                     }
118
-                    // data field
110
+                    // data from normal files
119
                     else {
111
                     else {
120
-                        header += "Content-Disposition: form-data; name=" + name + "\r\n";
121
-                        header += "Content-Type: " + field.mime + "\r\n\r\n";
122
-                        sink.write(header.getBytes());
123
-                        byte[] fieldData = field.data.getBytes();
124
-                        bytesWritten += fieldData.length;
125
-                        sink.write(fieldData);
112
+                        File file = new File(RNFetchBlobFS.normalizePath(orgPath));
113
+                        if(file.exists()) {
114
+                            FileInputStream fs = new FileInputStream(file);
115
+                            pipeStreamToSink(fs, sink);
116
+                        }
117
+                        else {
118
+                            Log.e("RNFetchBlob", "Failed to create form data from path :" + orgPath + "file not exists.");
119
+                        }
126
                     }
120
                     }
127
-                    // form end
128
-                    sink.write("\r\n".getBytes());
129
                 }
121
                 }
130
-                // close the form
131
-                byte[] end = ("--" + boundary + "--\r\n").getBytes();
132
-                sink.write(end);
133
-                break;
134
-            case SingleFile:
135
-                pipeStreamToSink(requestStream, sink);
136
-                break;
122
+                // base64 embedded file content
123
+                else {
124
+                    byte[] b = Base64.decode(data, 0);
125
+                    sink.write(b);
126
+                    bytesWritten += b.length;
127
+                    emitUploadProgress();
128
+                }
129
+
130
+            }
131
+            // data field
132
+            else {
133
+                header += "Content-Disposition: form-data; name=" + name + "\r\n";
134
+                header += "Content-Type: " + field.mime + "\r\n\r\n";
135
+                sink.write(header.getBytes());
136
+                byte[] fieldData = field.data.getBytes();
137
+                bytesWritten += fieldData.length;
138
+                sink.write(fieldData);
139
+            }
140
+            // form end
141
+            sink.write("\r\n".getBytes());
137
         }
142
         }
138
-        buffer.flush();
143
+        // close the form
144
+        byte[] end = ("--" + boundary + "--\r\n").getBytes();
145
+        sink.write(end);
146
+    }
147
+
148
+    /**
149
+     * Write octet stream data to request body
150
+     * @param sink
151
+     */
152
+    private void writeOctetData(BufferedSink sink) throws IOException {
153
+        // upload from storage
154
+        if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
155
+            String orgPath = rawBody.substring(RNFetchBlobConst.FILE_PREFIX.length());
156
+            orgPath = RNFetchBlobFS.normalizePath(orgPath);
157
+            // upload file from assets
158
+            if (RNFetchBlobFS.isAsset(orgPath)) {
159
+                try {
160
+                    String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
161
+                    contentLength = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
162
+                    requestStream = RNFetchBlob.RCTContext.getAssets().open(assetName);
163
+                } catch (IOException e) {
164
+//                        e.printStackTrace();
165
+                }
166
+            } else {
167
+                File f = new File(RNFetchBlobFS.normalizePath(orgPath));
168
+                try {
169
+                    if(!f.exists())
170
+                        f.createNewFile();
171
+                    contentLength = f.length();
172
+                    requestStream = new FileInputStream(f);
173
+                } catch (Exception e) {
174
+//                        callback.invoke(e.getLocalizedMessage(), null);
175
+                }
176
+            }
177
+        } else {
178
+            byte[] bytes = Base64.decode(rawBody, 0);
179
+            contentLength = bytes.length;
180
+            requestStream = new ByteArrayInputStream(bytes);
181
+        }
182
+        if(requestStream != null)
183
+            pipeStreamToSink(requestStream, sink);
184
+
139
     }
185
     }
140
 
186
 
187
+    /**
188
+     * Pipe input stream to request body output stream
189
+     * @param stream    The input stream
190
+     * @param sink      The request body buffer sink
191
+     * @throws IOException
192
+     */
141
     private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
193
     private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
142
         byte [] chunk = new byte[10240];
194
         byte [] chunk = new byte[10240];
143
         int read = stream.read(chunk, 0, 10240);
195
         int read = stream.read(chunk, 0, 10240);
150
             if(read > 0) {
202
             if(read > 0) {
151
                 sink.write(chunk, 0, read);
203
                 sink.write(chunk, 0, read);
152
                 bytesWritten += read;
204
                 bytesWritten += read;
153
-                emitUploadProgress(bytesWritten, contentLength);
205
+                emitUploadProgress();
154
             }
206
             }
155
 
207
 
156
         }
208
         }
157
         stream.close();
209
         stream.close();
158
     }
210
     }
159
 
211
 
160
-    private void emitUploadProgress(long current, long total) {
161
-        WritableMap args = Arguments.createMap();
162
-        args.putString("taskId", mTaskId);
163
-        args.putString("written", String.valueOf(bytesWritten));
164
-        args.putString("total", String.valueOf(contentLength));
165
-
166
-        // emit event to js context
167
-        RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
168
-                .emit(RNFetchBlobConst.EVENT_UPLOAD_PROGRESS, args);
212
+    private void writeBufferToSink(byte [] bytes, BufferedSink sink) throws IOException {
213
+        bytesWritten += bytes.length;
214
+        sink.write(bytes);
215
+        emitUploadProgress();
169
     }
216
     }
170
 
217
 
171
-
172
-
173
     /**
218
     /**
174
-     * Compute a proximate content length for form data
219
+     * Compute approximate content length for form data
175
      * @return
220
      * @return
176
      */
221
      */
177
     private ArrayList<FormField> countFormDataLength() {
222
     private ArrayList<FormField> countFormDataLength() {
218
         return list;
263
         return list;
219
     }
264
     }
220
 
265
 
266
+    /**
267
+     * Since ReadableMap could only be access once, we have to store the field into a map for
268
+     * repeatedly access.
269
+     */
221
     private class FormField {
270
     private class FormField {
222
         public String name;
271
         public String name;
223
         public String filename;
272
         public String filename;
239
         }
288
         }
240
     }
289
     }
241
 
290
 
291
+    private void emitUploadProgress() {
292
+        WritableMap args = Arguments.createMap();
293
+        args.putString("taskId", mTaskId);
294
+        args.putString("written", String.valueOf(bytesWritten));
295
+        args.putString("total", String.valueOf(contentLength));
296
+
297
+        // emit event to js context
298
+        RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
299
+                .emit(RNFetchBlobConst.EVENT_UPLOAD_PROGRESS, args);
300
+    }
301
+
242
     private final class ProgressReportingSource extends ForwardingSink {
302
     private final class ProgressReportingSource extends ForwardingSink {
243
 
303
 
244
         private long bytesWritten = 0;
304
         private long bytesWritten = 0;
245
         private String mTaskId;
305
         private String mTaskId;
246
-        private long mContentLength ;
247
         private Sink delegate;
306
         private Sink delegate;
248
 
307
 
249
-        public ProgressReportingSource (Sink delegate, String taskId, long contentLength) {
308
+        public ProgressReportingSource (Sink delegate, String taskId) {
250
             super(delegate);
309
             super(delegate);
251
             this.mTaskId = taskId;
310
             this.mTaskId = taskId;
252
-            this.mContentLength = contentLength;
253
             this.delegate = delegate;
311
             this.delegate = delegate;
254
         }
312
         }
255
 
313
 
262
             WritableMap args = Arguments.createMap();
320
             WritableMap args = Arguments.createMap();
263
             args.putString("taskId", mTaskId);
321
             args.putString("taskId", mTaskId);
264
             args.putString("written", String.valueOf(bytesWritten));
322
             args.putString("written", String.valueOf(bytesWritten));
265
-            args.putString("total", String.valueOf(mContentLength ));
323
+            args.putString("total", String.valueOf(contentLength));
266
 
324
 
267
             // emit event to js context
325
             // emit event to js context
268
             RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
326
             RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)

+ 1
- 51
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

182
             // set request body
182
             // set request body
183
             switch (requestType) {
183
             switch (requestType) {
184
                 case SingleFile:
184
                 case SingleFile:
185
-                    InputStream dataStream = buildOctetBody(rawRequestBody);
186
                     builder.method(method, new RNFetchBlobBody(
185
                     builder.method(method, new RNFetchBlobBody(
187
                             taskId,
186
                             taskId,
188
                             RequestType.SingleFile,
187
                             RequestType.SingleFile,
189
-                            null,
190
-                            dataStream,
191
-                            contentLength,
188
+                            rawRequestBody,
192
                             RNFetchBlobConst.MIME_OCTET
189
                             RNFetchBlobConst.MIME_OCTET
193
                     ));
190
                     ));
194
                     break;
191
                     break;
197
                             taskId,
194
                             taskId,
198
                             RequestType.Form,
195
                             RequestType.Form,
199
                             rawRequestBodyArray,
196
                             rawRequestBodyArray,
200
-                            null,
201
-                            0,
202
                             MediaType.parse("multipart/form-data; boundary=RNFetchBlob-" + taskId)
197
                             MediaType.parse("multipart/form-data; boundary=RNFetchBlob-" + taskId)
203
                     ));
198
                     ));
204
                     break;
199
                     break;
329
 
324
 
330
     }
325
     }
331
 
326
 
332
-    /**
333
-     * Get InputStream of request body when request body contains a single file.
334
-     *
335
-     * @param body Body in string format
336
-     * @return InputStream When there's no request body, returns null
337
-     */
338
-    InputStream buildOctetBody(String body) {
339
-        // set body for POST and PUT
340
-        if (body != null && (method.equalsIgnoreCase("post") || method.equalsIgnoreCase("put"))) {
341
-            this.contentType = RNFetchBlobConst.MIME_OCTET;
342
-            byte[] blob;
343
-            // upload from storage
344
-            if (body.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
345
-                String orgPath = body.substring(RNFetchBlobConst.FILE_PREFIX.length());
346
-                orgPath = RNFetchBlobFS.normalizePath(orgPath);
347
-                // upload file from assets
348
-                if (RNFetchBlobFS.isAsset(orgPath)) {
349
-                    try {
350
-                        String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
351
-                        contentLength = RNFetchBlob.RCTContext.getAssets().openFd(assetName).getLength();
352
-                        return RNFetchBlob.RCTContext.getAssets().open(assetName);
353
-                    } catch (IOException e) {
354
-//                        e.printStackTrace();
355
-                    }
356
-                } else {
357
-                    File f = new File(RNFetchBlobFS.normalizePath(orgPath));
358
-                    try {
359
-                        if(!f.exists())
360
-                            f.createNewFile();
361
-                        contentLength = f.length();
362
-                        return new FileInputStream(f);
363
-                    } catch (Exception e) {
364
-                        callback.invoke(e.getLocalizedMessage(), null);
365
-                    }
366
-                }
367
-            } else {
368
-                byte[] bytes = Base64.decode(body, 0);
369
-                contentLength = bytes.length;
370
-                return new ByteArrayInputStream(bytes);
371
-            }
372
-        }
373
-        return null;
374
-
375
-    }
376
-
377
     @Override
327
     @Override
378
     public void onReceive(Context context, Intent intent) {
328
     public void onReceive(Context context, Intent intent) {
379
         String action = intent.getAction();
329
         String action = intent.getAction();

+ 5
- 3
src/index.js View File

149
 
149
 
150
   })
150
   })
151
 
151
 
152
-  // extend Promise object, add a `progress` method for register progress event
153
-  // handler.
152
+  // extend Promise object, add `progress`, `uploadProgress`, and `cancel`
153
+  // method for register progress event handler and cancel request.
154
   promise.progress = (fn) => {
154
   promise.progress = (fn) => {
155
     promise.onProgress = fn
155
     promise.onProgress = fn
156
     return promise
156
     return promise
157
   }
157
   }
158
-
159
   promise.uploadProgress = (fn) => {
158
   promise.uploadProgress = (fn) => {
160
     promise.onUploadProgress = fn
159
     promise.onUploadProgress = fn
161
     return promise
160
     return promise
162
   }
161
   }
162
+  promise.cancel = (fn) => {
163
+    RNFetchBlob.cancelRequest(taskId, fn)
164
+  }
163
 
165
 
164
   return promise
166
   return promise
165
 
167
 

+ 0
- 60
test/test-0.6.3.js View File

47
   })
47
   })
48
 
48
 
49
 })
49
 })
50
-
51
-RNTest.config({
52
-  group : '0.7.0',
53
-  run : true,
54
-  expand : false,
55
-  timeout : 600000,
56
-})('Upload and download large file', (report, done) => {
57
-  let filename = '22mb-dummy-' + Date.now()
58
-  let begin = -1
59
-  let begin2 = -1
60
-  let deb = Date.now()
61
-  RNFetchBlob.config({
62
-    fileCache : true
63
-  })
64
-  .fetch('GET', `${TEST_SERVER_URL}/public/22mb-dummy`)
65
-  .progress((now, total) => {
66
-    if(begin === -1)
67
-      begin = Date.now()
68
-    if(Date.now() - deb < 1000)
69
-      return
70
-    deb = Date.now()
71
-    report(<Info uid="200" key="progress">
72
-      <Text>
73
-        {`download ${now} / ${total} bytes (${Math.floor(now / (Date.now() - begin))} kb/s)`}
74
-      </Text>
75
-    </Info>)
76
-  })
77
-  .then((res) => {
78
-    try {
79
-    deb = Date.now()
80
-    let promise =  RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
81
-      Authorization : `Bearer ${DROPBOX_TOKEN}`,
82
-      'Dropbox-API-Arg': '{\"path\": \"/rn-upload/'+filename+'\",\"mode\": \"add\",\"autorename\": true,\"mute\": false}',
83
-      'Content-Type' : 'application/octet-stream',
84
-    }, RNFetchBlob.wrap(res.path()))
85
-    promise.uploadProgress((now, total) => {
86
-      if(Date.now() - deb < 1000)
87
-        return
88
-      deb = Date.now()
89
-      if(begin2 === -1)
90
-        begin2 = Date.now()
91
-      let speed = Math.floor(now / (Date.now() - begin2))
92
-      report(<Info uid="100"  key="progress">
93
-        <Text>
94
-          {`upload ${now} / ${total} bytes (${speed} kb/s)`}
95
-          {` ${Math.floor((total-now)/speed/1000)} seconds left`}
96
-        </Text>
97
-      </Info>)
98
-    })
99
-    return promise
100
-  } catch(err) { console.log(err) }
101
-  })
102
-  .then((res) => {
103
-    report(<Assert
104
-      key="upload should success without crashing app"
105
-      expect={filename}
106
-      actual={res.json().name}/>)
107
-    done()
108
-  })
109
-})