@sasjs/cli 6.30.0 → 6.31.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.
Files changed (23) hide show
  1. package/build/commands/build/internal/mockSite/assets/api-console.css +365 -0
  2. package/build/commands/build/internal/mockSite/assets/api-console.js +762 -0
  3. package/build/commands/build/internal/mockSite/assets/api-index.html +83 -0
  4. package/build/commands/build/internal/mockSite/assets/boot.js +23 -1
  5. package/build/commands/build/internal/mockSite/assets/persist.js +76 -5
  6. package/build/commands/build/internal/mockSite/assets/runtime-core.js +413 -50
  7. package/build/commands/build/internal/mockSite/assets/sw.js +156 -15
  8. package/build/commands/build/internal/mockSite/mockSite.d.ts +25 -0
  9. package/build/commands/build/internal/mockSite/mockSite.js +311 -24
  10. package/build/commands/build/internal/mockSite/spec/apiConsole.spec.d.ts +1 -0
  11. package/build/commands/build/internal/mockSite/spec/apiConsole.spec.js +1371 -0
  12. package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.d.ts +1 -0
  13. package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.js +303 -0
  14. package/build/commands/build/internal/mockSite/spec/boot.spec.js +50 -3
  15. package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +217 -3
  16. package/build/commands/build/internal/mockSite/spec/persist.spec.js +179 -1
  17. package/build/commands/build/internal/mockSite/spec/runtime-core.spec.js +546 -0
  18. package/build/commands/build/internal/mockSite/spec/sw.spec.js +407 -53
  19. package/build/commands/version/spec/version.spec.js +11 -1
  20. package/build/commands/version/version.d.ts +5 -3
  21. package/build/commands/version/version.js +17 -7
  22. package/build/types/mockWebBasePath.d.ts +9 -0
  23. package/package.json +13 -32
@@ -35,6 +35,31 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
35
35
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
36
  }
37
37
  };
38
+ var __read = (this && this.__read) || function (o, n) {
39
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
40
+ if (!m) return o;
41
+ var i = m.call(o), r, ar = [], e;
42
+ try {
43
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
44
+ }
45
+ catch (error) { e = { error: error }; }
46
+ finally {
47
+ try {
48
+ if (r && !r.done && (m = i["return"])) m.call(i);
49
+ }
50
+ finally { if (e) throw e.error; }
51
+ }
52
+ return ar;
53
+ };
54
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
55
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
56
+ if (ar || !(i in from)) {
57
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
58
+ ar[i] = from[i];
59
+ }
60
+ }
61
+ return to.concat(ar || Array.prototype.slice.call(from));
62
+ };
38
63
  var __importDefault = (this && this.__importDefault) || function (mod) {
39
64
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
65
  };
@@ -118,6 +143,527 @@ describe('browser runtime', function () {
118
143
  delete global.self;
119
144
  delete global.fetch;
120
145
  });
