@sasjs/cli 6.25.0 → 6.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -86,6 +86,7 @@ var fs_1 = __importDefault(require("fs"));
86
86
  var os_1 = __importDefault(require("os"));
87
87
  var path_1 = __importDefault(require("path"));
88
88
  var esbuild_1 = require("esbuild");
89
+ var api_1 = require("@sasjs/api");
89
90
  var utils_1 = require("@sasjs/utils");
90
91
  /**
91
92
  * The webout() block is the one @sasjs/utils generates for the server, emitted
@@ -346,6 +347,36 @@ exports.DRIVE_FORMAT = 1;
346
347
  var megabytes = function (bytes) {
347
348
  return "".concat(Math.round((bytes / 1024 / 1024) * 10) / 10, " MB");
348
349
  };
350
+ /**
351
+ * The identity the site ships, and which file it came from.
352
+ *
353
+ * `mock-identity.json` beside the build wins, exactly as `mock-snapshot.json`
354
+ * replaces the drive seed: one file, one load path, and which one wins is decided
355
+ * here rather than at run time. Without it the package's demo fixture ships, so a
356
+ * mock works with no configuration at all.
357
+ *
358
+ * The fixture is validated at build time - a malformed one stops the build while
359
+ * the person who wrote it is watching, instead of producing a site that boots to
360
+ * a refusal nobody can connect to a file.
361
+ */
362
+ var readIdentityFixture = function (buildFolder) {
363
+ var file = path_1.default.join(buildFolder, 'mock-identity.json');
364
+ if (!fs_1.default.existsSync(file))
365
+ return { fixture: api_1.DEFAULT_IDENTITY, source: 'default' };
366
+ var parsed;
367
+ try {
368
+ parsed = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
369
+ }
370
+ catch (e) {
371
+ throw new Error("".concat(file, " is not valid JSON: ").concat((e === null || e === void 0 ? void 0 : e.message) || e));
372
+ }
373
+ try {
374
+ return { fixture: (0, api_1.parseIdentity)(parsed), source: 'mock-identity.json' };
375
+ }
376
+ catch (e) {
377
+ throw new Error("".concat(file, " cannot be used: ").concat((e === null || e === void 0 ? void 0 : e.message) || e));
378
+ }
379
+ };
349
380
  var readDriveFile = function (absolute) {
350
381
  var buffer = fs_1.default.readFileSync(absolute);
351
382
  var text = buffer.toString('utf8');
@@ -728,7 +759,7 @@ var patchIndexHtml = function (html, version, basePath, outputFolder) {
728
759
  */
729
760
  function buildMockSite(target) {
730
761
  return __awaiter(this, void 0, void 0, function () {
731
- var projectDir, appLocRaw, appLoc, buildFolder, basePath, webSourceRaw, webFolder, outputFolder, streamConfig, excluded, _a, seed, skipped, jsPrograms, sasPrograms, driveEntries, webFileCount, hostFolder, _b, _c, asset, hostVersion, buildStamp, swPath, snapshotSource, hasSnapshot, transcriptSource, hasTranscript, indexPath, _d, patched, warnings;
762
+ var projectDir, appLocRaw, appLoc, buildFolder, basePath, webSourceRaw, webFolder, outputFolder, streamConfig, excluded, _a, seed, skipped, jsPrograms, sasPrograms, driveEntries, identity, webFileCount, hostFolder, _b, _c, asset, hostVersion, buildStamp, swPath, snapshotSource, hasSnapshot, transcriptSource, hasTranscript, indexPath, _d, patched, warnings;
732
763
  var e_8, _e;
733
764
  var _f, _g, _h, _j, _k, _l;
734
765
  return __generator(this, function (_m) {
@@ -812,6 +843,7 @@ function buildMockSite(target) {
812
843
  })
813
844
  .join(', ')).concat(skipped.length > 3 ? ', ...' : ''));
814
845
  }
846
+ identity = readIdentityFixture(buildFolder);
815
847
  // Only now is the previous site expendable.
816
848
  fs_1.default.rmSync(outputFolder, { recursive: true, force: true });
817
849
  fs_1.default.mkdirSync(outputFolder, { recursive: true });
@@ -868,6 +900,11 @@ function buildMockSite(target) {
868
900
  apiServerUrl: exports.WEBOUT_SENTINELS.apiServerUrl
869
901
  })
870
902
  }));
