@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.
@@ -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.json()];
517
+ return [4 /*yield*/, response.text()];
432
518
  case 2:
433
- _a.apply(void 0, [(_b.sent()).message]).toBe('File not found');
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.json()];
570
+ return [4 /*yield*/, response.text()];
485
571
  case 2:
486
- _a.apply(void 0, [(_c.sent()).message]).toContain('Corrupt binary entry');
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
@@ -123,5 +123,5 @@
123
123
  "ws": "8.21.0",
124
124
  "nanoid": "3.3.18"
125
125
  },
126
- "version": "6.10.0"
126
+ "version": "6.11.0"
127
127
  }