@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 streams this host published: the site itself.
701
+ * The app stream, over the drive this site carries.
702
702
  *
703
- * The index is the package's, so the contract is the package's - an app is its
704
- * id, one app per `appLoc`, and unpublish takes the id (sasjs/server#544). The id
705
- * is fixed here rather than minted: this host publishes exactly one app, the
706
- * folder the site was built for, and a stable id is a handle a test and a
707
- * reviewer can both name.
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
- * The folder is the identity, so unpublishing the app the site was built for
710
- * answers with that app; any other id is the documented 404. The static files
711
- * stay where they are - a worker cannot delete the site it is served from - so
712
- * this is a record of the publication, not a filesystem operation.
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 SITE_APP_ID = '6f1c1c1e-6a1e-4c4a-9d1a-1b2c3d4e5f60'
715
-
716
- let appStreamPromise
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
- return API.createAppStreamService(
729
- new API.MemoryAppStreamStore(
730
- appLoc
731
- ? [
732
- {
733
- id: SITE_APP_ID,
734
- name: API.defaultAppName(appLoc),
735
- appLoc,
736
- streamWebFolder: 'web',
737
- publishedAt: new Date(0).toISOString()
738
- }
739
- ]
740
- : []
741
- )
742
- )
743
- })()
744
- return appStreamPromise
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. The answers are the same either way, so
1251
- // `/AppStream` redirects to `/app` - exactly as the server redirects it -
1252
- // and the landing page, the listing and the missing-name refusal are decided
1253
- // on both.
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
- /^\/(app|AppStream)\/?$/i.test(stream) ||
1272
- /^\/(app|AppStream)\/index\.html$/i.test(stream)
1273
- )
1274
- return new Response(
1275
- [
1276
- '<!doctype html><html><head><meta charset="utf-8">',
1277
- '<title>App Stream</title></head><body>',
1278
- '<h1>SASjs App Stream</h1>',
1279
- '<p>This browser host serves no apps: nothing is published on a static site.</p>',
1280
- '</body></html>'
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
- if (/^\/(app|AppStream)\/apps\.json$/i.test(stream))
1286
- return json({ apps: [] })
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
- if (/^\/(app|AppStream)\//i.test(stream)) {
1289
- return json(
1290
- {
1291
- message:
1292
- 'This browser host serves no apps: nothing is published on a static site.'
1293
- },
1294
- 404
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 drive's routes send the message
1375
- // itself as plain text - the server answers them with `res.status(code)
1376
- // .send(err.message)` - so those stay text; everything else keeps the JSON
1377
- // envelope the other routes already answer with.
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, and unpublishes the site itself', function () { return __awaiter(void 0, void 0, void 0, function () {
959
- var dispatch, synced, _a, siteAppId, unpublished, removed, other;
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.text()];
1272
+ return [4 /*yield*/, empty.json()];
1238
1273
  case 2:
1239
- _a.apply(void 0, [_c.sent()]).toBe('"file" is not present.');
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.text()];
1290
+ return [4 /*yield*/, notZip.json()];
1251
1291
  case 4:
1252
- _b.apply(void 0, [_c.sent()]).toBe('The archive is not a zip file.');
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('answers the app stream on the short path, and the canonical one as an alias', function () { return __awaiter(void 0, void 0, void 0, function () {
1506
- var dispatch, _a, _b, path_2, listing, _c, e_1_1, _d, _e, path_3, landing, _f, e_2_1, root, canonicalLanding, _g, _h, _j, path_4, app, _k, e_3_1;
1507
- var e_1, _l, e_2, _m, e_3, _o;
1508
- return __generator(this, function (_p) {
1509
- switch (_p.label) {
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
- _p.label = 1;
1555
+ _u.label = 1;
1513
1556
  case 1:
1514
- _p.trys.push([1, 7, 8, 9]);
1557
+ _u.trys.push([1, 7, 8, 9]);
1515
1558
  _a = __values(['/app/apps.json', '/AppStream/apps.json']), _b = _a.next();
1516
- _p.label = 2;
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 = _p.sent();
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, [_p.sent()]).toEqual({ apps: [] });
1528
- _p.label = 5;
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 = _p.sent();
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 && (_l = _a.return)) _l.call(_a);
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
- _p.trys.push([9, 15, 16, 17]);
1587
+ _u.trys.push([9, 14, 15, 16]);
1545
1588
  _d = __values(['/app', '/app/', '/app/index.html']), _e = _d.next();
1546
- _p.label = 10;
1589
+ _u.label = 10;
1547
1590
  case 10:
1548
- if (!!_e.done) return [3 /*break*/, 14];
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 = _p.sent();
1595
+ landing = _u.sent();
1553
1596
  expect(landing.status).toBe(200);
1554
1597
  expect(landing.headers.get('content-type')).toContain('text/html');
1555
- _f = expect;
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 14: return [3 /*break*/, 17];
1564
- case 15:
1565
- e_2_1 = _p.sent();
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*/, 17];
1568
- case 16:
1606
+ return [3 /*break*/, 16];
1607
+ case 15:
1569
1608
  try {
1570
- if (_e && !_e.done && (_m = _d.return)) _m.call(_d);
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 17: return [4 /*yield*/, dispatch('/AppStream')];
1575
- case 18:
1576
- root = _p.sent();
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*/, dispatch('/AppStream/')];
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
- canonicalLanding = _p.sent();
1582
- expect(canonicalLanding.status).toBe(200);
1583
- _g = expect;
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
- _g.apply(void 0, [_p.sent()]).toContain('serves no apps');
1587
- _p.label = 21;
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
- _p.trys.push([21, 27, 28, 29]);
1590
- _h = __values(['/app/Demo/', '/AppStream/Demo/']), _j = _h.next();
1591
- _p.label = 22;
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 (!!_j.done) return [3 /*break*/, 26];
1594
- path_4 = _j.value;
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
- app = _p.sent();
1598
- expect(app.status).toBe(404);
1599
- _k = expect;
1600
- return [4 /*yield*/, app.json()];
1649
+ listing = _u.sent();
1650
+ _h = expect;
1651
+ return [4 /*yield*/, listing.json()];
1601
1652
  case 24:
1602
- _k.apply(void 0, [(_p.sent()).message]).toContain('serves no apps');
1603
- _p.label = 25;
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
- _j = _h.next();
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 = _p.sent();
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 (_j && !_j.done && (_o = _h.return)) _o.call(_h);
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: return [2 /*return*/];
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 e_4, _a;
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 e_5, _a;
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 (e_5_1) { e_5 = { error: e_5_1 }; }
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 (e_5) throw e_5.error; }
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, e_6_1;
2567
- var e_7, _g, e_6, _h;
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 (e_7_1) { e_7 = { error: e_7_1 }; }
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 (e_7) throw e_7.error; }
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
- e_6_1 = _j.sent();
2608
- e_6 = { error: e_6_1 };
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 (e_6) throw e_6.error; }
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, body, want, _b, _c, _d;
2623
- return __generator(this, function (_e) {
2624
- switch (_e.label) {
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
- _e.sent();
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 = _e.sent();
2635
- expect(response.status).toBe(testCase.expect.status);
2636
- if (!(testCase.expect.error !== undefined)) return [3 /*break*/, 4];
2637
- return [4 /*yield*/, response.text()
2638
- // the routes that answer an envelope - the two deploy routes - send
2639
- // `{status, message}`; the rest send the message itself as text, which
2640
- // is what the server's `res.status(code).send(err.message)` does
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
- body = _e.sent();
2644
- // the routes that answer an envelope - the two deploy routes - send
2645
- // `{status, message}`; the rest send the message itself as text, which
2646
- // is what the server's `res.status(code).send(err.message)` does
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
- want = testCase.expect.contents;
2657
- if (!(typeof want === 'string')) return [3 /*break*/, 6];
2658
- _b = expect;
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
- _b.apply(void 0, [_e.sent()]).toBe(want);
2662
- return [3 /*break*/, 8];
2966
+ listing = _l.sent();
2967
+ return [4 /*yield*/, listing.json()];
2663
2968
  case 6:
2664
- if (!(want !== undefined)) return [3 /*break*/, 8];
2665
- _c = expect;
2666
- _d = Uint8Array.bind;
2667
- return [4 /*yield*/, response.arrayBuffer()];
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
- _c.apply(void 0, [new (_d.apply(Uint8Array, [void 0, _e.sent()]))()]).toEqual(want);
2670
- _e.label = 8;
2671
- case 8: return [2 /*return*/];
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 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()) {
2678
- var testCase = DRIVE_CORPUS_1_1.value;
2679
- _loop_1(testCase);
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 (e_4_1) { e_4 = { error: e_4_1 }; }
3037
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
2683
3038
  finally {
2684
3039
  try {
2685
- if (DRIVE_CORPUS_1_1 && !DRIVE_CORPUS_1_1.done && (_a = DRIVE_CORPUS_1.return)) _a.call(DRIVE_CORPUS_1);
3040
+ if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
2686
3041
  }
2687
- finally { if (e_4) throw e_4.error; }
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 e_8, _a;
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 e_9, _a;
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 (e_9_1) { e_9 = { error: e_9_1 }; }
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 (e_9) throw e_9.error; }
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 _loop_2 = function (testCase) {
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
- _loop_2(testCase);
3224
+ _loop_3(testCase);
2870
3225
  }
2871
3226
  }
2872
- catch (e_8_1) { e_8 = { error: e_8_1 }; }
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 (e_8) throw e_8.error; }
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.0",
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.26.1"
128
+ "version": "6.27.1"
129
129
  }