903
+ // 2a-ii. the identity the site signs in with: the fixture the build chose, in
904
+ // the one place the runtime reads it. `window.SASjsMock.as()` and a reset both
905
+ // come back to this file, and the page stores the store's snapshot beside the
906
+ // drive so an account created through the routes survives a reload.
907
+ fs_1.default.writeFileSync(path_1.default.join(hostFolder, 'identity.json'), JSON.stringify(identity.fixture, null, 2));
871
908
  hostVersion = (0, crypto_1.createHash)('md5')
872
909
  .update([
873
910
  'runtime-core.js',
@@ -876,16 +913,20 @@ function buildMockSite(target) {
876
913
  'boot.js',
877
914
  'api-browser.js',
878
915
  'lint-browser.js',
879
- 'webout-prelude.json'
916
+ 'webout-prelude.json',
917
+ 'identity.json'
880
918
  ]
881
919
  .map(function (asset) { return fs_1.default.readFileSync(path_1.default.join(hostFolder, asset)); })
882
920
  .reduce(function (all, buf) { return Buffer.concat([all, buf]); }, Buffer.alloc(0)))
883
921
  .digest('hex')
884
922
  .slice(0, 10);
885
923
  buildStamp = (0, crypto_1.createHash)('md5')
886
- .update(JSON.stringify(Object.keys(seed)
887
- .sort()
888
- .map(function (key) { return [key, seed[key]]; })))
924
+ .update(JSON.stringify({
925
+ files: Object.keys(seed)
926
+ .sort()
927
+ .map(function (key) { return [key, seed[key]]; }),
928
+ identity: identity.fixture
929
+ }))
889
930
  .digest('hex')
890
931
  .slice(0, 10);
891
932
  swPath = path_1.default.join(outputFolder, 'sw.js');
@@ -913,6 +954,7 @@ function buildMockSite(target) {
913
954
  programFolders: PROGRAM_FOLDERS,
914
955
  snapshot: hasSnapshot,
915
956
  sasTranscript: hasTranscript,
957
+ identity: identity.source,
916
958
  buildStamp: buildStamp,
917
959
  builtAt: new Date().toISOString()
918
960
  }, null, 2));
@@ -927,7 +969,8 @@ function buildMockSite(target) {
927
969
  jsPrograms: jsPrograms,
928
970
  webFileCount: webFileCount,
929
971
  snapshot: hasSnapshot,
930
- sasTranscript: hasTranscript
972
+ sasTranscript: hasTranscript,
973
+ identitySource: identity.source
931
974
  }];
932
975
  }
933
976
  });
@@ -1107,4 +1107,154 @@ describe('browser-hosted build', function () {
1107
1107
  }
1108
1108
  });
1109
1109
  }); });
