@sasjs/cli 6.19.3 → 6.19.4
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.
|
@@ -575,6 +575,19 @@ const payloadTooLarge = (limit) => {
|
|
|
575
575
|
*/
|
|
576
576
|
const readJsonBody = async (request) => {
|
|
577
577
|
const body = await readJson(request)
|
|
578
|
+
|
|
579
|
+
// The bridge forwards a JSON body as `json` - a request the page made with
|
|
580
|
+
// `JSON.stringify` has no fields for the envelope it wraps everything in, so
|
|
581
|
+
// the payload is the whole point of the message. Reading only `params` here
|
|
582
|
+
// dropped it: `code/execute` and `code/lint` arrived with no code.
|
|
583
|
+
if (
|
|
584
|
+
body &&
|
|
585
|
+
typeof body === 'object' &&
|
|
586
|
+
body.json &&
|
|
587
|
+
typeof body.json === 'object'
|
|
588
|
+
)
|
|
589
|
+
return { body: body.json, files: [] }
|
|
590
|
+
|
|
578
591
|
if (
|
|
579
592
|
body &&
|
|
580
593
|
typeof body === 'object' &&
|
|
@@ -988,8 +1001,21 @@ const routeRequest = async (request, url) => {
|
|
|
988
1001
|
return json(answer.body, answer.status)
|
|
989
1002
|
} catch (e) {
|
|
990
1003
|
const message = (e && e.stack) || String(e)
|
|
991
|
-
|
|
992
|
-
|
|
1004
|
+
const status = (e && e.status) || 500
|
|
1005
|
+
const refusal = status >= 400 && status < 500
|
|
1006
|
+
|
|
1007
|
+
// A 4xx is an ANSWER: the API decided, the caller was told, and a client
|
|
1008
|
+
// that expected it (Studio creates its default program and accepts the 409)
|
|
1009
|
+
// has already handled it. Broadcasting it put four console errors on a page
|
|
1010
|
+
// whose only fault was opening a file that was already there. A 5xx is the
|
|
1011
|
+
// host failing, and that is worth reporting to the page.
|
|
1012
|
+
logEntry(
|
|
1013
|
+
`${request.method} ${path} ${refusal ? 'refused' : 'ERROR'} ${
|
|
1014
|
+
(e && e.message) || message
|
|
1015
|
+
}`
|
|
1016
|
+
)
|
|
1017
|
+
|
|
1018
|
+
if (!refusal) broadcast({ type: 'sasjs-host-error', path, message })
|
|
993
1019
|
// A refusal the API decided on carries its status and, where the route
|
|
994
1020
|
// answers an envelope, the body to send. The drive's routes send the message
|
|
995
1021
|
// itself as plain text - the server answers them with `res.status(code)
|
|
@@ -115,9 +115,14 @@ var loadWorker = function (base) {
|
|
|
115
115
|
handlers[type].push(fn);
|
|
116
116
|
},
|
|
117
117
|
skipWaiting: function () { },
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
// a test can put a client here to observe what the host broadcasts; with
|
|
119
|
+
// none, a message reaches nobody and nothing can be asserted about it
|
|
120
|
+
clients: {
|
|
121
|
+
claim: function () { },
|
|
122
|
+
matchAll: function () { return __awaiter(void 0, void 0, void 0, function () { var _a; return __generator(this, function (_b) {
|
|
123
|
+
return [2 /*return*/, (_a = global.__mockHostClients) !== null && _a !== void 0 ? _a : []];
|
|
124
|
+
}); }); }
|
|
125
|
+
}
|
|
121
126
|
};
|
|
122
127
|
global.self = self;
|
|
123
128
|
global.importScripts = function (path) {
|
|
@@ -1233,6 +1238,83 @@ describe('service worker host', function () {
|
|
|
1233
1238
|
}
|
|
1234
1239
|
});
|
|
1235
1240
|
}); });
|
|
1241
|
+
/**
|
|
1242
|
+
* A refusal is the API's answer, and the page that asked gets it. Reporting it
|
|
1243
|
+
* as a host error put console errors on screens that had handled the refusal
|
|
1244
|
+
* themselves - Studio's 409 being the one that started this.
|
|
1245
|
+
*/
|
|
1246
|
+
it('reports a host failure to the page, and stays quiet about a refusal', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1247
|
+
var worker, messages, write, hostErrors, failed, _a, _b;
|
|
1248
|
+
return __generator(this, function (_c) {
|
|
1249
|
+
switch (_c.label) {
|
|
1250
|
+
case 0:
|
|
1251
|
+
worker = seeded();
|
|
1252
|
+
messages = [];
|
|
1253
|
+
global.__mockHostClients = [
|
|
1254
|
+
{ postMessage: function (message) { return messages.push(message); } }
|
|
1255
|
+
];
|
|
1256
|
+
write = function () {
|
|
1257
|
+
var form = new FormData();
|
|
1258
|
+
form.set('file', new Blob(['x']), 'new.txt');
|
|
1259
|
+
return worker.dispatch('/SASjsApi/drive/file?_filePath=' +
|
|
1260
|
+
encodeURIComponent('/Public/new.txt'), { method: 'POST', body: form });
|
|
1261
|
+
};
|
|
1262
|
+
hostErrors = function () {
|
|
1263
|
+
return messages.filter(function (message) { return message.type === 'sasjs-host-error'; }).length;
|
|
1264
|
+
};
|
|
1265
|
+
// a drive that cannot be written: a failure of the host, not an answer
|
|
1266
|
+
worker.refuseWrites(true);
|
|
1267
|
+
return [4 /*yield*/, write()];
|
|
1268
|
+
case 1:
|
|
1269
|
+
failed = _c.sent();
|
|
1270
|
+
expect(failed.status).toBeGreaterThanOrEqual(500);
|
|
1271
|
+
expect(hostErrors()).toBe(1);
|
|
1272
|
+
// a create over a file that is already there: the API answered, and the
|
|
1273
|
+
// caller handles it
|
|
1274
|
+
messages.length = 0;
|
|
1275
|
+
worker.refuseWrites(false);
|
|
1276
|
+
_a = expect;
|
|
1277
|
+
return [4 /*yield*/, write()];
|
|
1278
|
+
case 2:
|
|
1279
|
+
_a.apply(void 0, [(_c.sent()).status]).toBe(200);
|
|
1280
|
+
_b = expect;
|
|
1281
|
+
return [4 /*yield*/, write()];
|
|
1282
|
+
case 3:
|
|
1283
|
+
_b.apply(void 0, [(_c.sent()).status]).toBe(409);
|
|
1284
|
+
expect(hostErrors()).toBe(0);
|
|
1285
|
+
delete global.__mockHostClients;
|
|
1286
|
+
return [2 /*return*/];
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
}); });
|
|
1290
|
+
it('keeps the payload of a JSON body the page bridge forwarded', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1291
|
+
var dispatch, body, response, findings;
|
|
1292
|
+
return __generator(this, function (_a) {
|
|
1293
|
+
switch (_a.label) {
|
|
1294
|
+
case 0:
|
|
1295
|
+
dispatch = seeded().dispatch;
|
|
1296
|
+
body = JSON.stringify({
|
|
1297
|
+
params: {},
|
|
1298
|
+
tables: [],
|
|
1299
|
+
json: { code: 'data _null_;\n x = 1; \nrun;\n', filePath: '/a.sas' }
|
|
1300
|
+
});
|
|
1301
|
+
return [4 /*yield*/, dispatch('/SASjsApi/code/lint', {
|
|
1302
|
+
method: 'POST',
|
|
1303
|
+
headers: { 'content-type': 'application/json' },
|
|
1304
|
+
body: body
|
|
1305
|
+
})];
|
|
1306
|
+
case 1:
|
|
1307
|
+
response = _a.sent();
|
|
1308
|
+
expect(response.status).toBe(200);
|
|
1309
|
+
return [4 /*yield*/, response.json()];
|
|
1310
|
+
case 2:
|
|
1311
|
+
findings = _a.sent();
|
|
1312
|
+
expect(Array.isArray(findings)).toBe(true);
|
|
1313
|
+
expect(JSON.stringify(findings)).toContain('trailing spaces');
|
|
1314
|
+
return [2 /*return*/];
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
}); });
|
|
1236
1318
|
it('answers 404 for a drive file the drive does not hold', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1237
1319
|
var dispatch, response, _a;
|
|
1238
1320
|
return __generator(this, function (_b) {
|
package/package.json
CHANGED