@sasjs/cli 6.10.0 → 6.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/build/internal/mockSite/assets/runtime-core.js +248 -2
- package/build/commands/build/internal/mockSite/assets/sw.js +209 -14
- package/build/commands/build/internal/mockSite/spec/runtime-core.spec.js +255 -6
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +576 -4
- package/package.json +1 -1
|
@@ -239,6 +239,92 @@ describe('service worker host', function () {
|
|
|
239
239
|
}
|
|
240
240
|
});
|
|
241
241
|
}); });
|
|
242
|
+
it('keeps a header appended without a leading newline on its own line', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
243
|
+
var dispatch, response, _a;
|
|
244
|
+
return __generator(this, function (_b) {
|
|
245
|
+
switch (_b.label) {
|
|
246
|
+
case 0:
|
|
247
|
+
dispatch = streaming([
|
|
248
|
+
"fs.appendFileSync(_SASJS_WEBOUT_HEADERS, 'Content-type: application/json')",
|
|
249
|
+
"_webout = '{}'"
|
|
250
|
+
].join('\n')).dispatch;
|
|
251
|
+
return [4 /*yield*/, dispatch(downloadPath())];
|
|
252
|
+
case 1:
|
|
253
|
+
response = _b.sent();
|
|
254
|
+
expect(response.headers.get('content-type')).toBe('application/json');
|
|
255
|
+
_a = expect;
|
|
256
|
+
return [4 /*yield*/, response.text()];
|
|
257
|
+
case 2:
|
|
258
|
+
_a.apply(void 0, [_b.sent()]).toBe('{}');
|
|
259
|
+
return [2 /*return*/];
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}); });
|
|
263
|
+
it('keeps a declared type that happens to equal the seed', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
264
|
+
var dispatch, response;
|
|
265
|
+
return __generator(this, function (_a) {
|
|
266
|
+
switch (_a.label) {
|
|
267
|
+
case 0:
|
|
268
|
+
dispatch = streaming([
|
|
269
|
+
"fs.writeFileSync(_SASJS_WEBOUT_HEADERS, 'content-type: text/html; charset=utf-8')",
|
|
270
|
+
'fs.writeFileSync(weboutPath, Uint8Array.from([1, 2, 3]))'
|
|
271
|
+
].join('\n')).dispatch;
|
|
272
|
+
return [4 /*yield*/, dispatch(downloadPath())];
|
|
273
|
+
case 1:
|
|
274
|
+
response = _a.sent();
|
|
275
|
+
expect(response.headers.get('content-type')).toBe('text/html; charset=utf-8');
|
|
276
|
+
return [2 /*return*/];
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}); });
|
|
280
|
+
it('answers text/html for a service that declares no type, as a server does', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
281
|
+
var dispatch, response, _a;
|
|
282
|
+
return __generator(this, function (_b) {
|
|
283
|
+
switch (_b.label) {
|
|
284
|
+
case 0:
|
|
285
|
+
dispatch = seeded().dispatch;
|
|
286
|
+
return [4 /*yield*/, dispatch('/SASjsApi/stp/execute?_program=' +
|
|
287
|
+
encodeURIComponent("".concat(appLoc, "/services/public/getdata")))
|
|
288
|
+
// The session header file is seeded with the server's line, so a service
|
|
289
|
+
// that assigns _webout and stops answers with a content type instead of
|
|
290
|
+
// leaving the choice to the browser.
|
|
291
|
+
];
|
|
292
|
+
case 1:
|
|
293
|
+
response = _b.sent();
|
|
294
|
+
// The session header file is seeded with the server's line, so a service
|
|
295
|
+
// that assigns _webout and stops answers with a content type instead of
|
|
296
|
+
// leaving the choice to the browser.
|
|
297
|
+
expect(response.headers.get('content-type')).toBe('text/html; charset=utf-8');
|
|
298
|
+
_a = expect;
|
|
299
|
+
return [4 /*yield*/, response.text()];
|
|
300
|
+
case 2:
|
|
301
|
+
_a.apply(void 0, [_b.sent()]).toBe('seeded');
|
|
302
|
+
return [2 /*return*/];
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}); });
|
|
306
|
+
it('keeps a declared text/html on a byte body, and ignores only the seed', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
307
|
+
var dispatch, response;
|
|
308
|
+
return __generator(this, function (_a) {
|
|
309
|
+
switch (_a.label) {
|
|
310
|
+
case 0:
|
|
311
|
+
dispatch = streaming([
|
|
312
|
+
"fs.appendFileSync(_SASJS_WEBOUT_HEADERS, '\\nContent-type: text/html')",
|
|
313
|
+
'fs.writeFileSync(weboutPath, Uint8Array.from([1, 2, 3]))'
|
|
314
|
+
].join('\n')).dispatch;
|
|
315
|
+
return [4 /*yield*/, dispatch(downloadPath())
|
|
316
|
+
// only the seeded value is treated as undeclared; the same type written by
|
|
317
|
+
// the service is a declaration and is applied
|
|
318
|
+
];
|
|
319
|
+
case 1:
|
|
320
|
+
response = _a.sent();
|
|
321
|
+
// only the seeded value is treated as undeclared; the same type written by
|
|
322
|
+
// the service is a declaration and is applied
|
|
323
|
+
expect(response.headers.get('content-type')).toBe('text/html');
|
|
324
|
+
return [2 /*return*/];
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}); });
|
|
242
328
|
it('keeps a debug reply text, as the body is the framed log', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
243
329
|
var dispatch, response, _a;
|
|
244
330
|
return __generator(this, function (_b) {
|
|
@@ -428,9 +514,9 @@ describe('service worker host', function () {
|
|
|
428
514
|
response = _b.sent();
|
|
429
515
|
expect(response.status).toBe(404);
|
|
430
516
|
_a = expect;
|
|
431
|
-
return [4 /*yield*/, response.
|
|
517
|
+
return [4 /*yield*/, response.text()];
|
|
432
518
|
case 2:
|
|
433
|
-
_a.apply(void 0, [
|
|
519
|
+
_a.apply(void 0, [_b.sent()]).toBe("File doesn't exist.");
|
|
434
520
|
return [2 /*return*/];
|
|
435
521
|
}
|
|
436
522
|
});
|
|
@@ -481,9 +567,9 @@ describe('service worker host', function () {
|
|
|
481
567
|
response = _c.sent();
|
|
482
568
|
expect(response.status).toBe(500);
|
|
483
569
|
_a = expect;
|
|
484
|
-
return [4 /*yield*/, response.
|
|
570
|
+
return [4 /*yield*/, response.text()];
|
|
485
571
|
case 2:
|
|
486
|
-
_a.apply(void 0, [
|
|
572
|
+
_a.apply(void 0, [_c.sent()]).toContain('Corrupt binary entry');
|
|
487
573
|
return [2 /*return*/];
|
|
488
574
|
}
|
|
489
575
|
});
|
|
@@ -713,4 +799,490 @@ describe('service worker host', function () {
|
|
|
713
799
|
var dispatch = seeded().dispatch;
|
|
714
800
|
expect(dispatch('/index.html')).toBeUndefined();
|
|
715
801
|
});
|
|
802
|
+
describe('drive writes', function () {
|
|
803
|
+
/**
|
|
804
|
+
* The response as a client sees it. A drive refusal is plain text (the
|
|
805
|
+
* server sends `res.status(code).send(err.message)`), a success is JSON.
|
|
806
|
+
*/
|
|
807
|
+
var answer = function (response) { return __awaiter(void 0, void 0, void 0, function () {
|
|
808
|
+
var resolved, type, _a;
|
|
809
|
+
var _b;
|
|
810
|
+
return __generator(this, function (_c) {
|
|
811
|
+
switch (_c.label) {
|
|
812
|
+
case 0: return [4 /*yield*/, response];
|
|
813
|
+
case 1:
|
|
814
|
+
resolved = _c.sent();
|
|
815
|
+
type = resolved.headers.get('content-type') || '';
|
|
816
|
+
_b = {
|
|
817
|
+
status: resolved.status
|
|
818
|
+
};
|
|
819
|
+
if (!type.includes('application/json')) return [3 /*break*/, 3];
|
|
820
|
+
return [4 /*yield*/, resolved.json()];
|
|
821
|
+
case 2:
|
|
822
|
+
_a = _c.sent();
|
|
823
|
+
return [3 /*break*/, 5];
|
|
824
|
+
case 3: return [4 /*yield*/, resolved.text()];
|
|
825
|
+
case 4:
|
|
826
|
+
_a = _c.sent();
|
|
827
|
+
_c.label = 5;
|
|
828
|
+
case 5: return [2 /*return*/, (_b.body = _a,
|
|
829
|
+
_b)];
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
}); };
|
|
833
|
+
it('answers a drive refusal as text, the way the server sends one', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
834
|
+
var dispatch, response, _a;
|
|
835
|
+
return __generator(this, function (_b) {
|
|
836
|
+
switch (_b.label) {
|
|
837
|
+
case 0:
|
|
838
|
+
dispatch = seeded().dispatch;
|
|
839
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
840
|
+
encodeURIComponent('/data/missing.csv'))
|
|
841
|
+
// the server's drive routes answer `res.status(code).send(err.message)`,
|
|
842
|
+
// and its own suite asserts `res.text` - so a client that reads the body
|
|
843
|
+
// as text must get the message here too, not a JSON envelope
|
|
844
|
+
];
|
|
845
|
+
case 1:
|
|
846
|
+
response = _b.sent();
|
|
847
|
+
// the server's drive routes answer `res.status(code).send(err.message)`,
|
|
848
|
+
// and its own suite asserts `res.text` - so a client that reads the body
|
|
849
|
+
// as text must get the message here too, not a JSON envelope
|
|
850
|
+
expect(response.status).toBe(404);
|
|
851
|
+
expect(response.headers.get('content-type')).toContain('text/plain');
|
|
852
|
+
_a = expect;
|
|
853
|
+
return [4 /*yield*/, response.text()];
|
|
854
|
+
case 2:
|
|
855
|
+
_a.apply(void 0, [_b.sent()]).toBe("File doesn't exist.");
|
|
856
|
+
return [2 /*return*/];
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}); });
|
|
860
|
+
it('creates a file, which the drive then serves', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
861
|
+
var dispatch, created, read, _a;
|
|
862
|
+
return __generator(this, function (_b) {
|
|
863
|
+
switch (_b.label) {
|
|
864
|
+
case 0:
|
|
865
|
+
dispatch = seeded().dispatch;
|
|
866
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
867
|
+
encodeURIComponent('/data/users.csv'), (function () {
|
|
868
|
+
var form = new FormData();
|
|
869
|
+
form.set('file', new Blob(['id,name\n1,Ann\n']), 'users.csv');
|
|
870
|
+
return { method: 'POST', body: form };
|
|
871
|
+
})()))];
|
|
872
|
+
case 1:
|
|
873
|
+
created = _b.sent();
|
|
874
|
+
expect(created).toEqual({ status: 200, body: { status: 'success' } });
|
|
875
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
876
|
+
encodeURIComponent('/data/users.csv'))];
|
|
877
|
+
case 2:
|
|
878
|
+
read = _b.sent();
|
|
879
|
+
_a = expect;
|
|
880
|
+
return [4 /*yield*/, read.text()];
|
|
881
|
+
case 3:
|
|
882
|
+
_a.apply(void 0, [_b.sent()]).toBe('id,name\n1,Ann\n');
|
|
883
|
+
return [2 /*return*/];
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
}); });
|
|
887
|
+
it('creates a file at the path in the form when the query has none', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
888
|
+
var dispatch, form, created, read, _a;
|
|
889
|
+
return __generator(this, function (_b) {
|
|
890
|
+
switch (_b.label) {
|
|
891
|
+
case 0:
|
|
892
|
+
dispatch = seeded().dispatch;
|
|
893
|
+
form = new FormData();
|
|
894
|
+
form.set('file', new Blob(['x']), 'x.txt');
|
|
895
|
+
form.set('filePath', '/data/from-form.txt');
|
|
896
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file', { method: 'POST', body: form }))];
|
|
897
|
+
case 1:
|
|
898
|
+
created = _b.sent();
|
|
899
|
+
expect(created.body).toEqual({ status: 'success' });
|
|
900
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
901
|
+
encodeURIComponent('/data/from-form.txt'))];
|
|
902
|
+
case 2:
|
|
903
|
+
read = _b.sent();
|
|
904
|
+
_a = expect;
|
|
905
|
+
return [4 /*yield*/, read.text()];
|
|
906
|
+
case 3:
|
|
907
|
+
_a.apply(void 0, [_b.sent()]).toBe('x');
|
|
908
|
+
return [2 /*return*/];
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
}); });
|
|
912
|
+
it('answers 409 for a create over an existing file, 404 for a replace of a missing one', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
913
|
+
var dispatch, form, again, missing;
|
|
914
|
+
return __generator(this, function (_a) {
|
|
915
|
+
switch (_a.label) {
|
|
916
|
+
case 0:
|
|
917
|
+
dispatch = seeded().dispatch;
|
|
918
|
+
form = function () {
|
|
919
|
+
var f = new FormData();
|
|
920
|
+
f.set('file', new Blob(['x']), 'x.csv');
|
|
921
|
+
return f;
|
|
922
|
+
};
|
|
923
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/x.csv'), { method: 'POST', body: form() })];
|
|
924
|
+
case 1:
|
|
925
|
+
_a.sent();
|
|
926
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/x.csv'), { method: 'POST', body: form() }))];
|
|
927
|
+
case 2:
|
|
928
|
+
again = _a.sent();
|
|
929
|
+
expect(again.status).toBe(409);
|
|
930
|
+
expect(again.body).toEqual('File already exists.');
|
|
931
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
932
|
+
encodeURIComponent('/data/missing.csv'), { method: 'PATCH', body: form() }))];
|
|
933
|
+
case 3:
|
|
934
|
+
missing = _a.sent();
|
|
935
|
+
expect(missing.status).toBe(404);
|
|
936
|
+
expect(missing.body).toEqual("File doesn't exist.");
|
|
937
|
+
return [2 /*return*/];
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
}); });
|
|
941
|
+
it('replaces a file and deletes one', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
942
|
+
var dispatch, form, replacement, replaced, read, _a, removed, gone;
|
|
943
|
+
return __generator(this, function (_b) {
|
|
944
|
+
switch (_b.label) {
|
|
945
|
+
case 0:
|
|
946
|
+
dispatch = seeded().dispatch;
|
|
947
|
+
form = new FormData();
|
|
948
|
+
form.set('file', new Blob(['v1']), 'v.csv');
|
|
949
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/v.csv'), { method: 'POST', body: form })];
|
|
950
|
+
case 1:
|
|
951
|
+
_b.sent();
|
|
952
|
+
replacement = new FormData();
|
|
953
|
+
replacement.set('file', new Blob(['v2']), 'v.csv');
|
|
954
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/v.csv'), { method: 'PATCH', body: replacement }))];
|
|
955
|
+
case 2:
|
|
956
|
+
replaced = _b.sent();
|
|
957
|
+
expect(replaced.body).toEqual({ status: 'success' });
|
|
958
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/v.csv'))];
|
|
959
|
+
case 3:
|
|
960
|
+
read = _b.sent();
|
|
961
|
+
_a = expect;
|
|
962
|
+
return [4 /*yield*/, read.text()];
|
|
963
|
+
case 4:
|
|
964
|
+
_a.apply(void 0, [_b.sent()]).toBe('v2');
|
|
965
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/v.csv'), { method: 'DELETE' }))];
|
|
966
|
+
case 5:
|
|
967
|
+
removed = _b.sent();
|
|
968
|
+
expect(removed.body).toEqual({ status: 'success' });
|
|
969
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/v.csv'))];
|
|
970
|
+
case 6:
|
|
971
|
+
gone = _b.sent();
|
|
972
|
+
expect(gone.status).toBe(404);
|
|
973
|
+
return [2 /*return*/];
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
}); });
|
|
977
|
+
it('refuses an upload with no file, no path, or a blocked extension', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
978
|
+
var dispatch, empty, noFile, form, noPath, sh, blocked;
|
|
979
|
+
return __generator(this, function (_a) {
|
|
980
|
+
switch (_a.label) {
|
|
981
|
+
case 0:
|
|
982
|
+
dispatch = seeded().dispatch;
|
|
983
|
+
empty = new FormData();
|
|
984
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/x.csv'), { method: 'POST', body: empty }))];
|
|
985
|
+
case 1:
|
|
986
|
+
noFile = _a.sent();
|
|
987
|
+
expect(noFile.status).toBe(400);
|
|
988
|
+
expect(noFile.body).toEqual('"file" is not present.');
|
|
989
|
+
form = new FormData();
|
|
990
|
+
form.set('file', new Blob(['x']), 'x.csv');
|
|
991
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file', { method: 'POST', body: form }))];
|
|
992
|
+
case 2:
|
|
993
|
+
noPath = _a.sent();
|
|
994
|
+
expect(noPath.status).toBe(400);
|
|
995
|
+
expect(noPath.body).toEqual('"_filePath" is required');
|
|
996
|
+
sh = new FormData();
|
|
997
|
+
sh.set('file', new Blob(['x']), 'run.sh');
|
|
998
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
999
|
+
encodeURIComponent('/data/run.sh'), { method: 'POST', body: sh }))];
|
|
1000
|
+
case 3:
|
|
1001
|
+
blocked = _a.sent();
|
|
1002
|
+
expect(blocked.status).toBe(400);
|
|
1003
|
+
expect(blocked.body).toEqual('Invalid file extension');
|
|
1004
|
+
return [2 /*return*/];
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}); });
|
|
1008
|
+
it('refuses a path outside the drive on every write route', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1009
|
+
var dispatch, form, outside, rename;
|
|
1010
|
+
return __generator(this, function (_a) {
|
|
1011
|
+
switch (_a.label) {
|
|
1012
|
+
case 0:
|
|
1013
|
+
dispatch = seeded().dispatch;
|
|
1014
|
+
form = new FormData();
|
|
1015
|
+
form.set('file', new Blob(['x']), 'x.csv');
|
|
1016
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1017
|
+
encodeURIComponent('/../escape.csv'), { method: 'POST', body: form }))];
|
|
1018
|
+
case 1:
|
|
1019
|
+
outside = _a.sent();
|
|
1020
|
+
expect(outside.status).toBe(400);
|
|
1021
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/rename', {
|
|
1022
|
+
method: 'POST',
|
|
1023
|
+
headers: { 'content-type': 'application/json' },
|
|
1024
|
+
body: JSON.stringify({ oldPath: '/../x', newPath: '/data/y' })
|
|
1025
|
+
}))];
|
|
1026
|
+
case 2:
|
|
1027
|
+
rename = _a.sent();
|
|
1028
|
+
expect(rename.status).toBe(400);
|
|
1029
|
+
return [2 /*return*/];
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
}); });
|
|
1033
|
+
it('creates a folder, lists it, and deletes it with its contents', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1034
|
+
var dispatch, made, form, listed, _a, removed, gone;
|
|
1035
|
+
return __generator(this, function (_b) {
|
|
1036
|
+
switch (_b.label) {
|
|
1037
|
+
case 0:
|
|
1038
|
+
dispatch = seeded().dispatch;
|
|
1039
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/folder', {
|
|
1040
|
+
method: 'POST',
|
|
1041
|
+
headers: { 'content-type': 'application/json' },
|
|
1042
|
+
body: JSON.stringify({ folderPath: '/data/reports' })
|
|
1043
|
+
}))];
|
|
1044
|
+
case 1:
|
|
1045
|
+
made = _b.sent();
|
|
1046
|
+
expect(made.body).toEqual({ status: 'success' });
|
|
1047
|
+
form = new FormData();
|
|
1048
|
+
form.set('file', new Blob(['a']), 'a.csv');
|
|
1049
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1050
|
+
encodeURIComponent('/data/reports/a.csv'), { method: 'POST', body: form })];
|
|
1051
|
+
case 2:
|
|
1052
|
+
_b.sent();
|
|
1053
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/folder?_folderPath=' + encodeURIComponent('/data'))];
|
|
1054
|
+
case 3:
|
|
1055
|
+
listed = _b.sent();
|
|
1056
|
+
_a = expect;
|
|
1057
|
+
return [4 /*yield*/, listed.json()];
|
|
1058
|
+
case 4:
|
|
1059
|
+
_a.apply(void 0, [_b.sent()]).toEqual({
|
|
1060
|
+
files: [],
|
|
1061
|
+
folders: ['reports']
|
|
1062
|
+
});
|
|
1063
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/folder?_folderPath=' +
|
|
1064
|
+
encodeURIComponent('/data/reports'), { method: 'DELETE' }))];
|
|
1065
|
+
case 5:
|
|
1066
|
+
removed = _b.sent();
|
|
1067
|
+
expect(removed.body).toEqual({ status: 'success' });
|
|
1068
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1069
|
+
encodeURIComponent('/data/reports/a.csv'))];
|
|
1070
|
+
case 6:
|
|
1071
|
+
gone = _b.sent();
|
|
1072
|
+
expect(gone.status).toBe(404);
|
|
1073
|
+
return [2 /*return*/];
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
}); });
|
|
1077
|
+
it('refuses a folder delete with no path', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1078
|
+
var dispatch, removed;
|
|
1079
|
+
return __generator(this, function (_a) {
|
|
1080
|
+
switch (_a.label) {
|
|
1081
|
+
case 0:
|
|
1082
|
+
dispatch = seeded().dispatch;
|
|
1083
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/folder', { method: 'DELETE' }))];
|
|
1084
|
+
case 1:
|
|
1085
|
+
removed = _a.sent();
|
|
1086
|
+
expect(removed.status).toBe(400);
|
|
1087
|
+
expect(removed.body).toEqual('"_folderPath" is required');
|
|
1088
|
+
return [2 /*return*/];
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
}); });
|
|
1092
|
+
it('refuses a rename that would produce a blocked extension', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1093
|
+
var dispatch, form, toBlocked, fromBlocked;
|
|
1094
|
+
return __generator(this, function (_a) {
|
|
1095
|
+
switch (_a.label) {
|
|
1096
|
+
case 0:
|
|
1097
|
+
dispatch = seeded().dispatch;
|
|
1098
|
+
form = new FormData();
|
|
1099
|
+
form.set('file', new Blob(['r']), 'r.csv');
|
|
1100
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/old.csv'), { method: 'POST', body: form })];
|
|
1101
|
+
case 1:
|
|
1102
|
+
_a.sent();
|
|
1103
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/rename', {
|
|
1104
|
+
method: 'POST',
|
|
1105
|
+
headers: { 'content-type': 'application/json' },
|
|
1106
|
+
body: JSON.stringify({
|
|
1107
|
+
oldPath: '/data/old.csv',
|
|
1108
|
+
newPath: '/data/x.sh'
|
|
1109
|
+
})
|
|
1110
|
+
}))
|
|
1111
|
+
// DELETE and PATCH run the same check on the path they are given, so a file
|
|
1112
|
+
// renamed into a blocked name could not be removed or replaced afterwards
|
|
1113
|
+
];
|
|
1114
|
+
case 2:
|
|
1115
|
+
toBlocked = _a.sent();
|
|
1116
|
+
// DELETE and PATCH run the same check on the path they are given, so a file
|
|
1117
|
+
// renamed into a blocked name could not be removed or replaced afterwards
|
|
1118
|
+
expect(toBlocked.status).toBe(400);
|
|
1119
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/rename', {
|
|
1120
|
+
method: 'POST',
|
|
1121
|
+
headers: { 'content-type': 'application/json' },
|
|
1122
|
+
body: JSON.stringify({
|
|
1123
|
+
oldPath: '/data/x.sh',
|
|
1124
|
+
newPath: '/data/new.csv'
|
|
1125
|
+
})
|
|
1126
|
+
}))];
|
|
1127
|
+
case 3:
|
|
1128
|
+
fromBlocked = _a.sent();
|
|
1129
|
+
expect(fromBlocked.status).toBe(400);
|
|
1130
|
+
return [2 /*return*/];
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
}); });
|
|
1134
|
+
it('refuses a file where a folder already holds the path', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1135
|
+
var dispatch, form, atFolder;
|
|
1136
|
+
return __generator(this, function (_a) {
|
|
1137
|
+
switch (_a.label) {
|
|
1138
|
+
case 0:
|
|
1139
|
+
dispatch = seeded().dispatch;
|
|
1140
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/folder', {
|
|
1141
|
+
method: 'POST',
|
|
1142
|
+
headers: { 'content-type': 'application/json' },
|
|
1143
|
+
body: JSON.stringify({ folderPath: '/data/reports' })
|
|
1144
|
+
})];
|
|
1145
|
+
case 1:
|
|
1146
|
+
_a.sent();
|
|
1147
|
+
form = new FormData();
|
|
1148
|
+
form.set('file', new Blob(['r']), 'r.csv');
|
|
1149
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1150
|
+
encodeURIComponent('/data/reports'), { method: 'POST', body: form }))
|
|
1151
|
+
// the server fails this at the filesystem (EISDIR); naming it as a conflict
|
|
1152
|
+
// keeps the drive from holding one path as both a file and a folder
|
|
1153
|
+
];
|
|
1154
|
+
case 2:
|
|
1155
|
+
atFolder = _a.sent();
|
|
1156
|
+
// the server fails this at the filesystem (EISDIR); naming it as a conflict
|
|
1157
|
+
// keeps the drive from holding one path as both a file and a folder
|
|
1158
|
+
expect(atFolder.status).toBe(409);
|
|
1159
|
+
return [2 /*return*/];
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
}); });
|
|
1163
|
+
it('refuses a file under a path that is already a file', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1164
|
+
var dispatch, form, child, under, stillAFile, _a;
|
|
1165
|
+
return __generator(this, function (_b) {
|
|
1166
|
+
switch (_b.label) {
|
|
1167
|
+
case 0:
|
|
1168
|
+
dispatch = seeded().dispatch;
|
|
1169
|
+
form = new FormData();
|
|
1170
|
+
form.set('file', new Blob(['a']), 'a.csv');
|
|
1171
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/a.csv'), { method: 'POST', body: form })];
|
|
1172
|
+
case 1:
|
|
1173
|
+
_b.sent();
|
|
1174
|
+
child = new FormData();
|
|
1175
|
+
child.set('file', new Blob(['b']), 'b.csv');
|
|
1176
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1177
|
+
encodeURIComponent('/data/a.csv/child.csv'), { method: 'POST', body: child }))
|
|
1178
|
+
// mkdirp would otherwise turn the file into a folder to hold the child
|
|
1179
|
+
];
|
|
1180
|
+
case 2:
|
|
1181
|
+
under = _b.sent();
|
|
1182
|
+
// mkdirp would otherwise turn the file into a folder to hold the child
|
|
1183
|
+
expect(under.status).toBe(400);
|
|
1184
|
+
expect(under.body).toBe('An ancestor of the path is a file: /data/a.csv');
|
|
1185
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/a.csv'))];
|
|
1186
|
+
case 3:
|
|
1187
|
+
stillAFile = _b.sent();
|
|
1188
|
+
_a = expect;
|
|
1189
|
+
return [4 /*yield*/, stillAFile.text()];
|
|
1190
|
+
case 4:
|
|
1191
|
+
_a.apply(void 0, [_b.sent()]).toBe('a');
|
|
1192
|
+
return [2 /*return*/];
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
}); });
|
|
1196
|
+
it('serves a download with the type its extension implies', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1197
|
+
var dispatch, form, csv, _a, sas, program;
|
|
1198
|
+
return __generator(this, function (_b) {
|
|
1199
|
+
switch (_b.label) {
|
|
1200
|
+
case 0:
|
|
1201
|
+
dispatch = seeded().dispatch;
|
|
1202
|
+
form = new FormData();
|
|
1203
|
+
form.set('file', new Blob(['id,name\n1,Ann\n']), 'users.csv');
|
|
1204
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1205
|
+
encodeURIComponent('/data/users.csv'), { method: 'POST', body: form })];
|
|
1206
|
+
case 1:
|
|
1207
|
+
_b.sent();
|
|
1208
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1209
|
+
encodeURIComponent('/data/users.csv'))
|
|
1210
|
+
// an upload is stored as bytes, as multer leaves it on the server; the
|
|
1211
|
+
// extension is what makes it answer text/csv rather than octet-stream
|
|
1212
|
+
];
|
|
1213
|
+
case 2:
|
|
1214
|
+
csv = _b.sent();
|
|
1215
|
+
// an upload is stored as bytes, as multer leaves it on the server; the
|
|
1216
|
+
// extension is what makes it answer text/csv rather than octet-stream
|
|
1217
|
+
expect(csv.headers.get('content-type')).toBe('text/csv');
|
|
1218
|
+
_a = expect;
|
|
1219
|
+
return [4 /*yield*/, csv.text()];
|
|
1220
|
+
case 3:
|
|
1221
|
+
_a.apply(void 0, [_b.sent()]).toBe('id,name\n1,Ann\n');
|
|
1222
|
+
sas = new FormData();
|
|
1223
|
+
sas.set('file', new Blob(['data _null_; run;']), 'p.sas');
|
|
1224
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/p.sas'), { method: 'POST', body: sas })];
|
|
1225
|
+
case 4:
|
|
1226
|
+
_b.sent();
|
|
1227
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/p.sas'))];
|
|
1228
|
+
case 5:
|
|
1229
|
+
program = _b.sent();
|
|
1230
|
+
expect(program.headers.get('content-type')).toBe('text/plain');
|
|
1231
|
+
return [2 /*return*/];
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
}); });
|
|
1235
|
+
it('refuses an upload larger than the host allows, before reading it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1236
|
+
var dispatch, runtime, form, response;
|
|
1237
|
+
return __generator(this, function (_a) {
|
|
1238
|
+
switch (_a.label) {
|
|
1239
|
+
case 0:
|
|
1240
|
+
dispatch = seeded().dispatch;
|
|
1241
|
+
runtime = loadWorker().runtime;
|
|
1242
|
+
form = new FormData();
|
|
1243
|
+
form.set('file', new Blob([new Uint8Array(runtime.MAX_UPLOAD_BYTES + 1)]), 'big.bin');
|
|
1244
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1245
|
+
encodeURIComponent('/data/big.bin'), { method: 'POST', body: form }))];
|
|
1246
|
+
case 1:
|
|
1247
|
+
response = _a.sent();
|
|
1248
|
+
expect(response.status).toBe(413);
|
|
1249
|
+
expect(response.body).toContain('larger than the browser host allows');
|
|
1250
|
+
return [2 /*return*/];
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
}); });
|
|
1254
|
+
it('renames a file through the route the API documents', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1255
|
+
var dispatch, form, renamed, read, _a;
|
|
1256
|
+
return __generator(this, function (_b) {
|
|
1257
|
+
switch (_b.label) {
|
|
1258
|
+
case 0:
|
|
1259
|
+
dispatch = seeded().dispatch;
|
|
1260
|
+
form = new FormData();
|
|
1261
|
+
form.set('file', new Blob(['r']), 'r.csv');
|
|
1262
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/old.csv'), { method: 'POST', body: form })];
|
|
1263
|
+
case 1:
|
|
1264
|
+
_b.sent();
|
|
1265
|
+
return [4 /*yield*/, answer(dispatch('/SASjsApi/drive/rename', {
|
|
1266
|
+
method: 'POST',
|
|
1267
|
+
headers: { 'content-type': 'application/json' },
|
|
1268
|
+
body: JSON.stringify({
|
|
1269
|
+
oldPath: '/data/old.csv',
|
|
1270
|
+
newPath: '/data/new.csv'
|
|
1271
|
+
})
|
|
1272
|
+
}))];
|
|
1273
|
+
case 2:
|
|
1274
|
+
renamed = _b.sent();
|
|
1275
|
+
expect(renamed.body).toEqual({ status: 'success' });
|
|
1276
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent('/data/new.csv'))];
|
|
1277
|
+
case 3:
|
|
1278
|
+
read = _b.sent();
|
|
1279
|
+
_a = expect;
|
|
1280
|
+
return [4 /*yield*/, read.text()];
|
|
1281
|
+
case 4:
|
|
1282
|
+
_a.apply(void 0, [_b.sent()]).toBe('r');
|
|
1283
|
+
return [2 /*return*/];
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
}); });
|
|
1287
|
+
});
|
|
716
1288
|
});
|
package/package.json
CHANGED