1110
+ /**
1111
+ * The fixture the site signs in with: one file, and which fixture wins is
1112
+ * decided at build time. A project replaces it the same way it replaces the
1113
+ * drive seed, so a site ships the identity its author chose rather than one
1114
+ * the runtime happens to hold.
1115
+ */
1116
+ it('ships the packaged identity fixture when the project has none', function () { return __awaiter(void 0, void 0, void 0, function () {
1117
+ var result, shipped;
1118
+ return __generator(this, function (_a) {
1119
+ switch (_a.label) {
1120
+ case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
1121
+ case 1:
1122
+ result = _a.sent();
1123
+ expect(result.identitySource).toBe('default');
1124
+ expect(readConfig().identity).toBe('default');
1125
+ shipped = JSON.parse(fs_1.default.readFileSync(path_1.default.join(out(), 'host', 'identity.json'), 'utf8'));
1126
+ expect(shipped.format).toBe(1);
1127
+ expect(shipped.users.map(function (user) { return user.username; })).toEqual([
1128
+ 'sasjsadmin',
1129
+ 'sasjsuser'
1130
+ ]);
1131
+ // the same fixture the package ships, so a mock signs in with no config
1132
+ expect(shipped.clients.map(function (client) { return client.clientId; })).toEqual([
1133
+ 'web_app',
1134
+ 'sasjs'
1135
+ ]);
1136
+ return [2 /*return*/];
1137
+ }
1138
+ });
1139
+ }); });
1140
+ it("ships the project's mock-identity.json instead of the packaged fixture", function () { return __awaiter(void 0, void 0, void 0, function () {
1141
+ var fixture, result;
1142
+ return __generator(this, function (_a) {
1143
+ switch (_a.label) {
1144
+ case 0:
1145
+ fixture = {
1146
+ format: 1,
1147
+ users: [
1148
+ {
1149
+ uid: 'user-auditor',
1150
+ username: 'auditor',
1151
+ displayName: 'Auditor',
1152
+ password: 'Orion123',
1153
+ isAdmin: true,
1154
+ isActive: true,
1155
+ needsToUpdatePassword: false
1156
+ }
1157
+ ],
1158
+ groups: [],
1159
+ permissions: [],
1160
+ clients: [
1161
+ {
1162
+ clientId: 'audit_app',
1163
+ clientSecret: 'audit_app',
1164
+ accessTokenExpiration: 3600,
1165
+ refreshTokenExpiration: 86400
1166
+ }
1167
+ ]
1168
+ };
1169
+ write(path_1.default.join(root, 'sasjsbuild', 'mock-identity.json'), JSON.stringify(fixture));
1170
+ return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
1171
+ case 1:
1172
+ result = _a.sent();
1173
+ expect(result.identitySource).toBe('mock-identity.json');
1174
+ expect(readConfig().identity).toBe('mock-identity.json');
1175
+ expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(out(), 'host', 'identity.json'), 'utf8'))).toEqual(fixture);
1176
+ return [2 /*return*/];
1177
+ }
1178
+ });
1179
+ }); });
1180
+ it('stamps the build with the fixture, so an identity-only change re-seeds it', function () { return __awaiter(void 0, void 0, void 0, function () {
1181
+ var before, fixture;
1182
+ return __generator(this, function (_a) {
1183
+ switch (_a.label) {
1184
+ case 0:
1185
+ // the stamp decides whether a visitor keeps the drive and identity row they
1186
+ // already have; `host/identity.json` is only read when there is no row, so a
1187
+ // project that edits only mock-identity.json has to move it
1188
+ return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
1189
+ case 1:
1190
+ // the stamp decides whether a visitor keeps the drive and identity row they
1191
+ // already have; `host/identity.json` is only read when there is no row, so a
1192
+ // project that edits only mock-identity.json has to move it
1193
+ _a.sent();
1194
+ before = readConfig().buildStamp;
1195
+ return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
1196
+ case 2:
1197
+ _a.sent();
1198
+ expect(readConfig().buildStamp).toBe(before);
1199
+ fixture = {
1200
+ format: 1,
1201
+ users: [
1202
+ {
1203
+ uid: 'user-auditor',
1204
+ username: 'auditor',
1205
+ displayName: 'Auditor',
1206
+ password: 'Orion123',
1207
+ isAdmin: true,
1208
+ isActive: true,
1209
+ needsToUpdatePassword: false
1210
+ }
1211
+ ],
1212
+ groups: [],
1213
+ permissions: [],
1214
+ clients: [
1215
+ {
1216
+ clientId: 'audit_app',
1217
+ clientSecret: 'audit_app',
1218
+ accessTokenExpiration: 3600,
1219
+ refreshTokenExpiration: 86400
1220
+ }
1221
+ ]
1222
+ };
1223
+ write(path_1.default.join(root, 'sasjsbuild', 'mock-identity.json'), JSON.stringify(fixture));
1224
+ return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
1225
+ case 3:
1226
+ _a.sent();
1227
+ expect(readConfig().buildStamp).not.toBe(before);
1228
+ return [2 /*return*/];
1229
+ }
1230
+ });
1231
+ }); });
1232
+ it('refuses a mock-identity.json it cannot read, leaving the previous site in place', function () { return __awaiter(void 0, void 0, void 0, function () {
1233
+ var before, wrongFormat;
1234
+ return __generator(this, function (_a) {
1235
+ switch (_a.label) {
1236
+ case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
1237
+ case 1:
1238
+ _a.sent();
1239
+ before = fs_1.default.readFileSync(path_1.default.join(out(), 'host', 'identity.json'), 'utf8');
1240
+ write(path_1.default.join(root, 'sasjsbuild', 'mock-identity.json'), '{ not json');
1241
+ return [4 /*yield*/, expect((0, mockSite_1.buildMockSite)(target())).rejects.toThrow(/mock-identity\.json is not valid JSON/)];
1242
+ case 2:
1243
+ _a.sent();
1244
+ expect(fs_1.default.readFileSync(path_1.default.join(out(), 'host', 'identity.json'), 'utf8')).toBe(before);
1245
+ wrongFormat = {
1246
+ format: 0,
1247
+ users: [],
1248
+ groups: [],
1249
+ permissions: [],
1250
+ clients: []
1251
+ };
1252
+ write(path_1.default.join(root, 'sasjsbuild', 'mock-identity.json'), JSON.stringify(wrongFormat));
1253
+ return [4 /*yield*/, expect((0, mockSite_1.buildMockSite)(target())).rejects.toThrow(/mock-identity\.json cannot be used: The identity fixture states format 0/)];
1254
+ case 3:
1255
+ _a.sent();
1256
+ return [2 /*return*/];
1257
+ }
1258
+ });
1259
+ }); });
1110
1260
  });
