@sasjs/cli 6.26.1 → 6.27.1
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.
|
@@ -698,63 +698,50 @@ const directorySource = {
|
|
|
698
698
|
}
|
|
699
699
|
|
|
700
700
|
/**
|
|
701
|
-
* The app
|
|
701
|
+
* The app stream, over the drive this site carries.
|
|
702
702
|
*
|
|
703
|
-
* The
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
703
|
+
* The package owns the index and every rule about it: what a publish must say,
|
|
704
|
+
* when an id is minted and when a redeploy keeps it, what the listing answers,
|
|
705
|
+
* how an app's files are found, what a removal removes. This host owns only the
|
|
706
|
+
* bytes - the drive store, which is persisted with the rest of the drive, so an
|
|
707
|
+
* app published in a mock survives a reload exactly as it would on a server.
|
|
708
708
|
*
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
709
|
+
* Nothing is published until a deploy publishes something, which is what makes
|
|
710
|
+
* the mock honest: a `sasjs deploy` against it, or a bundle posted to
|
|
711
|
+
* `/SASjsApi/drive/deploy`, is listed and served here the way it is on a server.
|
|
712
|
+
* The folder the site itself was built for is deliberately not seeded as an app:
|
|
713
|
+
* its files are this site's static build, not a deployment in the drive, so a
|
|
714
|
+
* tile for it would open a 404.
|
|
713
715
|
*/
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
const appStreamService = () => {
|
|
718
|
-
if (!appStreamPromise)
|
|
719
|
-
appStreamPromise = (async () => {
|
|
720
|
-
let appLoc
|
|
721
|
-
try {
|
|
722
|
-
const config = await (await fetch('host/mock-config.json')).json()
|
|
723
|
-
appLoc = config && config.appLoc
|
|
724
|
-
} catch (e) {
|
|
725
|
-
appLoc = undefined
|
|
726
|
-
}
|
|
716
|
+
const APP_STREAM = API.createAppStreamService(
|
|
717
|
+
API.createDriveAppStreamStore(API.createDriveService(DRIVE_STORE))
|
|
718
|
+
)
|
|
727
719
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
720
|
+
/**
|
|
721
|
+
* The app stream's own web surface: the landing page, the listing, an app's
|
|
722
|
+
* files and the removal of one.
|
|
723
|
+
*
|
|
724
|
+
* Mounted at the short spelling, which is what the interface links to; the
|
|
725
|
+
* canonical one is translated to it below, so both answer.
|
|
726
|
+
*
|
|
727
|
+
* `basePath` is the site's own folder plus that spelling, and the paths handed
|
|
728
|
+
* to the package include the folder too - the package builds an app's URL, its
|
|
729
|
+
* tiles and its logo links from `basePath`, so a surface mounted at `/app` on a
|
|
730
|
+
* site served from `/demo` would answer a listing whose every link pointed at
|
|
731
|
+
* the site root. Both sides take the same base and the answers land inside the
|
|
732
|
+
* folder.
|
|
733
|
+
*/
|
|
734
|
+
let appStreamWeb
|
|
735
|
+
const appStreamSurface = () => {
|
|
736
|
+
if (!appStreamWeb)
|
|
737
|
+
appStreamWeb = API.createAppStreamWebService(
|
|
738
|
+
APP_STREAM,
|
|
739
|
+
API.createDriveService(DRIVE_STORE),
|
|
740
|
+
{ basePath: `${BASE}${SHORT_APP_STREAM_PATH}` }
|
|
741
|
+
)
|
|
742
|
+
return appStreamWeb
|
|
745
743
|
}
|
|
746
744
|
|
|
747
|
-
/** The package's service, built once the config names the folder. */
|
|
748
|
-
const APP_STREAM = new Proxy(
|
|
749
|
-
{},
|
|
750
|
-
{
|
|
751
|
-
get:
|
|
752
|
-
(_target, method) =>
|
|
753
|
-
(...args) =>
|
|
754
|
-
appStreamService().then((service) => service[method](...args))
|
|
755
|
-
}
|
|
756
|
-
)
|
|
757
|
-
|
|
758
745
|
/**
|
|
759
746
|
* The page at `/`.
|
|
760
747
|
*
|
|
@@ -1247,18 +1234,10 @@ const routeRequest = async (request, url) => {
|
|
|
1247
1234
|
//
|
|
1248
1235
|
// `/app` is the spelling the interface links to and the server serves; the
|
|
1249
1236
|
// canonical `/AppStream` still answers, because a client built before the
|
|
1250
|
-
// short paths existed asks for it.
|
|
1251
|
-
//
|
|
1252
|
-
//
|
|
1253
|
-
//
|
|
1254
|
-
//
|
|
1255
|
-
// The listing is empty rather than read from `registry()`, which records this
|
|
1256
|
-
// deployment's own folder so the delete route has something to remove: the
|
|
1257
|
-
// stream really does serve nothing at `/app/<name>/` here, and a listing that
|
|
1258
|
-
// named it would offer a tile that 404s.
|
|
1259
|
-
// `path` has its trailing slashes stripped, and the landing answers differ by
|
|
1260
|
-
// exactly that, so they are decided on the request's own pathname - minus the
|
|
1261
|
-
// folder the site is served from, which the patterns do not name.
|
|
1237
|
+
// short paths existed asks for it. `/AppStream` itself redirects to `/app`,
|
|
1238
|
+
// exactly as the server redirects it, and every request under either prefix
|
|
1239
|
+
// is handed to the package as the short one - so a mock lists and serves what
|
|
1240
|
+
// it published at the same URLs, with the same headers, as a server.
|
|
1262
1241
|
const stream = withoutBase(url.pathname)
|
|
1263
1242
|
|
|
1264
1243
|
if (/^\/AppStream$/i.test(stream))
|
|
@@ -1267,31 +1246,31 @@ const routeRequest = async (request, url) => {
|
|
|
1267
1246
|
headers: { location: `${BASE}${SHORT_APP_STREAM_PATH}` }
|
|
1268
1247
|
})
|
|
1269
1248
|
|
|
1270
|
-
if (
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
].join(''),
|
|
1282
|
-
{ status: 200, headers: { 'content-type': 'text/html; charset=utf-8' } }
|
|
1283
|
-
)
|
|
1249
|
+
if (/^\/(app|AppStream)(\/|$)/i.test(stream)) {
|
|
1250
|
+
// the path the surface matches on includes the site's folder, which is what
|
|
1251
|
+
// it builds the answers' URLs from; `/AppStream` is translated to the short
|
|
1252
|
+
// spelling so a client built before it existed reaches the same surface
|
|
1253
|
+
const page = await appStreamSurface().request({
|
|
1254
|
+
method: request.method,
|
|
1255
|
+
path: url.pathname.replace(
|
|
1256
|
+
/\/AppStream(?=\/|$)/i,
|
|
1257
|
+
SHORT_APP_STREAM_PATH
|
|
1258
|
+
)
|
|
1259
|
+
})
|
|
1284
1260
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1261
|
+
// a removal changes the index, which lives in the drive: persisted in the
|
|
1262
|
+
// same breath as any other change, or a reload would bring the app back
|
|
1263
|
+
if (request.method !== 'GET') await persistChanges()
|
|
1287
1264
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1265
|
+
const headers = Object.assign({}, page.headers)
|
|
1266
|
+
// a location the package states is already inside the folder, because the
|
|
1267
|
+
// base it built it from carries it
|
|
1268
|
+
if (page.location) headers.location = page.location
|
|
1269
|
+
if (page.contentType) headers['content-type'] = page.contentType
|
|
1270
|
+
|
|
1271
|
+
return new Response(
|
|
1272
|
+
page.body === undefined || page.body === null ? null : page.body,
|
|
1273
|
+
{ status: page.status, headers }
|
|
1295
1274
|
)
|
|
1296
1275
|
}
|
|
1297
1276
|
|
|
@@ -1339,6 +1318,13 @@ const routeRequest = async (request, url) => {
|
|
|
1339
1318
|
const answer = await API.dispatch(SERVICES, routeId, apiRequest)
|
|
1340
1319
|
if (request.method !== 'GET') await persistChanges()
|
|
1341
1320
|
|
|
1321
|
+
// A deploy that published an app states its URL against the package's own
|
|
1322
|
+
// default mount. This site may be served from a folder, so the answer has to
|
|
1323
|
+
// name the URL a reader can actually open - the folder the surface above is
|
|
1324
|
+
// mounted under, which is the same base its links are built from.
|
|
1325
|
+
if (BASE && answer.body && answer.body.app && answer.body.app.url)
|
|
1326
|
+
answer.body.app.url = `${BASE}${answer.body.app.url}`
|
|
1327
|
+
|
|
1342
1328
|
// the shell is a page, not a file the caller named: the document states
|
|
1343
1329
|
// text/html for it, and the extension of `/` says nothing
|
|
1344
1330
|
if (routeId === 'GET /')
|
|
@@ -1371,12 +1357,14 @@ const routeRequest = async (request, url) => {
|
|
|
1371
1357
|
|
|
1372
1358
|
if (!refusal) broadcast({ type: 'sasjs-host-error', path, message })
|
|
1373
1359
|
// A refusal the API decided on carries its status and, where the route
|
|
1374
|
-
// answers an envelope, the body to send. The
|
|
1375
|
-
//
|
|
1376
|
-
//
|
|
1377
|
-
//
|
|
1360
|
+
// answers an envelope, the body to send. The deploy routes state one in the
|
|
1361
|
+
// document - a refusal there is `{status: 'failure', message}` - while the
|
|
1362
|
+
// other drive routes send the message itself as plain text, which is what the
|
|
1363
|
+
// server's `res.status(code).send(err.message)` does.
|
|
1378
1364
|
if (API.isApiError(e)) {
|
|
1379
1365
|
if (e.body !== undefined) return json(e.body, e.status)
|
|
1366
|
+
if (/\/SASjsApi\/drive\/deploy(\/|$)/.test(apiPath))
|
|
1367
|
+
return json({ status: 'failure', message: e.message }, e.status)
|
|
1380
1368
|
if (apiPath.includes('/SASjsApi/drive/')) return text(e.message, e.status)
|
|
1381
1369
|
return json({ message: e.message }, e.status)
|
|
1382
1370
|
}
|
|
@@ -955,8 +955,8 @@ describe('service worker host', function () {
|
|
|
955
955
|
}
|
|
956
956
|
});
|
|
957
957
|
}); });
|
|
958
|
-
it('imports the fixture directory
|
|
959
|
-
var dispatch, synced, _a
|
|
958
|
+
it('imports the fixture directory', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
959
|
+
var dispatch, synced, _a;
|
|
960
960
|
return __generator(this, function (_b) {
|
|
961
961
|
switch (_b.label) {
|
|
962
962
|
case 0:
|
|
@@ -969,28 +969,6 @@ describe('service worker host', function () {
|
|
|
969
969
|
return [4 /*yield*/, synced.json()];
|
|
970
970
|
case 2:
|
|
971
971
|
_a.apply(void 0, [_b.sent()]).toEqual({ userCount: 0, groupCount: 0 });
|
|
972
|
-
siteAppId = '6f1c1c1e-6a1e-4c4a-9d1a-1b2c3d4e5f60';
|
|
973
|
-
return [4 /*yield*/, dispatch('/SASjsApi/drive/deploy', {
|
|
974
|
-
method: 'DELETE',
|
|
975
|
-
headers: { 'content-type': 'application/json' },
|
|
976
|
-
body: JSON.stringify({ id: siteAppId })
|
|
977
|
-
})];
|
|
978
|
-
case 3:
|
|
979
|
-
unpublished = _b.sent();
|
|
980
|
-
expect(unpublished.status).toBe(200);
|
|
981
|
-
return [4 /*yield*/, unpublished.json()];
|
|
982
|
-
case 4:
|
|
983
|
-
removed = _b.sent();
|
|
984
|
-
expect(removed.status).toBe('success');
|
|
985
|
-
expect(removed.app.id).toBe(siteAppId);
|
|
986
|
-
return [4 /*yield*/, dispatch('/SASjsApi/drive/deploy', {
|
|
987
|
-
method: 'DELETE',
|
|
988
|
-
headers: { 'content-type': 'application/json' },
|
|
989
|
-
body: JSON.stringify({ id: '00000000-0000-4000-8000-000000000000' })
|
|
990
|
-
})];
|
|
991
|
-
case 5:
|
|
992
|
-
other = _b.sent();
|
|
993
|
-
expect(other.status).toBe(404);
|
|
994
972
|
return [2 /*return*/];
|
|
995
973
|
}
|
|
996
974
|
});
|
|
@@ -1023,6 +1001,61 @@ describe('service worker host', function () {
|
|
|
1023
1001
|
}
|
|
1024
1002
|
});
|
|
1025
1003
|
}); });
|
|
1004
|
+
it('keeps an app’s URL and tiles inside the folder the site is served from', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1005
|
+
var worker, archive, form, published, app, listing, apps, landing, _a, page, _b;
|
|
1006
|
+
return __generator(this, function (_c) {
|
|
1007
|
+
switch (_c.label) {
|
|
1008
|
+
case 0:
|
|
1009
|
+
worker = loadWorker('/demo');
|
|
1010
|
+
return [4 /*yield*/, zipOf({
|
|
1011
|
+
'services/web/index.html': '<h1>Deployed</h1>'
|
|
1012
|
+
})];
|
|
1013
|
+
case 1:
|
|
1014
|
+
archive = _c.sent();
|
|
1015
|
+
form = new FormData();
|
|
1016
|
+
form.set('appLoc', '/Public/apps/demo');
|
|
1017
|
+
form.set('streamWebFolder', 'web');
|
|
1018
|
+
form.set('file', new Blob([new Uint8Array(archive)]), 'deploy.zip');
|
|
1019
|
+
return [4 /*yield*/, worker.dispatch('/demo/SASjsApi/drive/deploy/upload', { method: 'POST', body: form })];
|
|
1020
|
+
case 2:
|
|
1021
|
+
published = _c.sent();
|
|
1022
|
+
expect(published.status).toBe(200);
|
|
1023
|
+
return [4 /*yield*/, published.json()];
|
|
1024
|
+
case 3:
|
|
1025
|
+
app = (_c.sent()).app;
|
|
1026
|
+
// The package builds an app's URL - and the landing page's links - from the
|
|
1027
|
+
// base it was mounted on. Mounted at `/app` on a site served from `/demo`,
|
|
1028
|
+
// every one of them would point at the site root and 404, so this asserts
|
|
1029
|
+
// the mount and the requests carry the same folder.
|
|
1030
|
+
expect(app.url).toBe("/demo/app/".concat(app.id, "/"));
|
|
1031
|
+
return [4 /*yield*/, worker.dispatch('/demo/app/apps.json')];
|
|
1032
|
+
case 4:
|
|
1033
|
+
listing = _c.sent();
|
|
1034
|
+
return [4 /*yield*/, listing.json()];
|
|
1035
|
+
case 5:
|
|
1036
|
+
apps = (_c.sent()).apps;
|
|
1037
|
+
expect(apps.map(function (entry) { return entry.url; })).toEqual([
|
|
1038
|
+
"/demo/app/".concat(app.id, "/")
|
|
1039
|
+
]);
|
|
1040
|
+
return [4 /*yield*/, worker.dispatch('/demo/app/')];
|
|
1041
|
+
case 6:
|
|
1042
|
+
landing = _c.sent();
|
|
1043
|
+
_a = expect;
|
|
1044
|
+
return [4 /*yield*/, landing.text()];
|
|
1045
|
+
case 7:
|
|
1046
|
+
_a.apply(void 0, [_c.sent()]).toContain("href=\"/demo/app/".concat(app.id, "/\""));
|
|
1047
|
+
return [4 /*yield*/, worker.dispatch("/demo/app/".concat(app.id, "/"))];
|
|
1048
|
+
case 8:
|
|
1049
|
+
page = _c.sent();
|
|
1050
|
+
expect(page.status).toBe(200);
|
|
1051
|
+
_b = expect;
|
|
1052
|
+
return [4 /*yield*/, page.text()];
|
|
1053
|
+
case 9:
|
|
1054
|
+
_b.apply(void 0, [_c.sent()]).toContain('<h1>Deployed</h1>');
|
|
1055
|
+
return [2 /*return*/];
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}); });
|
|
1026
1059
|
it('answers every route under the folder the site is published in', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1027
1060
|
var worker, info, _a, page, folder, stream, landing, apps, _b, aliased, _c;
|
|
1028
1061
|
var _d;
|
|
@@ -1233,10 +1266,17 @@ describe('service worker host', function () {
|
|
|
1233
1266
|
case 1:
|
|
1234
1267
|
empty = _c.sent();
|
|
1235
1268
|
expect(empty.status).toBe(400);
|
|
1269
|
+
// the deploy routes answer a refusal as the envelope the document states for
|
|
1270
|
+
// them, which is what the corpus pins for the same route
|
|
1236
1271
|
_a = expect;
|
|
1237
|
-
return [4 /*yield*/, empty.
|
|
1272
|
+
return [4 /*yield*/, empty.json()];
|
|
1238
1273
|
case 2:
|
|
1239
|
-
|
|
1274
|
+
// the deploy routes answer a refusal as the envelope the document states for
|
|
1275
|
+
// them, which is what the corpus pins for the same route
|
|
1276
|
+
_a.apply(void 0, [_c.sent()]).toEqual({
|
|
1277
|
+
status: 'failure',
|
|
1278
|
+
message: '"file" is not present.'
|
|
1279
|
+
});
|
|
1240
1280
|
form = new FormData();
|
|
1241
1281
|
form.set('file', new Blob([new Uint8Array([1, 2, 3])]), 'not-a-zip.zip');
|
|
1242
1282
|
return [4 /*yield*/, dispatch('/SASjsApi/drive/deploy/upload', {
|
|
@@ -1247,9 +1287,12 @@ describe('service worker host', function () {
|
|
|
1247
1287
|
notZip = _c.sent();
|
|
1248
1288
|
expect(notZip.status).toBe(400);
|
|
1249
1289
|
_b = expect;
|
|
1250
|
-
return [4 /*yield*/, notZip.
|
|
1290
|
+
return [4 /*yield*/, notZip.json()];
|
|
1251
1291
|
case 4:
|
|
1252
|
-
_b.apply(void 0, [_c.sent()]).
|
|
1292
|
+
_b.apply(void 0, [_c.sent()]).toEqual({
|
|
1293
|
+
status: 'failure',
|
|
1294
|
+
message: 'The archive is not a zip file.'
|
|
1295
|
+
});
|
|
1253
1296
|
return [2 /*return*/];
|
|
1254
1297
|
}
|
|
1255
1298
|
});
|
|
@@ -1502,120 +1545,211 @@ describe('service worker host', function () {
|
|
|
1502
1545
|
}
|
|
1503
1546
|
});
|
|
1504
1547
|
}); });
|
|
1505
|
-
it('
|
|
1506
|
-
var dispatch, _a, _b, path_2, listing, _c, e_1_1, _d, _e, path_3, landing,
|
|
1507
|
-
var e_1,
|
|
1508
|
-
return __generator(this, function (
|
|
1509
|
-
switch (
|
|
1548
|
+
it('publishes, lists, serves and removes an app through the package surface', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1549
|
+
var dispatch, _a, _b, path_2, listing, _c, e_1_1, _d, _e, path_3, landing, e_2_1, root, archive, form, published, app, _f, _g, path_4, listing, _h, e_3_1, _j, _k, path_5, page, _l, e_4_1, folder, _m, refused, missing, removed, _o, after, _p, gone;
|
|
1550
|
+
var e_1, _q, e_2, _r, e_3, _s, e_4, _t;
|
|
1551
|
+
return __generator(this, function (_u) {
|
|
1552
|
+
switch (_u.label) {
|
|
1510
1553
|
case 0:
|
|
1511
1554
|
dispatch = seeded().dispatch;
|
|
1512
|
-
|
|
1555
|
+
_u.label = 1;
|
|
1513
1556
|
case 1:
|
|
1514
|
-
|
|
1557
|
+
_u.trys.push([1, 7, 8, 9]);
|
|
1515
1558
|
_a = __values(['/app/apps.json', '/AppStream/apps.json']), _b = _a.next();
|
|
1516
|
-
|
|
1559
|
+
_u.label = 2;
|
|
1517
1560
|
case 2:
|
|
1518
1561
|
if (!!_b.done) return [3 /*break*/, 6];
|
|
1519
1562
|
path_2 = _b.value;
|
|
1520
1563
|
return [4 /*yield*/, dispatch(path_2)];
|
|
1521
1564
|
case 3:
|
|
1522
|
-
listing =
|
|
1565
|
+
listing = _u.sent();
|
|
1523
1566
|
expect(listing.status).toBe(200);
|
|
1524
1567
|
_c = expect;
|
|
1525
1568
|
return [4 /*yield*/, listing.json()];
|
|
1526
1569
|
case 4:
|
|
1527
|
-
_c.apply(void 0, [
|
|
1528
|
-
|
|
1570
|
+
_c.apply(void 0, [_u.sent()]).toEqual({ apps: [] });
|
|
1571
|
+
_u.label = 5;
|
|
1529
1572
|
case 5:
|
|
1530
1573
|
_b = _a.next();
|
|
1531
1574
|
return [3 /*break*/, 2];
|
|
1532
1575
|
case 6: return [3 /*break*/, 9];
|
|
1533
1576
|
case 7:
|
|
1534
|
-
e_1_1 =
|
|
1577
|
+
e_1_1 = _u.sent();
|
|
1535
1578
|
e_1 = { error: e_1_1 };
|
|
1536
1579
|
return [3 /*break*/, 9];
|
|
1537
1580
|
case 8:
|
|
1538
1581
|
try {
|
|
1539
|
-
if (_b && !_b.done && (
|
|
1582
|
+
if (_b && !_b.done && (_q = _a.return)) _q.call(_a);
|
|
1540
1583
|
}
|
|
1541
1584
|
finally { if (e_1) throw e_1.error; }
|
|
1542
1585
|
return [7 /*endfinally*/];
|
|
1543
1586
|
case 9:
|
|
1544
|
-
|
|
1587
|
+
_u.trys.push([9, 14, 15, 16]);
|
|
1545
1588
|
_d = __values(['/app', '/app/', '/app/index.html']), _e = _d.next();
|
|
1546
|
-
|
|
1589
|
+
_u.label = 10;
|
|
1547
1590
|
case 10:
|
|
1548
|
-
if (!!_e.done) return [3 /*break*/,
|
|
1591
|
+
if (!!_e.done) return [3 /*break*/, 13];
|
|
1549
1592
|
path_3 = _e.value;
|
|
1550
1593
|
return [4 /*yield*/, dispatch(path_3)];
|
|
1551
1594
|
case 11:
|
|
1552
|
-
landing =
|
|
1595
|
+
landing = _u.sent();
|
|
1553
1596
|
expect(landing.status).toBe(200);
|
|
1554
1597
|
expect(landing.headers.get('content-type')).toContain('text/html');
|
|
1555
|
-
|
|
1556
|
-
return [4 /*yield*/, landing.text()];
|
|
1598
|
+
_u.label = 12;
|
|
1557
1599
|
case 12:
|
|
1558
|
-
_f.apply(void 0, [_p.sent()]).toContain('serves no apps');
|
|
1559
|
-
_p.label = 13;
|
|
1560
|
-
case 13:
|
|
1561
1600
|
_e = _d.next();
|
|
1562
1601
|
return [3 /*break*/, 10];
|
|
1563
|
-
case
|
|
1564
|
-
case
|
|
1565
|
-
e_2_1 =
|
|
1602
|
+
case 13: return [3 /*break*/, 16];
|
|
1603
|
+
case 14:
|
|
1604
|
+
e_2_1 = _u.sent();
|
|
1566
1605
|
e_2 = { error: e_2_1 };
|
|
1567
|
-
return [3 /*break*/,
|
|
1568
|
-
case
|
|
1606
|
+
return [3 /*break*/, 16];
|
|
1607
|
+
case 15:
|
|
1569
1608
|
try {
|
|
1570
|
-
if (_e && !_e.done && (
|
|
1609
|
+
if (_e && !_e.done && (_r = _d.return)) _r.call(_d);
|
|
1571
1610
|
}
|
|
1572
1611
|
finally { if (e_2) throw e_2.error; }
|
|
1573
1612
|
return [7 /*endfinally*/];
|
|
1574
|
-
case
|
|
1575
|
-
case
|
|
1576
|
-
root =
|
|
1613
|
+
case 16: return [4 /*yield*/, dispatch('/AppStream')];
|
|
1614
|
+
case 17:
|
|
1615
|
+
root = _u.sent();
|
|
1577
1616
|
expect(root.status).toBe(301);
|
|
1578
1617
|
expect(root.headers.get('location')).toBe('/app');
|
|
1579
|
-
return [4 /*yield*/,
|
|
1618
|
+
return [4 /*yield*/, zipOf({
|
|
1619
|
+
'services/web/index.html': '<h1>Demo</h1>'
|
|
1620
|
+
})];
|
|
1621
|
+
case 18:
|
|
1622
|
+
archive = _u.sent();
|
|
1623
|
+
form = new FormData();
|
|
1624
|
+
form.set('appLoc', '/Public/apps/demo');
|
|
1625
|
+
form.set('streamWebFolder', 'web');
|
|
1626
|
+
form.set('file', new Blob([new Uint8Array(archive)]), 'deploy.zip');
|
|
1627
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/deploy/upload', {
|
|
1628
|
+
method: 'POST',
|
|
1629
|
+
body: form
|
|
1630
|
+
})];
|
|
1580
1631
|
case 19:
|
|
1581
|
-
|
|
1582
|
-
expect(
|
|
1583
|
-
|
|
1584
|
-
return [4 /*yield*/, canonicalLanding.text()];
|
|
1632
|
+
published = _u.sent();
|
|
1633
|
+
expect(published.status).toBe(200);
|
|
1634
|
+
return [4 /*yield*/, published.json()];
|
|
1585
1635
|
case 20:
|
|
1586
|
-
|
|
1587
|
-
|
|
1636
|
+
app = (_u.sent()).app;
|
|
1637
|
+
expect(app.name).toBe('demo');
|
|
1638
|
+
expect(app.url).toBe("/app/".concat(app.id, "/"));
|
|
1639
|
+
_u.label = 21;
|
|
1588
1640
|
case 21:
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1641
|
+
_u.trys.push([21, 27, 28, 29]);
|
|
1642
|
+
_f = __values(['/app/apps.json', '/AppStream/apps.json']), _g = _f.next();
|
|
1643
|
+
_u.label = 22;
|
|
1592
1644
|
case 22:
|
|
1593
|
-
if (!!
|
|
1594
|
-
path_4 =
|
|
1645
|
+
if (!!_g.done) return [3 /*break*/, 26];
|
|
1646
|
+
path_4 = _g.value;
|
|
1595
1647
|
return [4 /*yield*/, dispatch(path_4)];
|
|
1596
1648
|
case 23:
|
|
1597
|
-
|
|
1598
|
-
expect
|
|
1599
|
-
|
|
1600
|
-
return [4 /*yield*/, app.json()];
|
|
1649
|
+
listing = _u.sent();
|
|
1650
|
+
_h = expect;
|
|
1651
|
+
return [4 /*yield*/, listing.json()];
|
|
1601
1652
|
case 24:
|
|
1602
|
-
|
|
1603
|
-
|
|
1653
|
+
_h.apply(void 0, [(_u.sent()).apps[0]]).toMatchObject({
|
|
1654
|
+
id: app.id,
|
|
1655
|
+
name: 'demo',
|
|
1656
|
+
url: "/app/".concat(app.id, "/")
|
|
1657
|
+
});
|
|
1658
|
+
_u.label = 25;
|
|
1604
1659
|
case 25:
|
|
1605
|
-
|
|
1660
|
+
_g = _f.next();
|
|
1606
1661
|
return [3 /*break*/, 22];
|
|
1607
1662
|
case 26: return [3 /*break*/, 29];
|
|
1608
1663
|
case 27:
|
|
1609
|
-
e_3_1 =
|
|
1664
|
+
e_3_1 = _u.sent();
|
|
1610
1665
|
e_3 = { error: e_3_1 };
|
|
1611
1666
|
return [3 /*break*/, 29];
|
|
1612
1667
|
case 28:
|
|
1613
1668
|
try {
|
|
1614
|
-
if (
|
|
1669
|
+
if (_g && !_g.done && (_s = _f.return)) _s.call(_f);
|
|
1615
1670
|
}
|
|
1616
1671
|
finally { if (e_3) throw e_3.error; }
|
|
1617
1672
|
return [7 /*endfinally*/];
|
|
1618
|
-
case 29:
|
|
1673
|
+
case 29:
|
|
1674
|
+
_u.trys.push([29, 35, 36, 37]);
|
|
1675
|
+
_j = __values([
|
|
1676
|
+
"/app/".concat(app.id, "/"),
|
|
1677
|
+
"/app/".concat(app.id, "/index.html"),
|
|
1678
|
+
"/AppStream/".concat(app.id, "/")
|
|
1679
|
+
]), _k = _j.next();
|
|
1680
|
+
_u.label = 30;
|
|
1681
|
+
case 30:
|
|
1682
|
+
if (!!_k.done) return [3 /*break*/, 34];
|
|
1683
|
+
path_5 = _k.value;
|
|
1684
|
+
return [4 /*yield*/, dispatch(path_5)];
|
|
1685
|
+
case 31:
|
|
1686
|
+
page = _u.sent();
|
|
1687
|
+
expect(page.status).toBe(200);
|
|
1688
|
+
_l = expect;
|
|
1689
|
+
return [4 /*yield*/, page.text()];
|
|
1690
|
+
case 32:
|
|
1691
|
+
_l.apply(void 0, [_u.sent()]).toContain('<h1>Demo</h1>');
|
|
1692
|
+
_u.label = 33;
|
|
1693
|
+
case 33:
|
|
1694
|
+
_k = _j.next();
|
|
1695
|
+
return [3 /*break*/, 30];
|
|
1696
|
+
case 34: return [3 /*break*/, 37];
|
|
1697
|
+
case 35:
|
|
1698
|
+
e_4_1 = _u.sent();
|
|
1699
|
+
e_4 = { error: e_4_1 };
|
|
1700
|
+
return [3 /*break*/, 37];
|
|
1701
|
+
case 36:
|
|
1702
|
+
try {
|
|
1703
|
+
if (_k && !_k.done && (_t = _j.return)) _t.call(_j);
|
|
1704
|
+
}
|
|
1705
|
+
finally { if (e_4) throw e_4.error; }
|
|
1706
|
+
return [7 /*endfinally*/];
|
|
1707
|
+
case 37: return [4 /*yield*/, dispatch("/app/".concat(app.id))];
|
|
1708
|
+
case 38:
|
|
1709
|
+
folder = _u.sent();
|
|
1710
|
+
expect(folder.status).toBe(301);
|
|
1711
|
+
expect(folder.headers.get('location')).toBe("/app/".concat(app.id, "/"));
|
|
1712
|
+
_m = expect;
|
|
1713
|
+
return [4 /*yield*/, folder.text()];
|
|
1714
|
+
case 39:
|
|
1715
|
+
_m.apply(void 0, [_u.sent()]).toBe('');
|
|
1716
|
+
return [4 /*yield*/, dispatch('/app/apps.json', {
|
|
1717
|
+
method: 'POST',
|
|
1718
|
+
headers: { 'content-type': 'application/json' },
|
|
1719
|
+
body: '{}'
|
|
1720
|
+
})];
|
|
1721
|
+
case 40:
|
|
1722
|
+
refused = _u.sent();
|
|
1723
|
+
expect(refused.status).toBe(405);
|
|
1724
|
+
expect(refused.headers.get('allow')).toBe('GET');
|
|
1725
|
+
return [4 /*yield*/, dispatch('/app/00000000-0000-4000-8000-000000000000/')];
|
|
1726
|
+
case 41:
|
|
1727
|
+
missing = _u.sent();
|
|
1728
|
+
expect(missing.status).toBe(404);
|
|
1729
|
+
return [4 /*yield*/, dispatch('/SASjsApi/drive/deploy', {
|
|
1730
|
+
method: 'DELETE',
|
|
1731
|
+
headers: { 'content-type': 'application/json' },
|
|
1732
|
+
body: JSON.stringify({ id: app.id })
|
|
1733
|
+
})];
|
|
1734
|
+
case 42:
|
|
1735
|
+
removed = _u.sent();
|
|
1736
|
+
expect(removed.status).toBe(200);
|
|
1737
|
+
_o = expect;
|
|
1738
|
+
return [4 /*yield*/, removed.json()];
|
|
1739
|
+
case 43:
|
|
1740
|
+
_o.apply(void 0, [(_u.sent()).app.id]).toBe(app.id);
|
|
1741
|
+
return [4 /*yield*/, dispatch('/app/apps.json')];
|
|
1742
|
+
case 44:
|
|
1743
|
+
after = _u.sent();
|
|
1744
|
+
_p = expect;
|
|
1745
|
+
return [4 /*yield*/, after.json()];
|
|
1746
|
+
case 45:
|
|
1747
|
+
_p.apply(void 0, [_u.sent()]).toEqual({ apps: [] });
|
|
1748
|
+
return [4 /*yield*/, dispatch("/app/".concat(app.id, "/"))];
|
|
1749
|
+
case 46:
|
|
1750
|
+
gone = _u.sent();
|
|
1751
|
+
expect(gone.status).toBe(404);
|
|
1752
|
+
return [2 /*return*/];
|
|
1619
1753
|
}
|
|
1620
1754
|
});
|
|
1621
1755
|
}); });
|
|
@@ -2507,7 +2641,7 @@ describe('service worker host', function () {
|
|
|
2507
2641
|
}); });
|
|
2508
2642
|
});
|
|
2509
2643
|
describe('conformance corpus', function () {
|
|
2510
|
-
var
|
|
2644
|
+
var e_5, _a, e_6, _b;
|
|
2511
2645
|
/**
|
|
2512
2646
|
* The corpus is what "the same API" means: the cases @sasjs/api runs against
|
|
2513
2647
|
* its own services, and against a live sasjs/server, run here against this
|
|
@@ -2516,7 +2650,7 @@ describe('service worker host', function () {
|
|
|
2516
2650
|
* used to have - and it is a test rather than a review.
|
|
2517
2651
|
*/
|
|
2518
2652
|
var httpFor = function (testCase) {
|
|
2519
|
-
var
|
|
2653
|
+
var e_7, _a;
|
|
2520
2654
|
var _b;
|
|
2521
2655
|
var _c = __read(testCase.route.split(' '), 2), method = _c[0], routePath = _c[1];
|
|
2522
2656
|
var query = new URLSearchParams();
|
|
@@ -2527,12 +2661,12 @@ describe('service worker host', function () {
|
|
|
2527
2661
|
query.set(key, String(value));
|
|
2528
2662
|
}
|
|
2529
2663
|
}
|
|
2530
|
-
catch (
|
|
2664
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
2531
2665
|
finally {
|
|
2532
2666
|
try {
|
|
2533
2667
|
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
|
|
2534
2668
|
}
|
|
2535
|
-
finally { if (
|
|
2669
|
+
finally { if (e_7) throw e_7.error; }
|
|
2536
2670
|
}
|
|
2537
2671
|
var suffix = query.toString() ? "?".concat(query) : '';
|
|
2538
2672
|
var init = { method: method };
|
|
@@ -2563,8 +2697,8 @@ describe('service worker host', function () {
|
|
|
2563
2697
|
* does.
|
|
2564
2698
|
*/
|
|
2565
2699
|
var seedDrive = function (runtime, testCase) { return __awaiter(void 0, void 0, void 0, function () {
|
|
2566
|
-
var text, _a, _b, _c, file, contents, _d, _e, _f, file, contents,
|
|
2567
|
-
var
|
|
2700
|
+
var text, _a, _b, _c, file, contents, _d, _e, _f, file, contents, e_8_1;
|
|
2701
|
+
var e_9, _g, e_8, _h;
|
|
2568
2702
|
return __generator(this, function (_j) {
|
|
2569
2703
|
switch (_j.label) {
|
|
2570
2704
|
case 0:
|
|
@@ -2576,12 +2710,12 @@ describe('service worker host', function () {
|
|
|
2576
2710
|
text["/sasjs/drive/files".concat(file)] = contents;
|
|
2577
2711
|
}
|
|
2578
2712
|
}
|
|
2579
|
-
catch (
|
|
2713
|
+
catch (e_9_1) { e_9 = { error: e_9_1 }; }
|
|
2580
2714
|
finally {
|
|
2581
2715
|
try {
|
|
2582
2716
|
if (_b && !_b.done && (_g = _a.return)) _g.call(_a);
|
|
2583
2717
|
}
|
|
2584
|
-
finally { if (
|
|
2718
|
+
finally { if (e_9) throw e_9.error; }
|
|
2585
2719
|
}
|
|
2586
2720
|
return [4 /*yield*/, runtime.load(text)];
|
|
2587
2721
|
case 1:
|
|
@@ -2604,87 +2738,308 @@ describe('service worker host', function () {
|
|
|
2604
2738
|
return [3 /*break*/, 3];
|
|
2605
2739
|
case 6: return [3 /*break*/, 9];
|
|
2606
2740
|
case 7:
|
|
2607
|
-
|
|
2608
|
-
|
|
2741
|
+
e_8_1 = _j.sent();
|
|
2742
|
+
e_8 = { error: e_8_1 };
|
|
2609
2743
|
return [3 /*break*/, 9];
|
|
2610
2744
|
case 8:
|
|
2611
2745
|
try {
|
|
2612
2746
|
if (_e && !_e.done && (_h = _d.return)) _h.call(_d);
|
|
2613
2747
|
}
|
|
2614
|
-
finally { if (
|
|
2748
|
+
finally { if (e_8) throw e_8.error; }
|
|
2615
2749
|
return [7 /*endfinally*/];
|
|
2616
2750
|
case 9: return [2 /*return*/];
|
|
2617
2751
|
}
|
|
2618
2752
|
});
|
|
2619
2753
|
}); };
|
|
2754
|
+
/**
|
|
2755
|
+
* The expectation kinds this harness asserts.
|
|
2756
|
+
*
|
|
2757
|
+
* A case that states one it does not implement fails rather than passing
|
|
2758
|
+
* unproven - `keys`, `shape` and `encoding` are asserted by the package's own
|
|
2759
|
+
* corpus run and not here, and a case that starts using them has to be told
|
|
2760
|
+
* so by this list rather than by silence.
|
|
2761
|
+
*/
|
|
2762
|
+
var ASSERTED = ['status', 'error', 'partial', 'body', 'contents'];
|
|
2763
|
+
var assertExpectations = function (response, testCase) { return __awaiter(void 0, void 0, void 0, function () {
|
|
2764
|
+
var unasserted, bytes, _a, _b, text, _c, isJson, body;
|
|
2765
|
+
return __generator(this, function (_d) {
|
|
2766
|
+
switch (_d.label) {
|
|
2767
|
+
case 0:
|
|
2768
|
+
unasserted = Object.keys(testCase.expect).filter(function (kind) { return !ASSERTED.includes(kind); });
|
|
2769
|
+
if (unasserted.length)
|
|
2770
|
+
throw new Error("".concat(testCase.id, ": this harness does not assert ").concat(unasserted.join(', ')));
|
|
2771
|
+
expect(response.status).toBe(testCase.expect.status);
|
|
2772
|
+
if (!(testCase.expect.contents instanceof Uint8Array)) return [3 /*break*/, 2];
|
|
2773
|
+
_b = Uint8Array.bind;
|
|
2774
|
+
return [4 /*yield*/, response.arrayBuffer()];
|
|
2775
|
+
case 1:
|
|
2776
|
+
_a = new (_b.apply(Uint8Array, [void 0, _d.sent()]))();
|
|
2777
|
+
return [3 /*break*/, 3];
|
|
2778
|
+
case 2:
|
|
2779
|
+
_a = undefined;
|
|
2780
|
+
_d.label = 3;
|
|
2781
|
+
case 3:
|
|
2782
|
+
bytes = _a;
|
|
2783
|
+
if (!(bytes === undefined)) return [3 /*break*/, 5];
|
|
2784
|
+
return [4 /*yield*/, response.text()];
|
|
2785
|
+
case 4:
|
|
2786
|
+
_c = _d.sent();
|
|
2787
|
+
return [3 /*break*/, 6];
|
|
2788
|
+
case 5:
|
|
2789
|
+
_c = undefined;
|
|
2790
|
+
_d.label = 6;
|
|
2791
|
+
case 6:
|
|
2792
|
+
text = _c;
|
|
2793
|
+
isJson = (response.headers.get('content-type') || '').includes('application/json');
|
|
2794
|
+
if (testCase.expect.error !== undefined) {
|
|
2795
|
+
// the routes that answer an envelope - the two deploy routes - send
|
|
2796
|
+
// `{status, message}`; the rest send the message itself as text, which is
|
|
2797
|
+
// what the server's `res.status(code).send(err.message)` does
|
|
2798
|
+
if (isJson) {
|
|
2799
|
+
body = JSON.parse(text);
|
|
2800
|
+
expect(body.message).toBe(testCase.expect.error);
|
|
2801
|
+
// a refusal that states the envelope it expects is compared on the whole
|
|
2802
|
+
// of it, not only on the message
|
|
2803
|
+
if (testCase.expect.body !== undefined)
|
|
2804
|
+
expect(body).toEqual(testCase.expect.body);
|
|
2805
|
+
}
|
|
2806
|
+
else {
|
|
2807
|
+
// a case cannot state an envelope and be answered with text: one of the
|
|
2808
|
+
// two is wrong, and silence would hide which
|
|
2809
|
+
if (testCase.expect.body !== undefined)
|
|
2810
|
+
throw new Error("".concat(testCase.id, ": the answer is text, so its body cannot be asserted"));
|
|
2811
|
+
expect(text).toBe(testCase.expect.error);
|
|
2812
|
+
}
|
|
2813
|
+
return [2 /*return*/];
|
|
2814
|
+
}
|
|
2815
|
+
if (testCase.expect.contents !== undefined) {
|
|
2816
|
+
if (bytes !== undefined)
|
|
2817
|
+
expect(bytes).toEqual(testCase.expect.contents);
|
|
2818
|
+
else
|
|
2819
|
+
expect(text).toEqual(testCase.expect.contents);
|
|
2820
|
+
return [2 /*return*/];
|
|
2821
|
+
}
|
|
2822
|
+
if (testCase.expect.body !== undefined)
|
|
2823
|
+
expect(JSON.parse(text)).toEqual(testCase.expect.body);
|
|
2824
|
+
if (testCase.expect.partial !== undefined)
|
|
2825
|
+
expect(JSON.parse(text)).toMatchObject(testCase.expect.partial);
|
|
2826
|
+
return [2 /*return*/];
|
|
2827
|
+
}
|
|
2828
|
+
});
|
|
2829
|
+
}); };
|
|
2620
2830
|
var _loop_1 = function (testCase) {
|
|
2621
2831
|
it(testCase.id, function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2622
|
-
var worker, _a, requestPath, init, response
|
|
2623
|
-
return __generator(this, function (
|
|
2624
|
-
switch (
|
|
2832
|
+
var worker, _a, requestPath, init, response;
|
|
2833
|
+
return __generator(this, function (_b) {
|
|
2834
|
+
switch (_b.label) {
|
|
2625
2835
|
case 0:
|
|
2626
2836
|
worker = loadWorker();
|
|
2627
2837
|
return [4 /*yield*/, seedDrive(worker.runtime, testCase)];
|
|
2628
2838
|
case 1:
|
|
2629
|
-
|
|
2839
|
+
_b.sent();
|
|
2630
2840
|
worker.refuseWrites(Boolean(testCase.unwritable));
|
|
2631
2841
|
_a = httpFor(testCase), requestPath = _a.path, init = _a.init;
|
|
2632
2842
|
return [4 /*yield*/, worker.dispatch(requestPath, init)];
|
|
2633
2843
|
case 2:
|
|
2634
|
-
response =
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2844
|
+
response = _b.sent();
|
|
2845
|
+
return [4 /*yield*/, assertExpectations(response, testCase)];
|
|
2846
|
+
case 3:
|
|
2847
|
+
_b.sent();
|
|
2848
|
+
return [2 /*return*/];
|
|
2849
|
+
}
|
|
2850
|
+
});
|
|
2851
|
+
}); });
|
|
2852
|
+
};
|
|
2853
|
+
try {
|
|
2854
|
+
for (var DRIVE_CORPUS_1 = __values(api_1.DRIVE_CORPUS), DRIVE_CORPUS_1_1 = DRIVE_CORPUS_1.next(); !DRIVE_CORPUS_1_1.done; DRIVE_CORPUS_1_1 = DRIVE_CORPUS_1.next()) {
|
|
2855
|
+
var testCase = DRIVE_CORPUS_1_1.value;
|
|
2856
|
+
_loop_1(testCase);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
2860
|
+
finally {
|
|
2861
|
+
try {
|
|
2862
|
+
if (DRIVE_CORPUS_1_1 && !DRIVE_CORPUS_1_1.done && (_a = DRIVE_CORPUS_1.return)) _a.call(DRIVE_CORPUS_1);
|
|
2863
|
+
}
|
|
2864
|
+
finally { if (e_5) throw e_5.error; }
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* The deploy route's JSON form and its removal, which is what `sasjs deploy`
|
|
2868
|
+
* sends and what this host's transport carries unchanged.
|
|
2869
|
+
*
|
|
2870
|
+
* The upload route's cases hand over the expanded tree in place of the
|
|
2871
|
+
* archive, which is a host's transport detail rather than an answer: this
|
|
2872
|
+
* worker takes the archive and expands it, so those cases would pin the
|
|
2873
|
+
* corpus's assumption instead of this host's behaviour. The zip cases above
|
|
2874
|
+
* pin that route. The removal cases need an app to remove, so the index is
|
|
2875
|
+
* seeded with the corpus's own - the worker starts with nothing published,
|
|
2876
|
+
* which is the server's state too.
|
|
2877
|
+
*
|
|
2878
|
+
* The state a case leaves is checked too - `expectDrive` - because both deploy
|
|
2879
|
+
* answers are `200` and the difference between replacing a folder and keeping
|
|
2880
|
+
* what is there is only visible in the drive afterwards.
|
|
2881
|
+
*/
|
|
2882
|
+
/** The app a removal case names, as the corpus states it. */
|
|
2883
|
+
var CORPUS_APP = {
|
|
2884
|
+
appLoc: '/Public/myApp',
|
|
2885
|
+
streamWebFolder: 'web',
|
|
2886
|
+
publishedAt: '2026-01-01T00:00:00.000Z'
|
|
2887
|
+
};
|
|
2888
|
+
/**
|
|
2889
|
+
* A second app that no removal case names.
|
|
2890
|
+
*
|
|
2891
|
+
* Without it, a removal that ignored the id and deleted whatever was
|
|
2892
|
+
* published would pass both removal cases - the unknown-id one sees an empty
|
|
2893
|
+
* index either way. With it, the assertion after the answer is that the app
|
|
2894
|
+
* the id did *not* name is still listed.
|
|
2895
|
+
*/
|
|
2896
|
+
var BYSTANDER = {
|
|
2897
|
+
id: '0b9a1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d',
|
|
2898
|
+
name: 'bystander',
|
|
2899
|
+
appLoc: '/Public/bystander',
|
|
2900
|
+
streamWebFolder: 'web',
|
|
2901
|
+
publishedAt: '2026-01-01T00:00:00.000Z'
|
|
2902
|
+
};
|
|
2903
|
+
/**
|
|
2904
|
+
* The index a removal case starts from - built from the id the case asks to
|
|
2905
|
+
* remove, not from the answer it expects, so a host that removed some other
|
|
2906
|
+
* app would answer a different id and fail.
|
|
2907
|
+
*/
|
|
2908
|
+
var seedIndex = function (runtime, testCase) { return __awaiter(void 0, void 0, void 0, function () {
|
|
2909
|
+
var id, named, entries;
|
|
2910
|
+
var _a, _b, _c;
|
|
2911
|
+
return __generator(this, function (_d) {
|
|
2912
|
+
switch (_d.label) {
|
|
2913
|
+
case 0:
|
|
2914
|
+
if (testCase.route !== 'DELETE /SASjsApi/drive/deploy')
|
|
2915
|
+
return [2 /*return*/];
|
|
2916
|
+
id = (_a = testCase.request.body) === null || _a === void 0 ? void 0 : _a.id;
|
|
2917
|
+
named = (_b = testCase.expect.partial) === null || _b === void 0 ? void 0 : _b.app;
|
|
2918
|
+
entries = [BYSTANDER];
|
|
2919
|
+
// the app the case expects to remove is published only when the case names
|
|
2920
|
+
// the id it removes: the unknown-id case asks for one that is not there, and
|
|
2921
|
+
// publishing it would make that case pass by accident. It goes in *after* the
|
|
2922
|
+
// bystander, so a removal that takes the first entry rather than the named
|
|
2923
|
+
// one removes the bystander and both assertions below fail.
|
|
2924
|
+
if (id !== undefined && (named === null || named === void 0 ? void 0 : named.id) === id)
|
|
2925
|
+
entries.push(__assign({ id: id, name: (_c = named.name) !== null && _c !== void 0 ? _c : 'myApp' }, CORPUS_APP));
|
|
2926
|
+
return [4 /*yield*/, runtime.load({
|
|
2927
|
+
'/sasjs/drive/files/.sasjs/apps.json': JSON.stringify(entries)
|
|
2928
|
+
})];
|
|
2929
|
+
case 1:
|
|
2930
|
+
_d.sent();
|
|
2931
|
+
return [2 /*return*/];
|
|
2932
|
+
}
|
|
2933
|
+
});
|
|
2934
|
+
}); };
|
|
2935
|
+
var _loop_2 = function (testCase) {
|
|
2936
|
+
it(testCase.id, function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2937
|
+
var worker, _a, requestPath, init, response, listing, ids, _b, _c, path_6, file, e_10_1, _d, _e, path_7, file, e_11_1;
|
|
2938
|
+
var e_10, _f, e_11, _g;
|
|
2939
|
+
var _h, _j, _k;
|
|
2940
|
+
return __generator(this, function (_l) {
|
|
2941
|
+
switch (_l.label) {
|
|
2942
|
+
case 0:
|
|
2943
|
+
worker = loadWorker();
|
|
2944
|
+
return [4 /*yield*/, seedDrive(worker.runtime, testCase)];
|
|
2945
|
+
case 1:
|
|
2946
|
+
_l.sent();
|
|
2947
|
+
return [4 /*yield*/, seedIndex(worker.runtime, testCase)
|
|
2948
|
+
// `unwritable` is not passed on here: the case that states it is the
|
|
2949
|
+
// upload route's, which the filter above keeps out, and the deploy route's
|
|
2950
|
+
// own `500` is covered by `deploy-refused-by-store` in the drive loop.
|
|
2641
2951
|
];
|
|
2952
|
+
case 2:
|
|
2953
|
+
_l.sent();
|
|
2954
|
+
_a = httpFor(testCase), requestPath = _a.path, init = _a.init;
|
|
2955
|
+
return [4 /*yield*/, worker.dispatch(requestPath, init)];
|
|
2642
2956
|
case 3:
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
if ((response.headers.get('content-type') || '').includes('application/json'))
|
|
2648
|
-
expect(JSON.parse(body)).toEqual({
|
|
2649
|
-
status: 'failure',
|
|
2650
|
-
message: testCase.expect.error
|
|
2651
|
-
});
|
|
2652
|
-
else
|
|
2653
|
-
expect(body).toBe(testCase.expect.error);
|
|
2654
|
-
return [3 /*break*/, 8];
|
|
2957
|
+
response = _l.sent();
|
|
2958
|
+
return [4 /*yield*/, assertExpectations(response, testCase)
|
|
2959
|
+
// a removal takes the app the id names, and leaves the rest published
|
|
2960
|
+
];
|
|
2655
2961
|
case 4:
|
|
2656
|
-
|
|
2657
|
-
if (!(
|
|
2658
|
-
|
|
2659
|
-
return [4 /*yield*/, response.text()];
|
|
2962
|
+
_l.sent();
|
|
2963
|
+
if (!(testCase.route === 'DELETE /SASjsApi/drive/deploy')) return [3 /*break*/, 7];
|
|
2964
|
+
return [4 /*yield*/, worker.dispatch('/app/apps.json')];
|
|
2660
2965
|
case 5:
|
|
2661
|
-
|
|
2662
|
-
return [
|
|
2966
|
+
listing = _l.sent();
|
|
2967
|
+
return [4 /*yield*/, listing.json()];
|
|
2663
2968
|
case 6:
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2969
|
+
ids = (_l.sent()).apps.map(function (app) { return app.id; });
|
|
2970
|
+
expect(ids).toContain(BYSTANDER.id);
|
|
2971
|
+
if (response.status === 200)
|
|
2972
|
+
expect(ids).not.toContain((_h = testCase.request.body) === null || _h === void 0 ? void 0 : _h.id);
|
|
2973
|
+
_l.label = 7;
|
|
2668
2974
|
case 7:
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2975
|
+
_l.trys.push([7, 12, 13, 14]);
|
|
2976
|
+
_b = __values(((_j = testCase.expectDrive) === null || _j === void 0 ? void 0 : _j.present) || []), _c = _b.next();
|
|
2977
|
+
_l.label = 8;
|
|
2978
|
+
case 8:
|
|
2979
|
+
if (!!_c.done) return [3 /*break*/, 11];
|
|
2980
|
+
path_6 = _c.value;
|
|
2981
|
+
return [4 /*yield*/, worker.dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent(path_6))];
|
|
2982
|
+
case 9:
|
|
2983
|
+
file = _l.sent();
|
|
2984
|
+
expect({ path: path_6, status: file.status }).toEqual({ path: path_6, status: 200 });
|
|
2985
|
+
_l.label = 10;
|
|
2986
|
+
case 10:
|
|
2987
|
+
_c = _b.next();
|
|
2988
|
+
return [3 /*break*/, 8];
|
|
2989
|
+
case 11: return [3 /*break*/, 14];
|
|
2990
|
+
case 12:
|
|
2991
|
+
e_10_1 = _l.sent();
|
|
2992
|
+
e_10 = { error: e_10_1 };
|
|
2993
|
+
return [3 /*break*/, 14];
|
|
2994
|
+
case 13:
|
|
2995
|
+
try {
|
|
2996
|
+
if (_c && !_c.done && (_f = _b.return)) _f.call(_b);
|
|
2997
|
+
}
|
|
2998
|
+
finally { if (e_10) throw e_10.error; }
|
|
2999
|
+
return [7 /*endfinally*/];
|
|
3000
|
+
case 14:
|
|
3001
|
+
_l.trys.push([14, 19, 20, 21]);
|
|
3002
|
+
_d = __values(((_k = testCase.expectDrive) === null || _k === void 0 ? void 0 : _k.missing) || []), _e = _d.next();
|
|
3003
|
+
_l.label = 15;
|
|
3004
|
+
case 15:
|
|
3005
|
+
if (!!_e.done) return [3 /*break*/, 18];
|
|
3006
|
+
path_7 = _e.value;
|
|
3007
|
+
return [4 /*yield*/, worker.dispatch('/SASjsApi/drive/file?_filePath=' + encodeURIComponent(path_7))];
|
|
3008
|
+
case 16:
|
|
3009
|
+
file = _l.sent();
|
|
3010
|
+
expect({ path: path_7, status: file.status }).toEqual({ path: path_7, status: 404 });
|
|
3011
|
+
_l.label = 17;
|
|
3012
|
+
case 17:
|
|
3013
|
+
_e = _d.next();
|
|
3014
|
+
return [3 /*break*/, 15];
|
|
3015
|
+
case 18: return [3 /*break*/, 21];
|
|
3016
|
+
case 19:
|
|
3017
|
+
e_11_1 = _l.sent();
|
|
3018
|
+
e_11 = { error: e_11_1 };
|
|
3019
|
+
return [3 /*break*/, 21];
|
|
3020
|
+
case 20:
|
|
3021
|
+
try {
|
|
3022
|
+
if (_e && !_e.done && (_g = _d.return)) _g.call(_d);
|
|
3023
|
+
}
|
|
3024
|
+
finally { if (e_11) throw e_11.error; }
|
|
3025
|
+
return [7 /*endfinally*/];
|
|
3026
|
+
case 21: return [2 /*return*/];
|
|
2672
3027
|
}
|
|
2673
3028
|
});
|
|
2674
3029
|
}); });
|
|
2675
3030
|
};
|
|
2676
3031
|
try {
|
|
2677
|
-
for (var
|
|
2678
|
-
var testCase =
|
|
2679
|
-
|
|
3032
|
+
for (var _c = __values(api_1.DEPLOY_CORPUS.filter(function (entry) { return entry.route !== 'POST /SASjsApi/drive/deploy/upload'; })), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
3033
|
+
var testCase = _d.value;
|
|
3034
|
+
_loop_2(testCase);
|
|
2680
3035
|
}
|
|
2681
3036
|
}
|
|
2682
|
-
catch (
|
|
3037
|
+
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
2683
3038
|
finally {
|
|
2684
3039
|
try {
|
|
2685
|
-
if (
|
|
3040
|
+
if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
|
|
2686
3041
|
}
|
|
2687
|
-
finally { if (
|
|
3042
|
+
finally { if (e_6) throw e_6.error; }
|
|
2688
3043
|
}
|
|
2689
3044
|
});
|
|
2690
3045
|
/**
|
|
@@ -2696,7 +3051,7 @@ describe('service worker host', function () {
|
|
|
2696
3051
|
* are the package's.
|
|
2697
3052
|
*/
|
|
2698
3053
|
describe('identity and record conformance', function () {
|
|
2699
|
-
var
|
|
3054
|
+
var e_12, _a;
|
|
2700
3055
|
/**
|
|
2701
3056
|
* Boots the worker from a fixture, the way the page does: the case's own
|
|
2702
3057
|
* identity, and `subject: null` for no implicit caller, so a case that
|
|
@@ -2729,7 +3084,7 @@ describe('service worker host', function () {
|
|
|
2729
3084
|
};
|
|
2730
3085
|
/** The request a case describes, as HTTP: path parameters filled, token sent. */
|
|
2731
3086
|
var httpForCase = function (testCase) {
|
|
2732
|
-
var
|
|
3087
|
+
var e_13, _a;
|
|
2733
3088
|
var _b = __read(testCase.route.split(' '), 2), method = _b[0], routePath = _b[1];
|
|
2734
3089
|
var filled = routePath.replace(/\{(\w+)\}/g, function (_match, name) {
|
|
2735
3090
|
return encodeURIComponent(String((testCase.request.params || {})[name]));
|
|
@@ -2742,12 +3097,12 @@ describe('service worker host', function () {
|
|
|
2742
3097
|
query.set(key, String(value));
|
|
2743
3098
|
}
|
|
2744
3099
|
}
|
|
2745
|
-
catch (
|
|
3100
|
+
catch (e_13_1) { e_13 = { error: e_13_1 }; }
|
|
2746
3101
|
finally {
|
|
2747
3102
|
try {
|
|
2748
3103
|
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
2749
3104
|
}
|
|
2750
|
-
finally { if (
|
|
3105
|
+
finally { if (e_13) throw e_13.error; }
|
|
2751
3106
|
}
|
|
2752
3107
|
var suffix = query.toString() ? "?".concat(query) : '';
|
|
2753
3108
|
var headers = {};
|
|
@@ -2841,7 +3196,7 @@ describe('service worker host', function () {
|
|
|
2841
3196
|
}
|
|
2842
3197
|
});
|
|
2843
3198
|
}); };
|
|
2844
|
-
var
|
|
3199
|
+
var _loop_3 = function (testCase) {
|
|
2845
3200
|
it("".concat(testCase.id, ": ").concat(testCase.description), function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2846
3201
|
var worker, _a, requestPath, init, _b, _c;
|
|
2847
3202
|
return __generator(this, function (_d) {
|
|
@@ -2866,15 +3221,15 @@ describe('service worker host', function () {
|
|
|
2866
3221
|
try {
|
|
2867
3222
|
for (var _b = __values(__spreadArray(__spreadArray([], __read(api_1.IDENTITY_CORPUS), false), __read(api_1.RECORD_CORPUS), false)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
2868
3223
|
var testCase = _c.value;
|
|
2869
|
-
|
|
3224
|
+
_loop_3(testCase);
|
|
2870
3225
|
}
|
|
2871
3226
|
}
|
|
2872
|
-
catch (
|
|
3227
|
+
catch (e_12_1) { e_12 = { error: e_12_1 }; }
|
|
2873
3228
|
finally {
|
|
2874
3229
|
try {
|
|
2875
3230
|
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
2876
3231
|
}
|
|
2877
|
-
finally { if (
|
|
3232
|
+
finally { if (e_12) throw e_12.error; }
|
|
2878
3233
|
}
|
|
2879
3234
|
it('answers as the fixture administrator when no subject was chosen', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
2880
3235
|
var worker, response, rules;
|
package/package.json
CHANGED
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@sasjs/adapter": "6.0.2",
|
|
74
|
-
"@sasjs/api": "0.27.
|
|
74
|
+
"@sasjs/api": "0.27.1",
|
|
75
75
|
"@sasjs/core": "7.1.12",
|
|
76
76
|
"@sasjs/lint": "5.1.1",
|
|
77
77
|
"@sasjs/utils": "4.11.0",
|
|
@@ -125,5 +125,5 @@
|
|
|
125
125
|
"ws": "8.21.0",
|
|
126
126
|
"nanoid": "3.3.18"
|
|
127
127
|
},
|
|
128
|
-
"version": "6.
|
|
128
|
+
"version": "6.27.1"
|
|
129
129
|
}
|