@sasjs/cli 6.10.1 → 6.12.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.
- package/build/commands/build/internal/mockSite/assets/runtime-core.js +221 -75
- package/build/commands/build/internal/mockSite/assets/sw.js +160 -24
- package/build/commands/build/internal/mockSite/mockSite.js +34 -2
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +120 -1
- package/build/commands/build/internal/mockSite/spec/runtime-core.spec.js +480 -14
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +710 -4
- package/package.json +3 -2
|
@@ -167,7 +167,8 @@ describe('browser-hosted build', function () {
|
|
|
167
167
|
'md5.js',
|
|
168
168
|
'runtime-core.js',
|
|
169
169
|
'persist.js',
|
|
170
|
-
'intercept.js'
|
|
170
|
+
'intercept.js',
|
|
171
|
+
'api-browser.js'
|
|
171
172
|
]), _b = _a.next(); !_b.done; _b = _a.next()) {
|
|
172
173
|
asset = _b.value;
|
|
173
174
|
expect(fs_1.default.existsSync(path_1.default.join(out(), 'host', asset))).toBe(true);
|
|
@@ -189,6 +190,124 @@ describe('browser-hosted build', function () {
|
|
|
189
190
|
}
|
|
190
191
|
});
|
|
191
192
|
}); });
|
|
193
|
+
it('ships the @sasjs/api build the CLI resolves, and versions both imports', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
194
|
+
var shipped, installed, sw;
|
|
195
|
+
return __generator(this, function (_a) {
|
|
196
|
+
switch (_a.label) {
|
|
197
|
+
case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())
|
|
198
|
+
// the same file the installed package carries: a vendored copy is exactly
|
|
199
|
+
// how the worker and the server drift apart
|
|
200
|
+
];
|
|
201
|
+
case 1:
|
|
202
|
+
_a.sent();
|
|
203
|
+
shipped = fs_1.default.readFileSync(path_1.default.join(out(), 'host', 'api-browser.js'));
|
|
204
|
+
installed = fs_1.default.readFileSync(path_1.default.join(path_1.default.dirname(require.resolve('@sasjs/api')), '..', '..', 'build', 'browser.js'));
|
|
205
|
+
expect(shipped.equals(installed)).toBe(true);
|
|
206
|
+
sw = fs_1.default.readFileSync(path_1.default.join(out(), 'sw.js'), 'utf8');
|
|
207
|
+
// both imports carry the host version: a worker caches what it imports, so
|
|
208
|
+
// a rebuild that changes either file has to change the URL
|
|
209
|
+
expect(sw).toMatch(/importScripts\('\/host\/runtime-core\.js\?v=[0-9a-f]{10}'\)/);
|
|
210
|
+
expect(sw).toMatch(/importScripts\('\/host\/api-browser\.js\?v=[0-9a-f]{10}'\)/);
|
|
211
|
+
expect(sw).not.toContain("importScripts('/host/runtime-core.js')");
|
|
212
|
+
return [2 /*return*/];
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}); });
|
|
216
|
+
it('names the package when its browser build is missing, and keeps the previous site', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
217
|
+
var browserBuild, exists;
|
|
218
|
+
return __generator(this, function (_a) {
|
|
219
|
+
switch (_a.label) {
|
|
220
|
+
case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
221
|
+
case 1:
|
|
222
|
+
_a.sent();
|
|
223
|
+
write(path_1.default.join(out(), 'stale.txt'), 'previous');
|
|
224
|
+
browserBuild = path_1.default.join(path_1.default.dirname(require.resolve('@sasjs/api')), '..', '..', 'build', 'browser.js');
|
|
225
|
+
exists = jest.spyOn(fs_1.default, 'existsSync');
|
|
226
|
+
exists.mockImplementation(function (checked) { return String(checked) !== browserBuild; });
|
|
227
|
+
_a.label = 2;
|
|
228
|
+
case 2:
|
|
229
|
+
_a.trys.push([2, , 4, 5]);
|
|
230
|
+
return [4 /*yield*/, expect((0, mockSite_1.buildMockSite)(target())).rejects.toThrow(/@sasjs\/api browser build is missing/)];
|
|
231
|
+
case 3:
|
|
232
|
+
_a.sent();
|
|
233
|
+
return [3 /*break*/, 5];
|
|
234
|
+
case 4:
|
|
235
|
+
exists.mockRestore();
|
|
236
|
+
return [7 /*endfinally*/];
|
|
237
|
+
case 5:
|
|
238
|
+
// and the refusal lands before anything is written, so the site that was
|
|
239
|
+
// there is still there
|
|
240
|
+
expect(fs_1.default.existsSync(path_1.default.join(out(), 'stale.txt'))).toBe(true);
|
|
241
|
+
expect(fs_1.default.readFileSync(path_1.default.join(out(), 'stale.txt'), 'utf8')).toBe('previous');
|
|
242
|
+
return [2 /*return*/];
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}); });
|
|
246
|
+
/**
|
|
247
|
+
* The worker's scripts share one global scope, so a name declared at the top
|
|
248
|
+
* level of two of them is a SyntaxError in the browser - and invisible to this
|
|
249
|
+
* suite, where each file is a module of its own. `importScripts` is the
|
|
250
|
+
* browser's rule: evaluate the files the build wrote, in one context, once.
|
|
251
|
+
*/
|
|
252
|
+
it('evaluates the shipped scripts in one global scope, the way a worker does', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
253
|
+
var vm, sandbox, context, imported;
|
|
254
|
+
return __generator(this, function (_a) {
|
|
255
|
+
switch (_a.label) {
|
|
256
|
+
case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
257
|
+
case 1:
|
|
258
|
+
_a.sent();
|
|
259
|
+
vm = require('vm');
|
|
260
|
+
sandbox = {
|
|
261
|
+
console: console,
|
|
262
|
+
Request: Request,
|
|
263
|
+
Response: Response,
|
|
264
|
+
Headers: Headers,
|
|
265
|
+
URL: URL,
|
|
266
|
+
FormData: FormData,
|
|
267
|
+
Blob: Blob,
|
|
268
|
+
TextEncoder: TextEncoder,
|
|
269
|
+
TextDecoder: TextDecoder,
|
|
270
|
+
Uint8Array: Uint8Array,
|
|
271
|
+
ArrayBuffer: ArrayBuffer,
|
|
272
|
+
atob: atob,
|
|
273
|
+
btoa: btoa,
|
|
274
|
+
setTimeout: setTimeout,
|
|
275
|
+
clearTimeout: clearTimeout,
|
|
276
|
+
location: { origin: 'https://mock.example.com' },
|
|
277
|
+
clients: { claim: function () { }, matchAll: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
278
|
+
return [2 /*return*/, []];
|
|
279
|
+
}); }); } },
|
|
280
|
+
skipWaiting: function () { },
|
|
281
|
+
addEventListener: function () { },
|
|
282
|
+
fetch: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
283
|
+
return [2 /*return*/, new Response('{}', { headers: { 'content-type': 'application/json' } })];
|
|
284
|
+
}); }); }
|
|
285
|
+
};
|
|
286
|
+
sandbox.self = sandbox;
|
|
287
|
+
context = vm.createContext(sandbox);
|
|
288
|
+
imported = new Set();
|
|
289
|
+
sandbox.importScripts = function (url) {
|
|
290
|
+
if (imported.has(url))
|
|
291
|
+
return;
|
|
292
|
+
imported.add(url);
|
|
293
|
+
var file = path_1.default.join(out(), url.replace(/^\//, '').split('?')[0]);
|
|
294
|
+
vm.runInContext(fs_1.default.readFileSync(file, 'utf8'), context, {
|
|
295
|
+
filename: file
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
expect(function () {
|
|
299
|
+
return vm.runInContext(fs_1.default.readFileSync(path_1.default.join(out(), 'sw.js'), 'utf8'), context, {
|
|
300
|
+
filename: 'sw.js'
|
|
301
|
+
});
|
|
302
|
+
}).not.toThrow();
|
|
303
|
+
// both scripts are loaded, and the drive the API is handed came from the
|
|
304
|
+
// runtime rather than a second implementation
|
|
305
|
+
expect(Object.keys(sandbox.SASjsMockRuntime)).toContain('driveStore');
|
|
306
|
+
expect(typeof sandbox.SASjsApi.createDriveService).toBe('function');
|
|
307
|
+
return [2 /*return*/];
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}); });
|
|
192
311
|
it('ships the webout() block with sentinels the runtime fills in', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
193
312
|
var prelude, _a, _b, sentinel;
|
|
194
313
|
var e_2, _c;
|
|
@@ -35,7 +35,12 @@ 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 __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
38
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
var fs_1 = __importDefault(require("fs"));
|
|
43
|
+
var path_1 = __importDefault(require("path"));
|
|
39
44
|
var utils_1 = require("@sasjs/utils");
|
|
40
45
|
var mockSite_1 = require("../mockSite");
|
|
41
46
|
/**
|
|
@@ -249,6 +254,83 @@ describe('browser runtime', function () {
|
|
|
249
254
|
expect(result.log).toContain('boom');
|
|
250
255
|
expect(result.webout).toBe('');
|
|
251
256
|
});
|
|
257
|
+
/**
|
|
258
|
+
* The shims a service reaches for are node's, refusals included: a mock that
|
|
259
|
+
* answered a copy from a missing file with an empty file, or accepted
|
|
260
|
+
* `createHash('sha256')`, would let a service pass here and fail on the server.
|
|
261
|
+
*/
|
|
262
|
+
it("refuses what the fs, path and crypto shims refuse, with node's words", function () {
|
|
263
|
+
var runtime = loadRuntime();
|
|
264
|
+
var failure = function (code) { return runtime.run({ code: code }).error; };
|
|
265
|
+
expect(failure("fs.copyFileSync('/missing.csv', '/x.csv')")).toMatch(/ENOENT: no such file, copy/);
|
|
266
|
+
expect(failure("fs.statSync('/missing.csv')")).toMatch(/ENOENT: no such file or directory, stat/);
|
|
267
|
+
expect(failure("fs.readFileSync('/missing.csv')")).toMatch(/ENOENT: no such file or directory, open/);
|
|
268
|
+
expect(failure("fs.writeFileSync('/x.csv', 'x', { flag: 'r' })")).toMatch(/Unsupported flag 'r' in the browser host/);
|
|
269
|
+
expect(failure("require('crypto').createHash('sha256')")).toMatch(/Unsupported hash in browser host: sha256/);
|
|
270
|
+
expect(failure("require('net')")).toMatch(/Cannot find module 'net' in the browser runtime host/);
|
|
271
|
+
});
|
|
272
|
+
it('answers the path and Buffer helpers a service uses', function () {
|
|
273
|
+
var runtime = loadRuntime();
|
|
274
|
+
var result = runtime.run({
|
|
275
|
+
code: [
|
|
276
|
+
"const path = require('path')",
|
|
277
|
+
'_webout = JSON.stringify({',
|
|
278
|
+
" ext: path.extname('/data/a.csv'),",
|
|
279
|
+
" none: path.extname('csv'),",
|
|
280
|
+
" base: path.basename('/data/a.csv'),",
|
|
281
|
+
" dir: path.dirname('/data/a.csv')",
|
|
282
|
+
'})'
|
|
283
|
+
].join('\n')
|
|
284
|
+
});
|
|
285
|
+
expect(JSON.parse(result.webout)).toEqual({
|
|
286
|
+
ext: '.csv',
|
|
287
|
+
none: '',
|
|
288
|
+
base: 'a.csv',
|
|
289
|
+
dir: '/data'
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
it('hashes with the md5 the worker imports, as node does', function () {
|
|
293
|
+
var runtime = loadRuntime();
|
|
294
|
+
// md5.js declares its hash in the global scope of the worker that imports
|
|
295
|
+
// it; a jest module would hide it, so it is evaluated the way the worker
|
|
296
|
+
// does - which is also what makes this the real md5 rather than a stub
|
|
297
|
+
var source = fs_1.default.readFileSync(path_1.default.join(__dirname, '..', 'assets', 'md5.js'), 'utf8');
|
|
298
|
+
global.MD5 = new Function("".concat(source, "\nreturn MD5"))();
|
|
299
|
+
var result = runtime.run({
|
|
300
|
+
code: "_webout = require('crypto').createHash('md5').update('abc').digest('hex')"
|
|
301
|
+
});
|
|
302
|
+
// md5('abc'), the digest node gives for the same call
|
|
303
|
+
expect(result.webout).toBe('900150983cd24fb0d6963f7d28e17f72');
|
|
304
|
+
});
|
|
305
|
+
it('reports a write that fails through the callback node hands it', function () {
|
|
306
|
+
var runtime = loadRuntime();
|
|
307
|
+
var result = runtime.run({
|
|
308
|
+
code: [
|
|
309
|
+
'let reported = null',
|
|
310
|
+
"fs.writeFile('/x.csv', 'x', { flag: 'r' }, (err) => { reported = err && err.message })",
|
|
311
|
+
'_webout = String(reported)'
|
|
312
|
+
].join('\n')
|
|
313
|
+
});
|
|
314
|
+
expect(result.webout).toMatch(/Unsupported flag 'r' in the browser host/);
|
|
315
|
+
});
|
|
316
|
+
it('refuses to boot a worker that has neither a seed nor a snapshot', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
317
|
+
var runtime, info;
|
|
318
|
+
return __generator(this, function (_a) {
|
|
319
|
+
switch (_a.label) {
|
|
320
|
+
case 0:
|
|
321
|
+
runtime = loadRuntime();
|
|
322
|
+
global.fetch = jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
323
|
+
return [2 /*return*/, ({ ok: false })];
|
|
324
|
+
}); }); });
|
|
325
|
+
return [4 /*yield*/, runtime.seed()];
|
|
326
|
+
case 1:
|
|
327
|
+
info = _a.sent();
|
|
328
|
+
expect(info.booted).toBe(false);
|
|
329
|
+
expect(info.error).toMatch(/No drive to load/);
|
|
330
|
+
return [2 /*return*/];
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}); });
|
|
252
334
|
it('collects service console output into the log', function () {
|
|
253
335
|
var runtime = loadRuntime();
|
|
254
336
|
var result = runtime.run({
|
|
@@ -847,24 +929,37 @@ describe('browser runtime', function () {
|
|
|
847
929
|
});
|
|
848
930
|
expect(result.webout).toBe('AQID');
|
|
849
931
|
});
|
|
850
|
-
it('
|
|
851
|
-
var runtime, listing,
|
|
852
|
-
var
|
|
853
|
-
return __generator(this, function (
|
|
854
|
-
switch (
|
|
932
|
+
it('hands the API a store over the drive it seeded', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
933
|
+
var runtime, listing, _a, _b;
|
|
934
|
+
var _c;
|
|
935
|
+
return __generator(this, function (_d) {
|
|
936
|
+
switch (_d.label) {
|
|
855
937
|
case 0:
|
|
856
938
|
runtime = loadRuntime();
|
|
857
|
-
return [4 /*yield*/, seedDrive(runtime, (
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
939
|
+
return [4 /*yield*/, seedDrive(runtime, (_c = {},
|
|
940
|
+
_c[drivePath('services/public/getdata.js')] = '_webout = "x";',
|
|
941
|
+
_c[drivePath('data/a.txt')] = 'a',
|
|
942
|
+
_c))
|
|
943
|
+
// drive-relative paths, the form every @sasjs/api service speaks: the app
|
|
944
|
+
// loc is part of the path, the host's own root is not
|
|
945
|
+
];
|
|
861
946
|
case 1:
|
|
862
|
-
|
|
863
|
-
|
|
947
|
+
_d.sent();
|
|
948
|
+
return [4 /*yield*/, runtime.driveStore.list(appLoc + '/services/public')];
|
|
949
|
+
case 2:
|
|
950
|
+
listing = _d.sent();
|
|
864
951
|
expect(listing.files).toEqual(['getdata.js']);
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
952
|
+
_a = expect;
|
|
953
|
+
return [4 /*yield*/, runtime.driveStore.read(appLoc + '/data/a.txt')];
|
|
954
|
+
case 3:
|
|
955
|
+
_a.apply(void 0, [_d.sent()]).toBe('a');
|
|
956
|
+
_b = expect;
|
|
957
|
+
return [4 /*yield*/, runtime.driveStore.stat(appLoc + '/data')];
|
|
958
|
+
case 4:
|
|
959
|
+
_b.apply(void 0, [_d.sent()]).toEqual({
|
|
960
|
+
directory: true,
|
|
961
|
+
size: 0
|
|
962
|
+
});
|
|
868
963
|
return [2 /*return*/];
|
|
869
964
|
}
|
|
870
965
|
});
|
|
@@ -949,4 +1044,375 @@ describe('browser runtime', function () {
|
|
|
949
1044
|
var runtime = loadRuntime();
|
|
950
1045
|
expect(function () { return runtime.run({ program: 'services/public/missing' }); }).toThrow(/Program not found in drive/);
|
|
951
1046
|
});
|
|
1047
|
+
describe('drive store', function () {
|
|
1048
|
+
// the store the API is handed: drive-relative paths in, the drive's own
|
|
1049
|
+
// absolute keys behind. What a status or a message should be is the
|
|
1050
|
+
// package's business (spec/sw.spec.ts runs its corpus through this worker),
|
|
1051
|
+
// so these cases are the storage contract only.
|
|
1052
|
+
var root = '/sasjs/drive/files';
|
|
1053
|
+
var seededRuntime = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1054
|
+
var runtime;
|
|
1055
|
+
var _a;
|
|
1056
|
+
return __generator(this, function (_b) {
|
|
1057
|
+
switch (_b.label) {
|
|
1058
|
+
case 0:
|
|
1059
|
+
runtime = loadRuntime();
|
|
1060
|
+
return [4 /*yield*/, seedDrive(runtime, (_a = {},
|
|
1061
|
+
_a["".concat(root, "/data/keep.csv")] = 'a,b\n1,2\n',
|
|
1062
|
+
_a))
|
|
1063
|
+
// boot marks the whole drive dirty, so the page persists a complete
|
|
1064
|
+
// snapshot; clearing it here leaves each test with only its own writes
|
|
1065
|
+
];
|
|
1066
|
+
case 1:
|
|
1067
|
+
_b.sent();
|
|
1068
|
+
// boot marks the whole drive dirty, so the page persists a complete
|
|
1069
|
+
// snapshot; clearing it here leaves each test with only its own writes
|
|
1070
|
+
runtime.takeDirty();
|
|
1071
|
+
return [2 /*return*/, runtime];
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}); };
|
|
1075
|
+
it('reads text as text and hands back the entry it holds', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1076
|
+
var runtime, store, _a, _b, _c, _d;
|
|
1077
|
+
return __generator(this, function (_e) {
|
|
1078
|
+
switch (_e.label) {
|
|
1079
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1080
|
+
case 1:
|
|
1081
|
+
runtime = _e.sent();
|
|
1082
|
+
store = runtime.driveStore;
|
|
1083
|
+
_a = expect;
|
|
1084
|
+
return [4 /*yield*/, store.exists('/data/keep.csv')];
|
|
1085
|
+
case 2:
|
|
1086
|
+
_a.apply(void 0, [_e.sent()]).toBe(true);
|
|
1087
|
+
_b = expect;
|
|
1088
|
+
return [4 /*yield*/, store.exists('/data/missing.csv')];
|
|
1089
|
+
case 3:
|
|
1090
|
+
_b.apply(void 0, [_e.sent()]).toBe(false);
|
|
1091
|
+
_c = expect;
|
|
1092
|
+
return [4 /*yield*/, store.read('/data/keep.csv')];
|
|
1093
|
+
case 4:
|
|
1094
|
+
_c.apply(void 0, [_e.sent()]).toBe('a,b\n1,2\n');
|
|
1095
|
+
_d = expect;
|
|
1096
|
+
return [4 /*yield*/, store.stat('/data/keep.csv')];
|
|
1097
|
+
case 5:
|
|
1098
|
+
_d.apply(void 0, [_e.sent()]).toEqual({
|
|
1099
|
+
directory: false,
|
|
1100
|
+
size: 8
|
|
1101
|
+
});
|
|
1102
|
+
return [2 /*return*/];
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
}); });
|
|
1106
|
+
it('keeps an entry written as bytes as bytes, and measures it in bytes', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1107
|
+
var runtime, store, _a, _b;
|
|
1108
|
+
return __generator(this, function (_c) {
|
|
1109
|
+
switch (_c.label) {
|
|
1110
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1111
|
+
case 1:
|
|
1112
|
+
runtime = _c.sent();
|
|
1113
|
+
store = runtime.driveStore;
|
|
1114
|
+
return [4 /*yield*/, store.write('/data/pack.zip', Uint8Array.from([80, 75, 3, 4]))];
|
|
1115
|
+
case 2:
|
|
1116
|
+
_c.sent();
|
|
1117
|
+
_a = expect;
|
|
1118
|
+
return [4 /*yield*/, store.read('/data/pack.zip')];
|
|
1119
|
+
case 3:
|
|
1120
|
+
_a.apply(void 0, [_c.sent()]).toEqual(Uint8Array.from([80, 75, 3, 4]));
|
|
1121
|
+
_b = expect;
|
|
1122
|
+
return [4 /*yield*/, store.stat('/data/pack.zip')];
|
|
1123
|
+
case 4:
|
|
1124
|
+
_b.apply(void 0, [_c.sent()]).toEqual({
|
|
1125
|
+
directory: false,
|
|
1126
|
+
size: 4
|
|
1127
|
+
});
|
|
1128
|
+
return [2 /*return*/];
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
}); });
|
|
1132
|
+
it('reports a write to the page, so the entry is persisted', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1133
|
+
var runtime;
|
|
1134
|
+
return __generator(this, function (_a) {
|
|
1135
|
+
switch (_a.label) {
|
|
1136
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1137
|
+
case 1:
|
|
1138
|
+
runtime = _a.sent();
|
|
1139
|
+
return [4 /*yield*/, runtime.driveStore.write('/data/new/deep/up.csv', 'x,y')];
|
|
1140
|
+
case 2:
|
|
1141
|
+
_a.sent();
|
|
1142
|
+
expect(Object.keys(runtime.takeDirty())).toEqual([
|
|
1143
|
+
"".concat(root, "/data/new/deep/up.csv")
|
|
1144
|
+
]);
|
|
1145
|
+
expect(runtime.dumpAll()["".concat(root, "/data/new/deep/up.csv")]).toBe('x,y');
|
|
1146
|
+
return [2 /*return*/];
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
}); });
|
|
1150
|
+
it('reports a deletion, so the page drops the entry', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1151
|
+
var runtime, _a;
|
|
1152
|
+
var _b;
|
|
1153
|
+
return __generator(this, function (_c) {
|
|
1154
|
+
switch (_c.label) {
|
|
1155
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1156
|
+
case 1:
|
|
1157
|
+
runtime = _c.sent();
|
|
1158
|
+
return [4 /*yield*/, runtime.driveStore.remove('/data/keep.csv')];
|
|
1159
|
+
case 2:
|
|
1160
|
+
_c.sent();
|
|
1161
|
+
expect(runtime.takeDirty()).toEqual((_b = {}, _b["".concat(root, "/data/keep.csv")] = null, _b));
|
|
1162
|
+
_a = expect;
|
|
1163
|
+
return [4 /*yield*/, runtime.driveStore.exists('/data/keep.csv')];
|
|
1164
|
+
case 3:
|
|
1165
|
+
_a.apply(void 0, [_c.sent()]).toBe(false);
|
|
1166
|
+
return [2 /*return*/];
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
}); });
|
|
1170
|
+
it('creates a folder and lists the immediate children of a folder', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1171
|
+
var runtime, store, _a, _b;
|
|
1172
|
+
return __generator(this, function (_c) {
|
|
1173
|
+
switch (_c.label) {
|
|
1174
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1175
|
+
case 1:
|
|
1176
|
+
runtime = _c.sent();
|
|
1177
|
+
store = runtime.driveStore;
|
|
1178
|
+
return [4 /*yield*/, store.mkdirp('/data/reports/nested')];
|
|
1179
|
+
case 2:
|
|
1180
|
+
_c.sent();
|
|
1181
|
+
return [4 /*yield*/, store.write('/data/reports/a.csv', 'a')];
|
|
1182
|
+
case 3:
|
|
1183
|
+
_c.sent();
|
|
1184
|
+
_a = expect;
|
|
1185
|
+
return [4 /*yield*/, store.list('/data')];
|
|
1186
|
+
case 4:
|
|
1187
|
+
_a.apply(void 0, [_c.sent()]).toEqual({
|
|
1188
|
+
files: ['keep.csv'],
|
|
1189
|
+
folders: ['reports']
|
|
1190
|
+
});
|
|
1191
|
+
_b = expect;
|
|
1192
|
+
return [4 /*yield*/, store.list('/data/reports')];
|
|
1193
|
+
case 5:
|
|
1194
|
+
_b.apply(void 0, [_c.sent()]).toEqual({
|
|
1195
|
+
files: ['a.csv'],
|
|
1196
|
+
folders: ['nested']
|
|
1197
|
+
});
|
|
1198
|
+
return [2 /*return*/];
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
}); });
|
|
1202
|
+
it('removes a folder with everything under it, and reports each entry', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1203
|
+
var runtime, store, _a, _b;
|
|
1204
|
+
var _c;
|
|
1205
|
+
return __generator(this, function (_d) {
|
|
1206
|
+
switch (_d.label) {
|
|
1207
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1208
|
+
case 1:
|
|
1209
|
+
runtime = _d.sent();
|
|
1210
|
+
store = runtime.driveStore;
|
|
1211
|
+
return [4 /*yield*/, store.mkdirp('/data/reports')];
|
|
1212
|
+
case 2:
|
|
1213
|
+
_d.sent();
|
|
1214
|
+
return [4 /*yield*/, store.write('/data/reports/a.csv', 'a')];
|
|
1215
|
+
case 3:
|
|
1216
|
+
_d.sent();
|
|
1217
|
+
return [4 /*yield*/, store.write('/data/reports/nested/b.csv', 'b')];
|
|
1218
|
+
case 4:
|
|
1219
|
+
_d.sent();
|
|
1220
|
+
runtime.takeDirty();
|
|
1221
|
+
return [4 /*yield*/, store.remove('/data/reports')];
|
|
1222
|
+
case 5:
|
|
1223
|
+
_d.sent();
|
|
1224
|
+
_a = expect;
|
|
1225
|
+
return [4 /*yield*/, store.exists('/data/reports')];
|
|
1226
|
+
case 6:
|
|
1227
|
+
_a.apply(void 0, [_d.sent()]).toBe(false);
|
|
1228
|
+
_b = expect;
|
|
1229
|
+
return [4 /*yield*/, store.list('/data')];
|
|
1230
|
+
case 7:
|
|
1231
|
+
_b.apply(void 0, [_d.sent()]).toEqual({
|
|
1232
|
+
files: ['keep.csv'],
|
|
1233
|
+
folders: []
|
|
1234
|
+
});
|
|
1235
|
+
// every file the removal took goes to the page, or IndexedDB keeps it
|
|
1236
|
+
expect(runtime.takeDirty()).toEqual((_c = {},
|
|
1237
|
+
_c["".concat(root, "/data/reports/a.csv")] = null,
|
|
1238
|
+
_c["".concat(root, "/data/reports/nested/b.csv")] = null,
|
|
1239
|
+
_c));
|
|
1240
|
+
return [2 /*return*/];
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
}); });
|
|
1244
|
+
it('moves a file, and marks both ends', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1245
|
+
var runtime, _a, _b;
|
|
1246
|
+
var _c;
|
|
1247
|
+
return __generator(this, function (_d) {
|
|
1248
|
+
switch (_d.label) {
|
|
1249
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1250
|
+
case 1:
|
|
1251
|
+
runtime = _d.sent();
|
|
1252
|
+
return [4 /*yield*/, runtime.driveStore.move('/data/keep.csv', '/data/kept.csv')];
|
|
1253
|
+
case 2:
|
|
1254
|
+
_d.sent();
|
|
1255
|
+
_a = expect;
|
|
1256
|
+
return [4 /*yield*/, runtime.driveStore.read('/data/kept.csv')];
|
|
1257
|
+
case 3:
|
|
1258
|
+
_a.apply(void 0, [_d.sent()]).toBe('a,b\n1,2\n');
|
|
1259
|
+
_b = expect;
|
|
1260
|
+
return [4 /*yield*/, runtime.driveStore.exists('/data/keep.csv')];
|
|
1261
|
+
case 4:
|
|
1262
|
+
_b.apply(void 0, [_d.sent()]).toBe(false);
|
|
1263
|
+
expect(runtime.takeDirty()).toEqual((_c = {},
|
|
1264
|
+
_c["".concat(root, "/data/kept.csv")] = 'a,b\n1,2\n',
|
|
1265
|
+
_c["".concat(root, "/data/keep.csv")] = null,
|
|
1266
|
+
_c));
|
|
1267
|
+
return [2 /*return*/];
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
1270
|
+
}); });
|
|
1271
|
+
it('moves a folder with its contents, bytes and all', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1272
|
+
var runtime, store, _a, _b, _c;
|
|
1273
|
+
return __generator(this, function (_d) {
|
|
1274
|
+
switch (_d.label) {
|
|
1275
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1276
|
+
case 1:
|
|
1277
|
+
runtime = _d.sent();
|
|
1278
|
+
store = runtime.driveStore;
|
|
1279
|
+
return [4 /*yield*/, store.mkdirp('/data/reports')];
|
|
1280
|
+
case 2:
|
|
1281
|
+
_d.sent();
|
|
1282
|
+
return [4 /*yield*/, store.write('/data/reports/a.csv', 'a')];
|
|
1283
|
+
case 3:
|
|
1284
|
+
_d.sent();
|
|
1285
|
+
return [4 /*yield*/, store.write('/data/reports/nested/pack.zip', Uint8Array.from([1, 2, 3]))];
|
|
1286
|
+
case 4:
|
|
1287
|
+
_d.sent();
|
|
1288
|
+
return [4 /*yield*/, store.move('/data/reports', '/archive')];
|
|
1289
|
+
case 5:
|
|
1290
|
+
_d.sent();
|
|
1291
|
+
_a = expect;
|
|
1292
|
+
return [4 /*yield*/, store.list('/archive')];
|
|
1293
|
+
case 6:
|
|
1294
|
+
_a.apply(void 0, [_d.sent()]).toEqual({
|
|
1295
|
+
files: ['a.csv'],
|
|
1296
|
+
folders: ['nested']
|
|
1297
|
+
});
|
|
1298
|
+
_b = expect;
|
|
1299
|
+
return [4 /*yield*/, store.read('/archive/nested/pack.zip')];
|
|
1300
|
+
case 7:
|
|
1301
|
+
_b.apply(void 0, [_d.sent()]).toEqual(Uint8Array.from([1, 2, 3]));
|
|
1302
|
+
_c = expect;
|
|
1303
|
+
return [4 /*yield*/, store.exists('/data/reports')];
|
|
1304
|
+
case 8:
|
|
1305
|
+
_c.apply(void 0, [_d.sent()]).toBe(false);
|
|
1306
|
+
return [2 /*return*/];
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
}); });
|
|
1310
|
+
it('refuses a write under a path that is a file, rather than writing through it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1311
|
+
var runtime, _a;
|
|
1312
|
+
return __generator(this, function (_b) {
|
|
1313
|
+
switch (_b.label) {
|
|
1314
|
+
case 0: return [4 /*yield*/, seededRuntime()
|
|
1315
|
+
// mkdirp would otherwise turn the file into a folder to hold the child
|
|
1316
|
+
];
|
|
1317
|
+
case 1:
|
|
1318
|
+
runtime = _b.sent();
|
|
1319
|
+
// mkdirp would otherwise turn the file into a folder to hold the child
|
|
1320
|
+
return [4 /*yield*/, expect(runtime.driveStore.write('/data/keep.csv/child.csv', 'b')).rejects.toThrow(/an ancestor of the path is a file/)];
|
|
1321
|
+
case 2:
|
|
1322
|
+
// mkdirp would otherwise turn the file into a folder to hold the child
|
|
1323
|
+
_b.sent();
|
|
1324
|
+
_a = expect;
|
|
1325
|
+
return [4 /*yield*/, runtime.driveStore.read('/data/keep.csv')];
|
|
1326
|
+
case 3:
|
|
1327
|
+
_a.apply(void 0, [_b.sent()]).toBe('a,b\n1,2\n');
|
|
1328
|
+
return [2 /*return*/];
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
}); });
|
|
1332
|
+
it("refuses a path it does not hold, in the drive's own words", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1333
|
+
var runtime, store;
|
|
1334
|
+
return __generator(this, function (_a) {
|
|
1335
|
+
switch (_a.label) {
|
|
1336
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1337
|
+
case 1:
|
|
1338
|
+
runtime = _a.sent();
|
|
1339
|
+
store = runtime.driveStore;
|
|
1340
|
+
// the API checks existence before it reads, so these are the store's own
|
|
1341
|
+
// refusals rather than answers a client normally sees
|
|
1342
|
+
return [4 /*yield*/, expect(store.read('/data/missing.csv')).rejects.toThrow("File doesn't exist.")];
|
|
1343
|
+
case 2:
|
|
1344
|
+
// the API checks existence before it reads, so these are the store's own
|
|
1345
|
+
// refusals rather than answers a client normally sees
|
|
1346
|
+
_a.sent();
|
|
1347
|
+
return [4 /*yield*/, expect(store.stat('/data/missing.csv')).rejects.toThrow(/ENOENT/)];
|
|
1348
|
+
case 3:
|
|
1349
|
+
_a.sent();
|
|
1350
|
+
return [4 /*yield*/, expect(store.move('/data/missing.csv', '/data/x.csv')).rejects.toThrow(/no such folder/)];
|
|
1351
|
+
case 4:
|
|
1352
|
+
_a.sent();
|
|
1353
|
+
return [2 /*return*/];
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
}); });
|
|
1357
|
+
it('refuses a path that climbs out of the drive, whatever the caller', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1358
|
+
var runtime, store, _a;
|
|
1359
|
+
return __generator(this, function (_b) {
|
|
1360
|
+
switch (_b.label) {
|
|
1361
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1362
|
+
case 1:
|
|
1363
|
+
runtime = _b.sent();
|
|
1364
|
+
store = runtime.driveStore;
|
|
1365
|
+
// the API refuses these first, with the wording a client sees; this is the
|
|
1366
|
+
// storage boundary refusing on its own, because the store is handed out and
|
|
1367
|
+
// a caller that skips the package must not be able to leave the drive
|
|
1368
|
+
return [4 /*yield*/, expect(store.read('/../escape.csv')).rejects.toThrow(/Can't reach a path outside the drive/)];
|
|
1369
|
+
case 2:
|
|
1370
|
+
// the API refuses these first, with the wording a client sees; this is the
|
|
1371
|
+
// storage boundary refusing on its own, because the store is handed out and
|
|
1372
|
+
// a caller that skips the package must not be able to leave the drive
|
|
1373
|
+
_b.sent();
|
|
1374
|
+
return [4 /*yield*/, expect(store.write('/data/../../escape.csv', 'x')).rejects.toThrow(/Can't reach a path outside the drive/)];
|
|
1375
|
+
case 3:
|
|
1376
|
+
_b.sent();
|
|
1377
|
+
return [4 /*yield*/, expect(store.list('/..')).rejects.toThrow(/Can't reach a path outside the drive/)];
|
|
1378
|
+
case 4:
|
|
1379
|
+
_b.sent();
|
|
1380
|
+
_a = expect;
|
|
1381
|
+
return [4 /*yield*/, store.exists('/data/keep.csv')];
|
|
1382
|
+
case 5:
|
|
1383
|
+
_a.apply(void 0, [_b.sent()]).toBe(true);
|
|
1384
|
+
return [2 /*return*/];
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
}); });
|
|
1388
|
+
it('counts the root as a folder, and its children as the listing', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1389
|
+
var runtime, _a, _b;
|
|
1390
|
+
return __generator(this, function (_c) {
|
|
1391
|
+
switch (_c.label) {
|
|
1392
|
+
case 0: return [4 /*yield*/, seededRuntime()];
|
|
1393
|
+
case 1:
|
|
1394
|
+
runtime = _c.sent();
|
|
1395
|
+
_a = expect;
|
|
1396
|
+
return [4 /*yield*/, runtime.driveStore.stat('')];
|
|
1397
|
+
case 2:
|
|
1398
|
+
_a.apply(void 0, [_c.sent()]).toEqual({
|
|
1399
|
+
directory: true,
|
|
1400
|
+
size: 0
|
|
1401
|
+
});
|
|
1402
|
+
// the seed's own folder sits under the root too, alongside the app loc the
|
|
1403
|
+
// host creates at boot, so the listing is the drive as it stands
|
|
1404
|
+
_b = expect;
|
|
1405
|
+
return [4 /*yield*/, runtime.driveStore.list('')];
|
|
1406
|
+
case 3:
|
|
1407
|
+
// the seed's own folder sits under the root too, alongside the app loc the
|
|
1408
|
+
// host creates at boot, so the listing is the drive as it stands
|
|
1409
|
+
_b.apply(void 0, [_c.sent()]).toEqual({
|
|
1410
|
+
files: [],
|
|
1411
|
+
folders: ['Public', 'data']
|
|
1412
|
+
});
|
|
1413
|
+
return [2 /*return*/];
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
}); });
|
|
1417
|
+
});
|
|
952
1418
|
});
|