@@ -1,4 +1,15 @@
1
1
  "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
2
13
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
14
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
15
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -94,6 +105,7 @@ var createIndexedDB = function () {
94
105
  data().delete(key);
95
106
  },
96
107
  clear: function () { return data().clear(); },
108
+ get: function (key) { return request(function () { return data().get(key); }); },
97
109
  getAllKeys: function () { return request(function () { return __spreadArray([], __read(data().keys()), false); }); },
98
110
  getAll: function () { return request(function () { return __spreadArray([], __read(data().values()), false); }); }
99
111
  };
@@ -192,16 +204,17 @@ var loadPersist = function (options) {
192
204
  })];
193
205
  });
194
206
  }); });
195
- var idb = options.seeded
196
- ? (function () {
197
- var fake = createIndexedDB();
198
- Object.entries(options.seeded).forEach(function (_a) {
199
- var _b = __read(_a, 2), key = _b[0], value = _b[1];
200
- return fake.rows.set(key, value);
201
- });
202
- return fake;
203
- })()
204
- : createIndexedDB();
207
+ var idb = (function () {
208
+ var fake = createIndexedDB();
209
+ Object.entries(options.seeded || {}).forEach(function (_a) {
210
+ var _b = __read(_a, 2), key = _b[0], value = _b[1];
211
+ return fake.rows.set(key, value);
212
+ });
213
+ // the identity row lives beside the drive, under its own key
214
+ if (options.seededIdentity)
215
+ fake.rows.set('current', options.seededIdentity);
216
+ return fake;
217
+ })();
205
218
  global.indexedDB = idb;
206
219
  require('../assets/persist.js');
207
220
  var reply = function (data) { return __spreadArray([], __read(listeners), false).forEach(function (fn) { return fn({ data: data }); }); };