146
+ /**
147
+ * A sharded drive is read without fetching what the request does not need.
148
+ *
149
+ * The build splits a captured drive so a first page does not download every
150
+ * app's bundle. What must hold here is that the split is invisible: a listing,
151
+ * an existence check and a stat answer for files whose bytes are still on the
152
+ * site, and only a read goes back for them.
153
+ */
154
+ it('answers for a sharded drive without fetching the pieces it does not need', function () { return __awaiter(void 0, void 0, void 0, function () {
155
+ var runtime, indexShard, bundleShard, bundle, fetched, listing, refused, read;
156
+ return __generator(this, function (_a) {
157
+ switch (_a.label) {
158
+ case 0:
159
+ runtime = loadRuntime();
160
+ indexShard = "".concat(drivePath('index.json'));
161
+ bundleShard = "".concat(drivePath('services/web/index.bundle.js'));
162
+ bundle = 'x'.repeat(400);
163
+ fetched = [];
164
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
165
+ var id, files;
166
+ var _a, _b, _c;
167
+ return __generator(this, function (_d) {
168
+ fetched.push(url);
169
+ if (url.includes('mock-config'))
170
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc })];
171
+ if (/vfs-snapshot-\d+\.json/.test(url)) {
172
+ id = Number(/vfs-snapshot-(\d+)\.json/.exec(url)[1]);
173
+ files = id === 0 ? (_a = {}, _a[indexShard] = '{"apps":[]}', _a) : (_b = {}, _b[bundleShard] = bundle, _b);
174
+ return [2 /*return*/, jsonResponse({ format: mockSite_1.DRIVE_FORMAT, files: files })];
175
+ }
176
+ if (url.includes('vfs-snapshot'))
177
+ return [2 /*return*/, jsonResponse({
178
+ format: mockSite_1.DRIVE_FORMAT,
179
+ sharded: true,
180
+ boot: 0,
181
+ entries: (_c = {},
182
+ _c[indexShard] = { shard: 0, bytes: 12 },
183
+ _c[bundleShard] = { shard: 1, bytes: bundle.length },
184
+ _c),
185
+ shards: [
186
+ { id: 0, file: 'host/vfs-snapshot-0.json' },
187
+ { id: 1, file: 'host/vfs-snapshot-1.json' }
188
+ ]
189
+ })];
190
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
191
+ return [2 /*return*/, ({})];
192
+ }); }); } }];
193
+ });
194
+ }); });
195
+ return [4 /*yield*/, runtime.seed()
196
+ // the boot shard came with the drive; the bundle did not
197
+ ];
198
+ case 1:
199
+ _a.sent();
200
+ // the boot shard came with the drive; the bundle did not
201
+ expect(fetched.some(function (url) { return url.includes('vfs-snapshot-1'); })).toBe(false);
202
+ listing = runtime.run({
203
+ code: [
204
+ "const bundle = '".concat(bundleShard, "'"),
205
+ "const folder = '".concat(drivePath('services/web'), "'"),
206
+ "const seen = fs.readdirSync(folder).join(',')",
207
+ "_webout = [seen, fs.existsSync(bundle), fs.statSync(bundle).size].join('|')"
208
+ ].join('\n')
209
+ });
210
+ expect(listing.error).toBeNull();
211
+ expect(listing.webout).toBe("index.bundle.js|true|".concat(bundle.length));
212
+ refused = runtime.run({
213
+ code: [
214
+ "let code = 'none'",
215
+ "try { fs.readFileSync('".concat(bundleShard, "') } catch (e) { code = e.code }"),
216
+ "_webout = code"
217
+ ].join('\n')
218
+ });
219
+ expect(refused.webout).toBe(runtime.PENDING_CODE);
220
+ return [4 /*yield*/, runtime.ensurePaths([bundleShard])];
221
+ case 2:
222
+ _a.sent();
223
+ expect(fetched.some(function (url) { return url.includes('vfs-snapshot-1'); })).toBe(true);
224
+ read = runtime.run({
225
+ code: "_webout = String(fs.readFileSync('".concat(bundleShard, "').length)")
226
+ });
227
+ expect(read.webout).toBe(String(bundle.length));
228
+ return [2 /*return*/];
229
+ }
230
+ });
231
+ }); });
232
+ /**
233
+ * A shard that cannot be read is named, not silently absent.
234
+ *
235
+ * The drive answers for a file it holds whether or not its bytes are in
236
+ * memory, so a piece that fails to arrive is the one way a read can end up
237
+ * with nothing: saying which file was missing is the difference between a
238
+ * broken deployment and a mystery.
239
+ */
240
+ it('names a shard it cannot fetch, and refuses one of another format', function () { return __awaiter(void 0, void 0, void 0, function () {
241
+ var runtime, missing, foreign, index;
242
+ return __generator(this, function (_a) {
243
+ switch (_a.label) {
244
+ case 0:
245
+ runtime = loadRuntime();
246
+ missing = "".concat(drivePath('missing.json'));
247
+ foreign = "".concat(drivePath('foreign.bin'));
248
+ index = function (shardFiles) { return ({
249
+ format: mockSite_1.DRIVE_FORMAT,
250
+ sharded: true,
251
+ boot: 0,
252
+ entries: Object.fromEntries(shardFiles.map(function (path, i) { return [path, { shard: i + 1, bytes: 4 }]; })),
253
+ shards: __spreadArray([
254
+ { id: 0, file: 'host/vfs-snapshot-0.json' }
255
+ ], __read(shardFiles.map(function (_, i) { return ({
256
+ id: i + 1,
257
+ file: "host/vfs-snapshot-".concat(i + 1, ".json")
258
+ }); })), false)
259
+ }); };
260
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
261
+ return __generator(this, function (_a) {
262
+ if (url.includes('mock-config'))
263
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc })];
264
+ if (url.includes('vfs-snapshot-0'))
265
+ return [2 /*return*/, jsonResponse({ files: {} })];
266
+ if (url.includes('vfs-snapshot-1'))
267
+ return [2 /*return*/, { ok: false, status: 404 }];
268
+ if (url.includes('vfs-snapshot-2'))
269
+ return [2 /*return*/, jsonResponse({ format: 99, files: {} })];
270
+ if (url.includes('vfs-snapshot'))
271
+ return [2 /*return*/, jsonResponse(index([missing, foreign]))];
272
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
273
+ return [2 /*return*/, ({})];
274
+ }); }); } }];
275
+ });
276
+ }); });
277
+ return [4 /*yield*/, runtime.seed()];
278
+ case 1:
279
+ _a.sent();
280
+ return [4 /*yield*/, expect(runtime.ensurePaths([missing])).rejects.toThrow(/vfs-snapshot-1\.json.*404/)];
281
+ case 2:
282
+ _a.sent();
283
+ return [4 /*yield*/, expect(runtime.ensurePaths([foreign])).rejects.toThrow(/format 99/)];
284
+ case 3:
285
+ _a.sent();
286
+ return [2 /*return*/];
287
+ }
288
+ });
289
+ }); });
290
+ it('boots without its boot shard, and names the piece a read could not fetch', function () { return __awaiter(void 0, void 0, void 0, function () {
291
+ var runtime, path;
292
+ return __generator(this, function (_a) {
293
+ switch (_a.label) {
294
+ case 0:
295
+ runtime = loadRuntime();
296
+ path = "".concat(drivePath('config.json'));
297
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
298
+ var _a;
299
+ return __generator(this, function (_b) {
300
+ if (url.includes('mock-config'))
301
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc, snapshotShards: 1 })];
302
+ if (/vfs-snapshot-0\.json/.test(url))
303
+ return [2 /*return*/, { ok: false, status: 404 }];
304
+ if (url.includes('vfs-snapshot'))
305
+ return [2 /*return*/, jsonResponse({
306
+ format: mockSite_1.DRIVE_FORMAT,
307
+ sharded: true,
308
+ boot: 0,
309
+ entries: (_a = {}, _a[path] = { shard: 0, bytes: 2 }, _a),
310
+ shards: [{ id: 0, file: 'host/vfs-snapshot-0.json' }]
311
+ })];
312
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
313
+ return [2 /*return*/, ({})];
314
+ }); }); } }];
315
+ });
316
+ }); });
317
+ // a half-uploaded deploy must not leave the page with no drive at all
318
+ return [4 /*yield*/, expect(runtime.seed()).resolves.toEqual(expect.objectContaining({ booted: true, driveError: expect.any(String) }))];
319
+ case 1:
320
+ // a half-uploaded deploy must not leave the page with no drive at all
321
+ _a.sent();
322
+ return [4 /*yield*/, expect(runtime.ensurePaths([path])).rejects.toThrow(/vfs-snapshot-0\.json.*404/)];
323
+ case 2:
324
+ _a.sent();
325
+ return [2 /*return*/];
326
+ }
327
+ });
328
+ }); });
329
+ it('counts the pieces still on the site in the drive total', function () { return __awaiter(void 0, void 0, void 0, function () {
330
+ var runtime, onSite;
331
+ return __generator(this, function (_a) {
332
+ switch (_a.label) {
333
+ case 0:
334
+ runtime = loadRuntime();
335
+ onSite = "".concat(drivePath('bundle.js'));
336
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
337
+ var _a, _b;
338
+ return __generator(this, function (_c) {
339
+ if (url.includes('mock-config'))
340
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc, snapshotShards: 2 })];
341
+ if (/vfs-snapshot-0\.json/.test(url))
342
+ return [2 /*return*/, jsonResponse({ files: (_a = {}, _a[drivePath('small.txt')] = 'ab', _a) })];
343
+ if (url.includes('vfs-snapshot'))
344
+ return [2 /*return*/, jsonResponse({
345
+ format: mockSite_1.DRIVE_FORMAT,
346
+ sharded: true,
347
+ boot: 0,
348
+ entries: (_b = {},
349
+ _b[drivePath('small.txt')] = { shard: 0, bytes: 2 },
350
+ _b[onSite] = { shard: 1, bytes: 4096 },
351
+ _b),
352
+ shards: [
353
+ { id: 0, file: 'host/vfs-snapshot-0.json' },
354
+ { id: 1, file: 'host/vfs-snapshot-1.json' }
355
+ ]
356
+ })];
357
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
358
+ return [2 /*return*/, ({})];
359
+ }); }); } }];
360
+ });
361
+ }); });
362
+ return [4 /*yield*/, runtime.seed()
363
+ // the piece nobody read is still part of the drive
364
+ ];
365
+ case 1:
366
+ _a.sent();
367
+ // the piece nobody read is still part of the drive
368
+ expect(runtime.stats()).toEqual(expect.objectContaining({ files: 1, onSite: 1, bytes: 2 + 4096 }));
369
+ return [2 /*return*/];
370
+ }
371
+ });
372
+ }); });
373
+ /**
374
+ * A deletion outlives the page, and the index must not undo it.
375
+ *
376
+ * A sharded site's index lists every file the build ships, so a file the
377
+ * visitor removed would come back with the next page load - the tombstone the
378
+ * page keeps is what stops it.
379
+ */
380
+ it('keeps a deleted file deleted across a restore', function () { return __awaiter(void 0, void 0, void 0, function () {
381
+ var runtime, captured, kept, read;
382
+ var _a;
383
+ return __generator(this, function (_b) {
384
+ switch (_b.label) {
385
+ case 0:
386
+ runtime = loadRuntime();
387
+ captured = "".concat(drivePath('captured.txt'));
388
+ kept = "".concat(drivePath('kept.txt'));
389
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
390
+ var _a, _b;
391
+ return __generator(this, function (_c) {
392
+ if (url.includes('mock-config'))
393
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc, snapshotShards: 1 })];
394
+ if (/vfs-snapshot-0\.json/.test(url))
395
+ return [2 /*return*/, jsonResponse({ files: (_a = {}, _a[kept] = 'kept', _a) })];
396
+ if (url.includes('vfs-snapshot'))
397
+ return [2 /*return*/, jsonResponse({
398
+ format: mockSite_1.DRIVE_FORMAT,
399
+ sharded: true,
400
+ boot: 0,
401
+ entries: (_b = {},
402
+ _b[captured] = { shard: 0, bytes: 8 },
403
+ _b[kept] = { shard: 0, bytes: 4 },
404
+ _b),
405
+ shards: [{ id: 0, file: 'host/vfs-snapshot-0.json' }]
406
+ })];
407
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
408
+ return [2 /*return*/, ({})];
409
+ }); }); } }];
410
+ });
411
+ }); });
412
+ return [4 /*yield*/, runtime.load((_a = {}, _a[kept] = 'kept', _a), [captured])];
413
+ case 1:
414
+ _b.sent();
415
+ expect(runtime.stats()).toEqual(expect.objectContaining({ files: 1, onSite: 0 }));
416
+ read = runtime.run({
417
+ code: [
418
+ "let seen = 'read'",
419
+ "try { fs.readFileSync('".concat(captured, "') } catch (e) { seen = e.code }"),
420
+ "_webout = [fs.existsSync('".concat(captured, "'), seen].join('|')")
421
+ ].join('\n')
422
+ });
423
+ expect(read.webout).toBe('false|ENOENT');
424
+ return [2 /*return*/];
425
+ }
426
+ });
427
+ }); });
428
+ it('asks for no drive index when the build shipped an unsharded capture', function () { return __awaiter(void 0, void 0, void 0, function () {
429
+ var runtime, fetched;
430
+ var _a;
431
+ return __generator(this, function (_b) {
432
+ switch (_b.label) {
433
+ case 0:
434
+ runtime = loadRuntime();
435
+ fetched = [];
436
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
437
+ return __generator(this, function (_a) {
438
+ fetched.push(url);
439
+ if (url.includes('mock-config'))
440
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc })];
441
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
442
+ return [2 /*return*/, ({})];
443
+ }); }); } }];
444
+ });
445
+ }); });
446
+ // a site built before the split carries no count, and the warm path must not
447
+ // pay a request for an index that does not exist
448
+ return [4 /*yield*/, runtime.load((_a = {}, _a[drivePath('a.txt')] = 'a', _a))];
449
+ case 1:
450
+ // a site built before the split carries no count, and the warm path must not
451
+ // pay a request for an index that does not exist
452
+ _b.sent();
453
+ expect(fetched.some(function (url) { return url.includes('vfs-snapshot'); })).toBe(false);
454
+ return [2 /*return*/];
455
+ }
456
+ });
457
+ }); });
458
+ it('restores a drive whose boot shard did not arrive, and says so', function () { return __awaiter(void 0, void 0, void 0, function () {
459
+ var runtime, path;
460
+ return __generator(this, function (_a) {
461
+ switch (_a.label) {
462
+ case 0:
463
+ runtime = loadRuntime();
464
+ path = "".concat(drivePath('config.json'));
465
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
466
+ var _a;
467
+ return __generator(this, function (_b) {
468
+ if (url.includes('mock-config'))
469
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc, snapshotShards: 1 })];
470
+ if (/vfs-snapshot-0\.json/.test(url))
471
+ return [2 /*return*/, { ok: false, status: 404 }];
472
+ if (url.includes('vfs-snapshot'))
473
+ return [2 /*return*/, jsonResponse({
474
+ format: mockSite_1.DRIVE_FORMAT,
475
+ sharded: true,
476
+ boot: 0,
477
+ entries: (_a = {}, _a[path] = { shard: 0, bytes: 2 }, _a),
478
+ shards: [{ id: 0, file: 'host/vfs-snapshot-0.json' }]
479
+ })];
480
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
481
+ return [2 /*return*/, ({})];
482
+ }); }); } }];
483
+ });
484
+ }); });
485
+ // a returning visitor must not be refused where a fresh visitor is only
486
+ // short: the restore reports the failure and keeps the index
487
+ return [4 /*yield*/, expect(runtime.load({})).resolves.toEqual(expect.objectContaining({ booted: true, driveError: expect.any(String) }))];
488
+ case 1:
489
+ // a returning visitor must not be refused where a fresh visitor is only
490
+ // short: the restore reports the failure and keeps the index
491
+ _a.sent();
492
+ return [2 /*return*/];
493
+ }
494
+ });
495
+ }); });
496
+ /**
497
+ * A write over a captured file replaces it, in both directions.
498
+ *
499
+ * The file's bytes are still on the site, so it is in the index as well as in
500
+ * memory unless the write takes it out - and then a listing names it twice, the
501
+ * drive total counts it twice, and a piece fetched later puts the capture's copy
502
+ * back over the visitor's.
503
+ */
504
+ it('takes a written path out of the capture, so it is counted and listed once', function () { return __awaiter(void 0, void 0, void 0, function () {
505
+ var runtime, path, written, read;
506
+ return __generator(this, function (_a) {
507
+ switch (_a.label) {
508
+ case 0:
509
+ runtime = loadRuntime();
510
+ path = "".concat(drivePath('overwritten.txt'));
511
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
512
+ var _a, _b;
513
+ return __generator(this, function (_c) {
514
+ if (url.includes('mock-config'))
515
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc, snapshotShards: 2 })];
516
+ if (/vfs-snapshot-0\.json/.test(url))
517
+ return [2 /*return*/, jsonResponse({ files: {} })];
518
+ if (/vfs-snapshot-1\.json/.test(url))
519
+ return [2 /*return*/, jsonResponse({
520
+ format: mockSite_1.DRIVE_FORMAT,
521
+ files: (_a = {}, _a[path] = 'captured content', _a)
522
+ })];
523
+ if (url.includes('vfs-snapshot'))
524
+ return [2 /*return*/, jsonResponse({
525
+ format: mockSite_1.DRIVE_FORMAT,
526
+ sharded: true,
527
+ boot: 0,
528
+ entries: (_b = {}, _b[path] = { shard: 1, bytes: 16 }, _b),
529
+ shards: [
530
+ { id: 0, file: 'host/vfs-snapshot-0.json' },
531
+ { id: 1, file: 'host/vfs-snapshot-1.json' }
532
+ ]
533
+ })];
534
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
535
+ return [2 /*return*/, ({})];
536
+ }); }); } }];
537
+ });
538
+ }); });
539
+ return [4 /*yield*/, runtime.seed()];
540
+ case 1:
541
+ _a.sent();
542
+ written = runtime.run({
543
+ code: "fs.writeFileSync('".concat(path, "', 'mine'); _webout = 'ok'")
544
+ });
545
+ expect(written.webout).toBe('ok');
546
+ read = runtime.run({
547
+ code: [
548
+ "const folder = fs.readdirSync('".concat(drivePath(''), "').filter((name) => name === 'overwritten.txt')"),
549
+ "_webout = [folder.length, fs.readFileSync('".concat(path, "')].join('|')")
550
+ ].join('\n')
551
+ });
552
+ expect(read.webout).toBe('1|mine');
553
+ // nothing of the capture is left unloaded for that path: the run's own session
554
+ // files are the only other entries, so the count of pieces still on the site
555
+ // is what says the write took the path out of the index
556
+ expect(runtime.stats()).toEqual(expect.objectContaining({ onSite: 0 }));
557
+ return [2 /*return*/];
558
+ }
559
+ });
560
+ }); });
561
+ it('reports an index that failed, rather than restoring the visitor files alone', function () { return __awaiter(void 0, void 0, void 0, function () {
562
+ var runtime;
563
+ return __generator(this, function (_a) {
564
+ switch (_a.label) {
565
+ case 0:
566
+ runtime = loadRuntime();
567
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
568
+ return __generator(this, function (_a) {
569
+ if (url.includes('mock-config'))
570
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc, snapshotShards: 1 })];
571
+ if (url.includes('vfs-snapshot'))
572
+ return [2 /*return*/, { ok: false, status: 500 }];
573
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
574
+ return [2 /*return*/, ({})];
575
+ }); }); } }];
576
+ });
577
+ }); });
578
+ // a 500 is not "this site ships no snapshot": restoring without the index
579
+ // would leave only the files this visitor changed, silently
580
+ return [4 /*yield*/, expect(runtime.load({})).resolves.toEqual(expect.objectContaining({
581
+ booted: true,
582
+ driveError: expect.stringContaining('answered 500')
583
+ }))
584
+ // and 404 still reads as a site with nothing to ship
585
+ ];
586
+ case 1:
587
+ // a 500 is not "this site ships no snapshot": restoring without the index
588
+ // would leave only the files this visitor changed, silently
589
+ _a.sent();
590
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
591
+ return __generator(this, function (_a) {
592
+ if (url.includes('mock-config'))
593
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc, snapshotShards: 1 })];
594
+ if (url.includes('vfs-snapshot'))
595
+ return [2 /*return*/, { ok: false, status: 404 }];
596
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
597
+ return [2 /*return*/, ({})];
598
+ }); }); } }];
599
+ });
600
+ }); });
601
+ return [4 /*yield*/, expect(loadRuntime().load({})).resolves.toEqual(expect.objectContaining({ booted: true }))];
602
+ case 2:
603
+ _a.sent();
604
+ return [2 /*return*/];
605
+ }
606
+ });
607
+ }); });
608
+ it('refuses an append onto a file whose bytes are still on the site', function () { return __awaiter(void 0, void 0, void 0, function () {
609
+ var runtime, path, refused, appended;
610
+ return __generator(this, function (_a) {
611
+ switch (_a.label) {
612
+ case 0:
613
+ runtime = loadRuntime();
614
+ path = "".concat(drivePath('appended.txt'));
615
+ global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
616
+ var _a, _b;
617
+ return __generator(this, function (_c) {
618
+ if (url.includes('mock-config'))
619
+ return [2 /*return*/, jsonResponse({ appLoc: appLoc, snapshotShards: 2 })];
620
+ if (/vfs-snapshot-0\.json/.test(url))
621
+ return [2 /*return*/, jsonResponse({ files: {} })];
622
+ if (/vfs-snapshot-1\.json/.test(url))
623
+ return [2 /*return*/, jsonResponse({
624
+ format: mockSite_1.DRIVE_FORMAT,
625
+ files: (_a = {}, _a[path] = 'first', _a)
626
+ })];
627
+ if (url.includes('vfs-snapshot'))
628
+ return [2 /*return*/, jsonResponse({
629
+ format: mockSite_1.DRIVE_FORMAT,
630
+ sharded: true,
631
+ boot: 0,
632
+ entries: (_b = {}, _b[path] = { shard: 1, bytes: 5 }, _b),
633
+ shards: [
634
+ { id: 0, file: 'host/vfs-snapshot-0.json' },
635
+ { id: 1, file: 'host/vfs-snapshot-1.json' }
636
+ ]
637
+ })];
638
+ return [2 /*return*/, { ok: false, json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
639
+ return [2 /*return*/, ({})];
640
+ }); }); } }];
641
+ });
642
+ }); });
643
+ return [4 /*yield*/, runtime.seed()
644
+ // the capture's bytes are still on the site, and appending would replace them
645
+ ];
646
+ case 1:
647
+ _a.sent();
648
+ refused = runtime.run({
649
+ code: "fs.appendFileSync('".concat(path, "', 'plus'); _webout = 'ok'")
650
+ });
651
+ // the statement after the append never ran: the write was refused, not done
652
+ expect(refused.webout).not.toBe('ok');
653
+ expect(runtime.stats()).toEqual(expect.objectContaining({ onSite: 1 }));
654
+ // once the piece is here the append lands on top of what was captured
655
+ return [4 /*yield*/, runtime.ensurePaths([path])];
656
+ case 2:
657
+ // once the piece is here the append lands on top of what was captured
658
+ _a.sent();
659
+ appended = runtime.run({
660
+ code: "fs.appendFileSync('".concat(path, "', 'plus'); _webout = fs.readFileSync('").concat(path, "')")
661
+ });
662
+ expect(appended.webout).toBe('firstplus');
663
+ return [2 /*return*/];
664
+ }
665
+ });
666
+ }); });
121
667
  it('refuses a drive file whose format it does not speak', function () { return __awaiter(void 0, void 0, void 0, function () {
122
668
  var runtime, result;
123
669
  return __generator(this, function (_a) {