@@ -512,7 +525,10 @@ describe('page-side store', function () {
512
525
  _b.sent();
513
526
  clearInterval(answering);
514
527
  expect(controller.postMessage).toHaveBeenCalledWith({
515
- type: 'sasjs-seed'
528
+ type: 'sasjs-seed',
529
+ files: {},
530
+ identity: undefined,
531
+ subject: undefined
516
532
  });
517
533
  return [2 /*return*/];
518
534
  }
@@ -542,6 +558,7 @@ describe('page-side store', function () {
542
558
  controller: controller,
543
559
  ready: Promise.resolve({}),
544
560
  seeded: { '/sasjs/drive/stored.txt': 'stored' },
561
+ seededIdentity: { fixture: FIXTURE, subject: 'sasjsuser' },
545
562
  buildStamp: 'build-b',
546
563
  storedStamp: 'build-a'
547
564
  }), window = _a.window, listeners = _a.listeners, stored = _a.stored, rows = _a.rows;
@@ -550,9 +567,14 @@ describe('page-side store', function () {
550
567
  case 1:
551
568
  _b.sent();
552
569
  stop();
553
- expect(controller.postMessage).toHaveBeenCalledWith({ type: 'sasjs-seed' });
570
+ expect(controller.postMessage).toHaveBeenCalledWith({
571
+ type: 'sasjs-seed',
572
+ files: {},
573
+ identity: undefined,
574
+ subject: undefined
575
+ });
554
576
  expect(stored.get('sasjs-mock-build')).toBe('build-b');
555
- // the old drive is gone rather than merged with the new seed
577
+ // the old drive and the old fixture are gone, not merged with the new build
556
578
  expect(rows.size).toBe(0);
557
579
  return [2 /*return*/];
558
580
  }
@@ -575,7 +597,12 @@ describe('page-side store', function () {
575
597
  case 1:
576
598
  _b.sent();
577
599
  stop();
578
- expect(controller.postMessage).toHaveBeenCalledWith({ type: 'sasjs-seed' });
600
+ expect(controller.postMessage).toHaveBeenCalledWith({
601
+ type: 'sasjs-seed',
602
+ files: {},
603
+ identity: undefined,
604
+ subject: undefined
605
+ });
579
606
  return [2 /*return*/];
580
607
  }
581
608
  });
@@ -671,7 +698,300 @@ describe('page-side store', function () {
671
698
  expect(String(url)).toBe('https://host.test/demo/host/mock-config.json');
672
699
  expect(init).toEqual({ cache: 'no-store' });
673
700
  // and the stamp it read is acted on
674
- expect(controller.postMessage).toHaveBeenCalledWith({ type: 'sasjs-seed' });
701
+ expect(controller.postMessage).toHaveBeenCalledWith({
702
+ type: 'sasjs-seed',
703
+ files: {},
704
+ identity: undefined,
705
+ subject: undefined
706
+ });
707
+ return [2 /*return*/];
708
+ }
709
+ });
710
+ }); });
711
+ /**
712
+ * The fixture a stored identity carries. The page never reads inside it - the
713
+ * worker parses it - so this only has to be the object that was stored.
714
+ */
715
+ /**
716
+ * Waits for an asynchronous write rather than guessing how long it takes: the
717
+ * page persists through IndexedDB, whose requests settle on their own
718
+ * schedule, and a fixed sleep that is comfortable on one runner is too short
719
+ * on another.
720
+ */
721
+ var until = function (ready_1) {
722
+ var args_1 = [];
723
+ for (var _i = 1; _i < arguments.length; _i++) {
724
+ args_1[_i - 1] = arguments[_i];
725
+ }
726
+ return __awaiter(void 0, __spreadArray([ready_1], __read(args_1), false), void 0, function (ready, timeout) {
727
+ var started;
728
+ if (timeout === void 0) { timeout = 2000; }
729
+ return __generator(this, function (_a) {
730
+ switch (_a.label) {
731
+ case 0:
732
+ started = Date.now();
733
+ _a.label = 1;
734
+ case 1:
735
+ if (!!ready()) return [3 /*break*/, 3];
736
+ if (Date.now() - started > timeout)
737
+ throw new Error('timed out waiting for the write');
738
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
739
+ case 2:
740
+ _a.sent();
741
+ return [3 /*break*/, 1];
742
+ case 3: return [2 /*return*/];
743
+ }
744
+ });
745
+ });
746
+ };
747
+ var FIXTURE = {
748
+ format: 1,
749
+ users: [{ username: 'sasjsadmin' }, { username: 'sasjsuser' }],
750
+ groups: [],
751
+ permissions: [],
752
+ clients: [{ clientId: 'web_app' }]
753
+ };
754
+ /**
755
+ * Answers every question the page has asked, once each.
756
+ *
757
+ * The page asks the worker more than one thing at boot (a seed or a load, and
758
+ * whatever a caller asked for next), and the order is not fixed: both go
759
+ * through `waitForController`. Answering "the last call" therefore answers the
760
+ * wrong one as often as the right one.
761
+ */
762
+ var answerAll = function (controller, listeners, payload) {
763
+ var answered = new Set();
764
+ var answering = setInterval(function () {
765
+ controller.postMessage.mock.calls.forEach(function (_a, index) {
766
+ var _b = __read(_a, 1), message = _b[0];
767
+ if (answered.has(index))
768
+ return;
769
+ answered.add(index);
770
+ __spreadArray([], __read(listeners), false).forEach(function (fn) {
771
+ return fn({
772
+ data: __assign({ type: message.type + '-ack' }, payload(index, message.type))
773
+ });
774
+ });
775
+ });
776
+ }, 5);
777
+ return function () { return clearInterval(answering); };
778
+ };
779
+ it('hands a stored fixture and subject to the worker at boot', function () { return __awaiter(void 0, void 0, void 0, function () {
780
+ var controller, _a, window, listeners, stop;
781
+ return __generator(this, function (_b) {
782
+ switch (_b.label) {
783
+ case 0:
784
+ controller = { postMessage: jest.fn() };
785
+ _a = loadPersist({
786
+ controller: controller,
787
+ ready: Promise.resolve({}),
788
+ seeded: { '/sasjs/drive/stored.txt': 'stored' },
789
+ seededIdentity: { fixture: FIXTURE, subject: 'sasjsuser' }
790
+ }), window = _a.window, listeners = _a.listeners;
791
+ stop = answerBoot(controller, listeners);
792
+ return [4 /*yield*/, window.__sasjsMockReady];
793
+ case 1:
794
+ _b.sent();
795
+ stop();
796
+ expect(controller.postMessage).toHaveBeenCalledWith(expect.objectContaining({
797
+ type: 'sasjs-load',
798
+ identity: FIXTURE,
799
+ subject: 'sasjsuser'
800
+ }));
801
+ return [2 /*return*/];
802
+ }
803
+ });
804
+ }); });
805
+ it('stores the fixture the worker reports when a route changed it', function () { return __awaiter(void 0, void 0, void 0, function () {
806
+ var controller, _a, reply, rows;
807
+ return __generator(this, function (_b) {
808
+ switch (_b.label) {
809
+ case 0:
810
+ controller = { postMessage: jest.fn() };
811
+ _a = loadPersist({ controller: controller }), reply = _a.reply, rows = _a.rows;
812
+ reply({ type: 'sasjs-persist', files: {}, identity: FIXTURE });
813
+ return [4 /*yield*/, until(function () { return rows.has('current'); })];
814
+ case 1:
815
+ _b.sent();
816
+ expect(rows.get('current')).toEqual({
817
+ fixture: FIXTURE,
818
+ subject: undefined
819
+ });
820
+ return [2 /*return*/];
821
+ }
822
+ });
823
+ }); });
824
+ it('drops a stored fixture this build cannot read, and boots from its own', function () { return __awaiter(void 0, void 0, void 0, function () {
825
+ var controller, _a, window, listeners, rows, stop, boot;
826
+ return __generator(this, function (_b) {
827
+ switch (_b.label) {
828
+ case 0:
829
+ controller = { postMessage: jest.fn() };
830
+ _a = loadPersist({
831
+ controller: controller,
832
+ ready: Promise.resolve({}),
833
+ seeded: { '/sasjs/drive/stored.txt': 'stored' },
834
+ seededIdentity: { fixture: __assign(__assign({}, FIXTURE), { format: 0 }), subject: null }
835
+ }), window = _a.window, listeners = _a.listeners, rows = _a.rows;
836
+ stop = answerAll(controller, listeners, function (index) {
837
+ return index === 0
838
+ ? {
839
+ error: 'The identity fixture states format 0.',
840
+ errorKind: 'identity'
841
+ }
842
+ : { booted: true };
843
+ });
844
+ return [4 /*yield*/, window.__sasjsMockReady];
845
+ case 1:
846
+ boot = _b.sent();
847
+ stop();
848
+ expect(boot).toEqual({ type: 'sasjs-load-ack', booted: true });
849
+ // the retry carries the drive, and none of the fixture it could not read
850
+ expect(controller.postMessage.mock.calls[1][0]).toEqual({
851
+ type: 'sasjs-load',
852
+ files: { '/sasjs/drive/stored.txt': 'stored' }
853
+ });
854
+ expect(rows.has('current')).toBe(false);
855
+ return [2 /*return*/];
856
+ }
857
+ });
858
+ }); });
859
+ it('keeps the stored identity when the boot failed for another reason', function () { return __awaiter(void 0, void 0, void 0, function () {
860
+ var controller, _a, window, listeners, rows, stop, boot;
861
+ return __generator(this, function (_b) {
862
+ switch (_b.label) {
863
+ case 0:
864
+ controller = { postMessage: jest.fn() };
865
+ _a = loadPersist({
866
+ controller: controller,
867
+ ready: Promise.resolve({}),
868
+ seeded: { '/sasjs/drive/stored.txt': 'stored' },
869
+ seededIdentity: { fixture: FIXTURE, subject: 'sasjsuser' }
870
+ }), window = _a.window, listeners = _a.listeners, rows = _a.rows;
871
+ stop = answerAll(controller, listeners, function () { return ({
872
+ error: 'the drive could not be read',
873
+ errorKind: 'host'
874
+ }); });
875
+ return [4 /*yield*/, window.__sasjsMockReady];
876
+ case 1:
877
+ boot = _b.sent();
878
+ stop();
879
+ expect(boot.error).toBe('the drive could not be read');
880
+ expect(rows.get('current')).toEqual({
881
+ fixture: FIXTURE,
882
+ subject: 'sasjsuser'
883
+ });
884
+ // and it did not re-boot without the fixture
885
+ expect(controller.postMessage).toHaveBeenCalledTimes(1);
886
+ return [2 /*return*/];
887
+ }
888
+ });
889
+ }); });
890
+ it('as() stores the subject, returns the pair and restarts the app', function () { return __awaiter(void 0, void 0, void 0, function () {
891
+ var controller, _a, window, listeners, rows, stop, pair;
892
+ return __generator(this, function (_b) {
893
+ switch (_b.label) {
894
+ case 0:
895
+ controller = { postMessage: jest.fn() };
896
+ _a = loadPersist({ controller: controller }), window = _a.window, listeners = _a.listeners, rows = _a.rows;
897
+ window.location = { reload: jest.fn() };
898
+ stop = answerAll(controller, listeners, function (_index, type) {
899
+ return type === 'sasjs-as'
900
+ ? {
901
+ username: 'sasjsuser',
902
+ accessToken: 'access-for-user2',
903
+ refreshToken: 'refresh-for-user2',
904
+ identity: FIXTURE
905
+ }
906
+ : {};
907
+ });
908
+ return [4 /*yield*/, window.SASjsMock.as('sasjsuser')];
909
+ case 1:
910
+ pair = _b.sent();
911
+ stop();
912
+ expect(controller.postMessage).toHaveBeenCalledWith({
913
+ type: 'sasjs-as',
914
+ username: 'sasjsuser'
915
+ });
916
+ expect(pair).toEqual({
917
+ username: 'sasjsuser',
918
+ accessToken: 'access-for-user2',
919
+ refreshToken: 'refresh-for-user2'
920
+ });
921
+ // the subject outlives the restart, beside the fixture it names
922
+ return [4 /*yield*/, until(function () { return window.location.reload.mock.calls.length > 0; })];
923
+ case 2:
924
+ // the subject outlives the restart, beside the fixture it names
925
+ _b.sent();
926
+ return [4 /*yield*/, until(function () { return rows.has('current'); })];
927
+ case 3:
928
+ _b.sent();
929
+ expect(rows.get('current')).toEqual({
930
+ fixture: FIXTURE,
931
+ subject: 'sasjsuser'
932
+ });
933
+ expect(window.location.reload).toHaveBeenCalled();
934
+ return [2 /*return*/];
935
+ }
936
+ });
937
+ }); });
938
+ it('surfaces a refusal from the worker rather than storing a subject', function () { return __awaiter(void 0, void 0, void 0, function () {
939
+ var controller, _a, window, listeners, rows, stop;
940
+ return __generator(this, function (_b) {
941
+ switch (_b.label) {
942
+ case 0:
943
+ controller = { postMessage: jest.fn() };
944
+ _a = loadPersist({ controller: controller }), window = _a.window, listeners = _a.listeners, rows = _a.rows;
945
+ stop = answerAll(controller, listeners, function (_index, type) {
946
+ return type === 'sasjs-as' ? { error: 'no account named "nobody"' } : {};
947
+ });
948
+ return [4 /*yield*/, expect(window.SASjsMock.as('nobody')).rejects.toThrow('no account named "nobody"')];
949
+ case 1:
950
+ _b.sent();
951
+ stop();
952
+ expect(rows.has('current')).toBe(false);
953
+ return [2 /*return*/];
954
+ }
955
+ });
956
+ }); });
957
+ it('reset reports a refusal from the worker rather than claiming success', function () { return __awaiter(void 0, void 0, void 0, function () {
958
+ var controller, _a, window, listeners, stop;
959
+ return __generator(this, function (_b) {
960
+ switch (_b.label) {
961
+ case 0:
962
+ controller = { postMessage: jest.fn() };
963
+ _a = loadPersist({ controller: controller }), window = _a.window, listeners = _a.listeners;
964
+ stop = answerAll(controller, listeners, function (_index, type) {
965
+ return type === 'sasjs-reset'
966
+ ? { error: 'host/identity.json cannot be used: it is not readable' }
967
+ : {};
968
+ });
969
+ return [4 /*yield*/, expect(window.SASjsMock.reset()).rejects.toThrow('host/identity.json cannot be used')];
970
+ case 1:
971
+ _b.sent();
972
+ stop();
973
+ return [2 /*return*/];
974
+ }
975
+ });
976
+ }); });
977
+ it('reset clears the identity beside the drive', function () { return __awaiter(void 0, void 0, void 0, function () {
978
+ var controller, _a, window, listeners, rows, stop;
979
+ return __generator(this, function (_b) {
980
+ switch (_b.label) {
981
+ case 0:
982
+ controller = { postMessage: jest.fn() };
983
+ _a = loadPersist({
984
+ controller: controller,
985
+ seeded: { '/sasjs/drive/stored.txt': 'stored' },
986
+ seededIdentity: { fixture: FIXTURE, subject: 'sasjsuser' }
987
+ }), window = _a.window, listeners = _a.listeners, rows = _a.rows;
988
+ stop = answerAll(controller, listeners, function () { return ({}); });
989
+ return [4 /*yield*/, window.SASjsMock.reset()];
990
+ case 1:
991
+ _b.sent();
992
+ stop();
993
+ expect(rows.has('current')).toBe(false);
994
+ expect(rows.has('/sasjs/drive/stored.txt')).toBe(false);
675
995
  return [2 /*return*/];
676
996
  }
677
997
  });