@sasjs/cli 6.30.1 → 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.
- package/build/commands/build/internal/mockSite/assets/api-console.css +365 -0
- package/build/commands/build/internal/mockSite/assets/api-console.js +762 -0
- package/build/commands/build/internal/mockSite/assets/api-index.html +83 -0
- package/build/commands/build/internal/mockSite/assets/sw.js +28 -4
- package/build/commands/build/internal/mockSite/mockSite.d.ts +2 -0
- package/build/commands/build/internal/mockSite/mockSite.js +80 -8
- package/build/commands/build/internal/mockSite/spec/apiConsole.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/apiConsole.spec.js +1371 -0
- package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.d.ts +1 -0
- package/build/commands/build/internal/mockSite/spec/apiConsoleBasePath.spec.js +303 -0
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +86 -0
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +99 -49
- package/build/commands/version/spec/version.spec.js +11 -1
- package/build/commands/version/version.d.ts +5 -3
- package/build/commands/version/version.js +17 -7
- package/build/types/mockWebBasePath.d.ts +9 -0
- package/package.json +13 -32
|
@@ -0,0 +1,1371 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
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
|
+
};
|
|
63
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
64
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
65
|
+
};
|
|
66
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
67
|
+
/**
|
|
68
|
+
* @jest-environment jsdom
|
|
69
|
+
*/
|
|
70
|
+
var fs_1 = __importDefault(require("fs"));
|
|
71
|
+
var path_1 = __importDefault(require("path"));
|
|
72
|
+
/**
|
|
73
|
+
* The API console, as a page.
|
|
74
|
+
*
|
|
75
|
+
* The build's own specs prove the files are written and that the operations come
|
|
76
|
+
* from the route table; they cannot prove the page works. This one drives it in a
|
|
77
|
+
* DOM: it loads the real `api-index.html` (so a rename of an id fails here rather
|
|
78
|
+
* than in a browser), stubs the two files the page reads and the host bridge, and
|
|
79
|
+
* asserts the request a form sends - including the bearer token, which is the
|
|
80
|
+
* part that has to survive the reload `as()` performs.
|
|
81
|
+
*
|
|
82
|
+
* The service worker is deliberately absent: jsdom has none, and the page has to
|
|
83
|
+
* render without it (a site whose `host/persist.js` cannot load must still show
|
|
84
|
+
* the route list).
|
|
85
|
+
*/
|
|
86
|
+
var page = function () {
|
|
87
|
+
return fs_1.default.readFileSync(path_1.default.join(__dirname, '..', 'assets', 'api-index.html'), 'utf8');
|
|
88
|
+
};
|
|
89
|
+
var bodyOf = function (html) {
|
|
90
|
+
var start = html.indexOf('<body>');
|
|
91
|
+
var end = html.indexOf('</body>');
|
|
92
|
+
return start < 0 || end < 0 ? html : html.slice(start + 6, end);
|
|
93
|
+
};
|
|
94
|
+
var operations = [
|
|
95
|
+
{
|
|
96
|
+
id: 'POST /SASjsApi/user',
|
|
97
|
+
method: 'POST',
|
|
98
|
+
path: '/SASjsApi/user',
|
|
99
|
+
tag: 'User',
|
|
100
|
+
summary: 'Create a user',
|
|
101
|
+
params: [
|
|
102
|
+
{ name: 'username', in: 'body', required: true, type: 'string' },
|
|
103
|
+
{ name: 'password', in: 'body', required: true, type: 'string' },
|
|
104
|
+
{ name: 'isAdmin', in: 'body', required: false, type: 'boolean' }
|
|
105
|
+
],
|
|
106
|
+
requestBodies: ['application/json'],
|
|
107
|
+
responses: [{ status: 200, description: 'Ok' }],
|
|
108
|
+
implemented: true
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'GET /SASjsApi/user',
|
|
112
|
+
method: 'GET',
|
|
113
|
+
path: '/SASjsApi/user',
|
|
114
|
+
tag: 'User',
|
|
115
|
+
summary: 'List all users',
|
|
116
|
+
params: [],
|
|
117
|
+
requestBodies: [],
|
|
118
|
+
responses: [{ status: 200, description: 'Ok' }],
|
|
119
|
+
implemented: true
|
|
120
|
+
}
|
|
121
|
+
];
|
|
122
|
+
var fixture = {
|
|
123
|
+
format: 1,
|
|
124
|
+
users: [
|
|
125
|
+
{
|
|
126
|
+
uid: 'user-admin',
|
|
127
|
+
username: 'sasjsadmin',
|
|
128
|
+
password: 'Orion123',
|
|
129
|
+
isAdmin: true
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
uid: 'user-restricted',
|
|
133
|
+
username: 'sasjsuser',
|
|
134
|
+
password: 'Orion123',
|
|
135
|
+
isAdmin: false
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
var jsonResponse = function (body) { return ({
|
|
140
|
+
ok: true,
|
|
141
|
+
status: 200,
|
|
142
|
+
statusText: 'OK',
|
|
143
|
+
headers: { get: function () { return 'application/json'; } },
|
|
144
|
+
json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
145
|
+
return [2 /*return*/, body];
|
|
146
|
+
}); }); },
|
|
147
|
+
text: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
148
|
+
return [2 /*return*/, JSON.stringify(body)];
|
|
149
|
+
}); }); }
|
|
150
|
+
}); };
|
|
151
|
+
/** A service worker the page can register, and drive by hand. */
|
|
152
|
+
var withServiceWorker = function (options) {
|
|
153
|
+
if (options === void 0) { options = {}; }
|
|
154
|
+
var listeners = [];
|
|
155
|
+
var sw = {
|
|
156
|
+
controller: null,
|
|
157
|
+
register: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
158
|
+
return __generator(this, function (_a) {
|
|
159
|
+
if (options.registerFails)
|
|
160
|
+
throw new Error('the browser refused');
|
|
161
|
+
return [2 /*return*/, {}];
|
|
162
|
+
});
|
|
163
|
+
}); }),
|
|
164
|
+
addEventListener: function (type, fn) {
|
|
165
|
+
if (type === 'controllerchange')
|
|
166
|
+
listeners.push(fn);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
Object.defineProperty(navigator, 'serviceWorker', {
|
|
170
|
+
value: sw,
|
|
171
|
+
configurable: true
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
sw: sw,
|
|
175
|
+
takeControl: function () {
|
|
176
|
+
sw.controller = {};
|
|
177
|
+
listeners.forEach(function (fn) { return fn(); });
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Records the scripts the page asks for, and answers each one.
|
|
183
|
+
*
|
|
184
|
+
* jsdom does not fetch a script it is given, so the load event is dispatched
|
|
185
|
+
* here - otherwise the page would wait forever for `persist.js`.
|
|
186
|
+
*/
|
|
187
|
+
var stubScripts = function () {
|
|
188
|
+
var realCreate = document.createElement.bind(document);
|
|
189
|
+
var requested = [];
|
|
190
|
+
jest.spyOn(document, 'createElement').mockImplementation(function (tag) {
|
|
191
|
+
var node = realCreate(tag);
|
|
192
|
+
if (String(tag).toLowerCase() === 'script') {
|
|
193
|
+
setTimeout(function () {
|
|
194
|
+
requested.push(node.src);
|
|
195
|
+
node.dispatchEvent(new Event('load'));
|
|
196
|
+
}, 0);
|
|
197
|
+
}
|
|
198
|
+
return node;
|
|
199
|
+
});
|
|
200
|
+
return { requested: requested };
|
|
201
|
+
};
|
|
202
|
+
/** Boots the page against a stubbed host and returns what it asked for. */
|
|
203
|
+
/** Waits for the render an assertion depends on, rather than for a wall clock. */
|
|
204
|
+
var settle = function (check) { return __awaiter(void 0, void 0, void 0, function () {
|
|
205
|
+
var i;
|
|
206
|
+
return __generator(this, function (_a) {
|
|
207
|
+
switch (_a.label) {
|
|
208
|
+
case 0:
|
|
209
|
+
i = 0;
|
|
210
|
+
_a.label = 1;
|
|
211
|
+
case 1:
|
|
212
|
+
if (!(i < 400)) return [3 /*break*/, 4];
|
|
213
|
+
if (check())
|
|
214
|
+
return [2 /*return*/];
|
|
215
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
216
|
+
case 2:
|
|
217
|
+
_a.sent();
|
|
218
|
+
_a.label = 3;
|
|
219
|
+
case 3:
|
|
220
|
+
i++;
|
|
221
|
+
return [3 /*break*/, 1];
|
|
222
|
+
case 4: throw new Error('the page did not reach the state the case expected');
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}); };
|
|
226
|
+
var loadConsole = function () {
|
|
227
|
+
var args_1 = [];
|
|
228
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
229
|
+
args_1[_i] = arguments[_i];
|
|
230
|
+
}
|
|
231
|
+
return __awaiter(void 0, __spreadArray([], __read(args_1), false), void 0, function (before, table, identity) {
|
|
232
|
+
var calls, i;
|
|
233
|
+
if (before === void 0) { before = function () { }; }
|
|
234
|
+
if (table === void 0) { table = operations; }
|
|
235
|
+
if (identity === void 0) { identity = fixture; }
|
|
236
|
+
return __generator(this, function (_a) {
|
|
237
|
+
switch (_a.label) {
|
|
238
|
+
case 0:
|
|
239
|
+
jest.resetModules();
|
|
240
|
+
document.body.innerHTML = bodyOf(page());
|
|
241
|
+
// the version lives in the head, so the cases that care about it set it there
|
|
242
|
+
document.head.innerHTML = '';
|
|
243
|
+
before();
|
|
244
|
+
calls = [];
|
|
245
|
+
global.fetch = jest.fn(function (url_1) {
|
|
246
|
+
var args_2 = [];
|
|
247
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
248
|
+
args_2[_i - 1] = arguments[_i];
|
|
249
|
+
}
|
|
250
|
+
return __awaiter(void 0, __spreadArray([url_1], __read(args_2), false), void 0, function (url, init) {
|
|
251
|
+
if (init === void 0) { init = {}; }
|
|
252
|
+
return __generator(this, function (_a) {
|
|
253
|
+
if (String(url).includes('operations.json'))
|
|
254
|
+
return [2 /*return*/, jsonResponse(table)];
|
|
255
|
+
if (String(url).includes('identity.json'))
|
|
256
|
+
return [2 /*return*/, jsonResponse(identity)];
|
|
257
|
+
calls.push({ url: String(url), init: init });
|
|
258
|
+
return [2 /*return*/, jsonResponse({ created: true })];
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
require('../assets/api-console.js');
|
|
263
|
+
i = 0;
|
|
264
|
+
_a.label = 1;
|
|
265
|
+
case 1:
|
|
266
|
+
if (!(i < 200)) return [3 /*break*/, 4];
|
|
267
|
+
if (document.querySelectorAll('#routes .route').length)
|
|
268
|
+
return [3 /*break*/, 4];
|
|
269
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
270
|
+
case 2:
|
|
271
|
+
_a.sent();
|
|
272
|
+
_a.label = 3;
|
|
273
|
+
case 3:
|
|
274
|
+
i++;
|
|
275
|
+
return [3 /*break*/, 1];
|
|
276
|
+
case 4: return [2 /*return*/, { calls: calls }];
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
var selectRoute = function (id) { return __awaiter(void 0, void 0, void 0, function () {
|
|
282
|
+
var button;
|
|
283
|
+
return __generator(this, function (_a) {
|
|
284
|
+
switch (_a.label) {
|
|
285
|
+
case 0:
|
|
286
|
+
button = __spreadArray([], __read(document.querySelectorAll('#routes .route')), false).find(function (candidate) { return candidate.dataset.id === id; });
|
|
287
|
+
button.click();
|
|
288
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
289
|
+
case 1:
|
|
290
|
+
_a.sent();
|
|
291
|
+
return [2 /*return*/];
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}); };
|
|
295
|
+
var fill = function (name, value) {
|
|
296
|
+
var input = document.querySelector("#form [data-name=\"".concat(name, "\"]"));
|
|
297
|
+
input.value = value;
|
|
298
|
+
};
|
|
299
|
+
var sendAndWait = function (calls_1) {
|
|
300
|
+
var args_1 = [];
|
|
301
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
302
|
+
args_1[_i - 1] = arguments[_i];
|
|
303
|
+
}
|
|
304
|
+
return __awaiter(void 0, __spreadArray([calls_1], __read(args_1), false), void 0, function (calls, want) {
|
|
305
|
+
var i;
|
|
306
|
+
if (want === void 0) { want = 1; }
|
|
307
|
+
return __generator(this, function (_a) {
|
|
308
|
+
switch (_a.label) {
|
|
309
|
+
case 0:
|
|
310
|
+
;
|
|
311
|
+
document.getElementById('send').click();
|
|
312
|
+
i = 0;
|
|
313
|
+
_a.label = 1;
|
|
314
|
+
case 1:
|
|
315
|
+
if (!(i < 200)) return [3 /*break*/, 4];
|
|
316
|
+
if (calls.length >= want)
|
|
317
|
+
return [2 /*return*/];
|
|
318
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
319
|
+
case 2:
|
|
320
|
+
_a.sent();
|
|
321
|
+
_a.label = 3;
|
|
322
|
+
case 3:
|
|
323
|
+
i++;
|
|
324
|
+
return [3 /*break*/, 1];
|
|
325
|
+
case 4: return [2 /*return*/];
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
describe('the API console', function () {
|
|
331
|
+
afterEach(function () {
|
|
332
|
+
delete navigator.serviceWorker;
|
|
333
|
+
delete window.__sasjsMockReady;
|
|
334
|
+
});
|
|
335
|
+
beforeEach(function () {
|
|
336
|
+
// jsdom has no service worker, and the page says so on the console; that is
|
|
337
|
+
// the path under test, not a warning worth printing
|
|
338
|
+
jest.spyOn(console, 'warn').mockImplementation(function () { });
|
|
339
|
+
jest.spyOn(console, 'error').mockImplementation(function () { });
|
|
340
|
+
});
|
|
341
|
+
afterEach(function () {
|
|
342
|
+
jest.restoreAllMocks();
|
|
343
|
+
delete global.fetch;
|
|
344
|
+
delete global.SASjsMock;
|
|
345
|
+
sessionStorage.clear();
|
|
346
|
+
});
|
|
347
|
+
it('lists the operations it was built with, grouped by tag', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
348
|
+
var routes;
|
|
349
|
+
return __generator(this, function (_a) {
|
|
350
|
+
switch (_a.label) {
|
|
351
|
+
case 0: return [4 /*yield*/, loadConsole()];
|
|
352
|
+
case 1:
|
|
353
|
+
_a.sent();
|
|
354
|
+
routes = __spreadArray([], __read(document.querySelectorAll('#routes .route')), false);
|
|
355
|
+
expect(routes).toHaveLength(2);
|
|
356
|
+
expect(document.querySelector('#routes h3').textContent).toBe('User');
|
|
357
|
+
return [2 /*return*/];
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}); });
|
|
361
|
+
it('sends the route it was given, with the parameters typed into the form', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
362
|
+
var calls, i;
|
|
363
|
+
return __generator(this, function (_a) {
|
|
364
|
+
switch (_a.label) {
|
|
365
|
+
case 0: return [4 /*yield*/, loadConsole()];
|
|
366
|
+
case 1:
|
|
367
|
+
calls = (_a.sent()).calls;
|
|
368
|
+
return [4 /*yield*/, selectRoute('POST /SASjsApi/user')];
|
|
369
|
+
case 2:
|
|
370
|
+
_a.sent();
|
|
371
|
+
fill('username', 'demo_reviewer');
|
|
372
|
+
fill('password', 'Orion123');
|
|
373
|
+
return [4 /*yield*/, sendAndWait(calls)];
|
|
374
|
+
case 3:
|
|
375
|
+
_a.sent();
|
|
376
|
+
expect(calls[0].url).toMatch(/\/SASjsApi\/user$/);
|
|
377
|
+
expect(calls[0].init.method).toBe('POST');
|
|
378
|
+
// an untouched optional boolean is absent: the API reads `false` as a value
|
|
379
|
+
// it must act on, so sending it would change what was not asked for
|
|
380
|
+
expect(JSON.parse(calls[0].init.body)).toEqual({
|
|
381
|
+
username: 'demo_reviewer',
|
|
382
|
+
password: 'Orion123'
|
|
383
|
+
});
|
|
384
|
+
expect(calls[0].init.headers.authorization).toBeUndefined();
|
|
385
|
+
i = 0;
|
|
386
|
+
_a.label = 4;
|
|
387
|
+
case 4:
|
|
388
|
+
if (!(i < 200)) return [3 /*break*/, 7];
|
|
389
|
+
if ((document.getElementById('status').textContent || '').includes('200'))
|
|
390
|
+
return [3 /*break*/, 7];
|
|
391
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
392
|
+
case 5:
|
|
393
|
+
_a.sent();
|
|
394
|
+
_a.label = 6;
|
|
395
|
+
case 6:
|
|
396
|
+
i++;
|
|
397
|
+
return [3 /*break*/, 4];
|
|
398
|
+
case 7:
|
|
399
|
+
expect(document.getElementById('status').textContent).toContain('200');
|
|
400
|
+
return [2 /*return*/];
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}); });
|
|
404
|
+
it('sends the token the host issued for the account it is acting as', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
405
|
+
var calls;
|
|
406
|
+
return __generator(this, function (_a) {
|
|
407
|
+
switch (_a.label) {
|
|
408
|
+
case 0:
|
|
409
|
+
// `as()` reloads the page, so the console reads the pair it stored before the
|
|
410
|
+
// navigation: a token held only in memory would be gone by the time the form
|
|
411
|
+
// is usable again
|
|
412
|
+
sessionStorage.setItem('mock-console-token', 'token-for-sasjsuser');
|
|
413
|
+
return [4 /*yield*/, loadConsole()];
|
|
414
|
+
case 1:
|
|
415
|
+
calls = (_a.sent()).calls;
|
|
416
|
+
return [4 /*yield*/, selectRoute('GET /SASjsApi/user')];
|
|
417
|
+
case 2:
|
|
418
|
+
_a.sent();
|
|
419
|
+
return [4 /*yield*/, sendAndWait(calls)];
|
|
420
|
+
case 3:
|
|
421
|
+
_a.sent();
|
|
422
|
+
expect(calls[0].url).toMatch(/\/SASjsApi\/user$/);
|
|
423
|
+
expect(calls[0].init.headers.authorization).toBe('Bearer token-for-sasjsuser');
|
|
424
|
+
return [2 /*return*/];
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}); });
|
|
428
|
+
it('filters the list, and says so when nothing matches', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
429
|
+
var filter;
|
|
430
|
+
return __generator(this, function (_a) {
|
|
431
|
+
switch (_a.label) {
|
|
432
|
+
case 0: return [4 /*yield*/, loadConsole()];
|
|
433
|
+
case 1:
|
|
434
|
+
_a.sent();
|
|
435
|
+
filter = document.getElementById('filter');
|
|
436
|
+
filter.value = 'group';
|
|
437
|
+
filter.dispatchEvent(new Event('input'));
|
|
438
|
+
expect(document.querySelectorAll('#routes .route')).toHaveLength(0);
|
|
439
|
+
expect(document.getElementById('routes').textContent).toContain('No route matches that.');
|
|
440
|
+
filter.value = 'user';
|
|
441
|
+
filter.dispatchEvent(new Event('input'));
|
|
442
|
+
expect(document.querySelectorAll('#routes .route')).toHaveLength(2);
|
|
443
|
+
return [2 /*return*/];
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}); });
|
|
447
|
+
it('marks a route the host does not implement, and one that takes an upload', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
448
|
+
var extra, i;
|
|
449
|
+
return __generator(this, function (_a) {
|
|
450
|
+
switch (_a.label) {
|
|
451
|
+
case 0:
|
|
452
|
+
extra = [
|
|
453
|
+
{
|
|
454
|
+
id: 'POST /SASjsApi/drive/deploy/upload',
|
|
455
|
+
method: 'POST',
|
|
456
|
+
path: '/SASjsApi/drive/deploy/upload',
|
|
457
|
+
tag: 'Drive',
|
|
458
|
+
summary: 'Upload a deployment',
|
|
459
|
+
params: [],
|
|
460
|
+
requestBodies: ['multipart/form-data'],
|
|
461
|
+
responses: [],
|
|
462
|
+
implemented: true
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: 'GET /SASjsApi/session/{sessionId}/log',
|
|
466
|
+
method: 'GET',
|
|
467
|
+
path: '/SASjsApi/session/{sessionId}/log',
|
|
468
|
+
tag: 'Session',
|
|
469
|
+
summary: 'A session log',
|
|
470
|
+
params: [
|
|
471
|
+
{ name: 'sessionId', in: 'path', required: true, type: 'string' }
|
|
472
|
+
],
|
|
473
|
+
requestBodies: [],
|
|
474
|
+
responses: [],
|
|
475
|
+
implemented: false,
|
|
476
|
+
reason: 'this host keeps no log file'
|
|
477
|
+
}
|
|
478
|
+
];
|
|
479
|
+
jest.resetModules();
|
|
480
|
+
document.body.innerHTML = bodyOf(page());
|
|
481
|
+
global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
|
|
482
|
+
return __generator(this, function (_a) {
|
|
483
|
+
return [2 /*return*/, String(url).includes('operations.json')
|
|
484
|
+
? jsonResponse(extra)
|
|
485
|
+
: jsonResponse(fixture)];
|
|
486
|
+
});
|
|
487
|
+
}); });
|
|
488
|
+
require('../assets/api-console.js');
|
|
489
|
+
i = 0;
|
|
490
|
+
_a.label = 1;
|
|
491
|
+
case 1:
|
|
492
|
+
if (!(i < 200)) return [3 /*break*/, 4];
|
|
493
|
+
if (document.querySelectorAll('#routes .route').length)
|
|
494
|
+
return [3 /*break*/, 4];
|
|
495
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
496
|
+
case 2:
|
|
497
|
+
_a.sent();
|
|
498
|
+
_a.label = 3;
|
|
499
|
+
case 3:
|
|
500
|
+
i++;
|
|
501
|
+
return [3 /*break*/, 1];
|
|
502
|
+
case 4:
|
|
503
|
+
;
|
|
504
|
+
global.fetch = jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
505
|
+
return [2 /*return*/, jsonResponse({})];
|
|
506
|
+
}); }); });
|
|
507
|
+
return [4 /*yield*/, selectRoute('POST /SASjsApi/drive/deploy/upload')];
|
|
508
|
+
case 5:
|
|
509
|
+
_a.sent();
|
|
510
|
+
expect(document.getElementById('form').textContent).toContain('does not send');
|
|
511
|
+
return [4 /*yield*/, selectRoute('GET /SASjsApi/session/{sessionId}/log')];
|
|
512
|
+
case 6:
|
|
513
|
+
_a.sent();
|
|
514
|
+
expect(document.getElementById('summary').textContent).toContain('does not implement');
|
|
515
|
+
// a path parameter is rendered as a field, and the URL it builds carries it
|
|
516
|
+
expect(document.querySelector('#form [data-name="sessionId"]')).not.toBeNull();
|
|
517
|
+
return [2 /*return*/];
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}); });
|
|
521
|
+
it('reports a body that is not the JSON it claims to be, without sending it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
522
|
+
var calls, operations, field;
|
|
523
|
+
return __generator(this, function (_a) {
|
|
524
|
+
switch (_a.label) {
|
|
525
|
+
case 0: return [4 /*yield*/, loadConsole()
|
|
526
|
+
// a parameter the table describes as an object is sent as typed
|
|
527
|
+
];
|
|
528
|
+
case 1:
|
|
529
|
+
calls = (_a.sent()).calls;
|
|
530
|
+
operations = __spreadArray([], __read(document.querySelectorAll('#routes .route')), false);
|
|
531
|
+
expect(operations.length).toBeGreaterThan(0);
|
|
532
|
+
return [4 /*yield*/, selectRoute('POST /SASjsApi/user')
|
|
533
|
+
// the user route's fields are strings, so drive the object branch directly:
|
|
534
|
+
// an array parameter is the same code path
|
|
535
|
+
];
|
|
536
|
+
case 2:
|
|
537
|
+
_a.sent();
|
|
538
|
+
field = document.querySelector('#form [data-name="username"]');
|
|
539
|
+
field.dataset.type = 'object';
|
|
540
|
+
field.value = '{not json';
|
|
541
|
+
document.getElementById('send').click();
|
|
542
|
+
return [4 /*yield*/, settle(function () {
|
|
543
|
+
return /must be JSON/.test(document.getElementById('body').textContent || '');
|
|
544
|
+
})];
|
|
545
|
+
case 3:
|
|
546
|
+
_a.sent();
|
|
547
|
+
expect(calls).toHaveLength(0);
|
|
548
|
+
expect(document.getElementById('body').textContent).toContain('must be JSON');
|
|
549
|
+
return [2 /*return*/];
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
}); });
|
|
553
|
+
it('shows a non-JSON answer as it came, and an empty one as empty', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
554
|
+
var calls;
|
|
555
|
+
return __generator(this, function (_a) {
|
|
556
|
+
switch (_a.label) {
|
|
557
|
+
case 0: return [4 /*yield*/, loadConsole()];
|
|
558
|
+
case 1:
|
|
559
|
+
calls = (_a.sent()).calls;
|
|
560
|
+
return [4 /*yield*/, selectRoute('GET /SASjsApi/user')];
|
|
561
|
+
case 2:
|
|
562
|
+
_a.sent();
|
|
563
|
+
global.fetch = jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
564
|
+
return __generator(this, function (_a) {
|
|
565
|
+
return [2 /*return*/, ({
|
|
566
|
+
ok: true,
|
|
567
|
+
status: 200,
|
|
568
|
+
statusText: 'OK',
|
|
569
|
+
headers: { get: function () { return 'text/plain'; } },
|
|
570
|
+
text: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
571
|
+
return [2 /*return*/, 'NOTE: hello'];
|
|
572
|
+
}); }); }
|
|
573
|
+
})];
|
|
574
|
+
});
|
|
575
|
+
}); });
|
|
576
|
+
return [4 /*yield*/, sendAndWait(calls)];
|
|
577
|
+
case 3:
|
|
578
|
+
_a.sent();
|
|
579
|
+
return [4 /*yield*/, settle(function () { return document.getElementById('body').textContent === 'NOTE: hello'; })];
|
|
580
|
+
case 4:
|
|
581
|
+
_a.sent();
|
|
582
|
+
expect(document.getElementById('body').textContent).toBe('NOTE: hello');
|
|
583
|
+
global.fetch = jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
584
|
+
return __generator(this, function (_a) {
|
|
585
|
+
return [2 /*return*/, ({
|
|
586
|
+
ok: true,
|
|
587
|
+
status: 200,
|
|
588
|
+
statusText: 'OK',
|
|
589
|
+
headers: { get: function () { return 'application/json'; } },
|
|
590
|
+
text: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
591
|
+
return [2 /*return*/, ''];
|
|
592
|
+
}); }); }
|
|
593
|
+
})];
|
|
594
|
+
});
|
|
595
|
+
}); });
|
|
596
|
+
document.getElementById('send').click();
|
|
597
|
+
return [4 /*yield*/, settle(function () { return document.getElementById('body').textContent === '(empty body)'; })];
|
|
598
|
+
case 5:
|
|
599
|
+
_a.sent();
|
|
600
|
+
expect(document.getElementById('body').textContent).toBe('(empty body)');
|
|
601
|
+
return [2 /*return*/];
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
}); });
|
|
605
|
+
it('reports a request that never reached the host', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
606
|
+
return __generator(this, function (_a) {
|
|
607
|
+
switch (_a.label) {
|
|
608
|
+
case 0: return [4 /*yield*/, loadConsole()];
|
|
609
|
+
case 1:
|
|
610
|
+
_a.sent();
|
|
611
|
+
return [4 /*yield*/, selectRoute('GET /SASjsApi/user')];
|
|
612
|
+
case 2:
|
|
613
|
+
_a.sent();
|
|
614
|
+
global.fetch = jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
615
|
+
return __generator(this, function (_a) {
|
|
616
|
+
throw new Error('the network is gone');
|
|
617
|
+
});
|
|
618
|
+
}); });
|
|
619
|
+
document.getElementById('send').click();
|
|
620
|
+
return [4 /*yield*/, settle(function () { return document.getElementById('status').textContent === 'request failed'; })];
|
|
621
|
+
case 3:
|
|
622
|
+
_a.sent();
|
|
623
|
+
expect(document.getElementById('status').textContent).toBe('request failed');
|
|
624
|
+
expect(document.getElementById('body').textContent).toContain('the network is gone');
|
|
625
|
+
return [2 /*return*/];
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}); });
|
|
629
|
+
it('copies the answer, and survives a clipboard that refuses', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
630
|
+
var written;
|
|
631
|
+
return __generator(this, function (_a) {
|
|
632
|
+
switch (_a.label) {
|
|
633
|
+
case 0: return [4 /*yield*/, loadConsole()];
|
|
634
|
+
case 1:
|
|
635
|
+
_a.sent();
|
|
636
|
+
return [4 /*yield*/, selectRoute('GET /SASjsApi/user')];
|
|
637
|
+
case 2:
|
|
638
|
+
_a.sent();
|
|
639
|
+
written = [];
|
|
640
|
+
navigator.clipboard = {
|
|
641
|
+
writeText: function (text) { return __awaiter(void 0, void 0, void 0, function () {
|
|
642
|
+
return __generator(this, function (_a) {
|
|
643
|
+
written.push(text);
|
|
644
|
+
return [2 /*return*/];
|
|
645
|
+
});
|
|
646
|
+
}); }
|
|
647
|
+
};
|
|
648
|
+
document.getElementById('body').textContent = '{"a":1}';
|
|
649
|
+
document.getElementById('copy').click();
|
|
650
|
+
return [4 /*yield*/, settle(function () { return written.length === 1; })];
|
|
651
|
+
case 3:
|
|
652
|
+
_a.sent();
|
|
653
|
+
expect(written).toEqual(['{"a":1}']);
|
|
654
|
+
expect(document.getElementById('copy').textContent).toBe('Copied');
|
|
655
|
+
navigator.clipboard = {
|
|
656
|
+
writeText: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
657
|
+
return __generator(this, function (_a) {
|
|
658
|
+
throw new Error('denied');
|
|
659
|
+
});
|
|
660
|
+
}); }
|
|
661
|
+
};
|
|
662
|
+
document.getElementById('copy').click();
|
|
663
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 20); })];
|
|
664
|
+
case 4:
|
|
665
|
+
_a.sent();
|
|
666
|
+
expect(document.getElementById('copy').textContent).toBe('Copied');
|
|
667
|
+
return [2 /*return*/];
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
}); });
|
|
671
|
+
it('selects the route named in the URL, so a reload comes back to it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
672
|
+
var i;
|
|
673
|
+
return __generator(this, function (_a) {
|
|
674
|
+
switch (_a.label) {
|
|
675
|
+
case 0:
|
|
676
|
+
jest.resetModules();
|
|
677
|
+
document.body.innerHTML = bodyOf(page());
|
|
678
|
+
history.replaceState({}, '', '/api/?route=' + encodeURIComponent('GET /SASjsApi/user'));
|
|
679
|
+
global.fetch = jest.fn(function (url) { return __awaiter(void 0, void 0, void 0, function () {
|
|
680
|
+
return __generator(this, function (_a) {
|
|
681
|
+
return [2 /*return*/, String(url).includes('operations.json')
|
|
682
|
+
? jsonResponse(operations)
|
|
683
|
+
: jsonResponse(fixture)];
|
|
684
|
+
});
|
|
685
|
+
}); });
|
|
686
|
+
require('../assets/api-console.js');
|
|
687
|
+
i = 0;
|
|
688
|
+
_a.label = 1;
|
|
689
|
+
case 1:
|
|
690
|
+
if (!(i < 200)) return [3 /*break*/, 4];
|
|
691
|
+
if (document.getElementById('title').textContent)
|
|
692
|
+
return [3 /*break*/, 4];
|
|
693
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
694
|
+
case 2:
|
|
695
|
+
_a.sent();
|
|
696
|
+
_a.label = 3;
|
|
697
|
+
case 3:
|
|
698
|
+
i++;
|
|
699
|
+
return [3 /*break*/, 1];
|
|
700
|
+
case 4:
|
|
701
|
+
expect(document.getElementById('title').textContent).toBe('GET /SASjsApi/user');
|
|
702
|
+
history.replaceState({}, '', '/api/');
|
|
703
|
+
return [2 /*return*/];
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
}); });
|
|
707
|
+
it('stores the token acting as another account returns, and sends it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
708
|
+
var calls, actAs;
|
|
709
|
+
return __generator(this, function (_a) {
|
|
710
|
+
switch (_a.label) {
|
|
711
|
+
case 0: return [4 /*yield*/, loadConsole(function () {
|
|
712
|
+
;
|
|
713
|
+
window.SASjsMock = {
|
|
714
|
+
identity: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
715
|
+
return [2 /*return*/, ({ fixture: fixture, subject: null })];
|
|
716
|
+
}); }); },
|
|
717
|
+
as: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
718
|
+
return __generator(this, function (_a) {
|
|
719
|
+
return [2 /*return*/, ({
|
|
720
|
+
accessToken: 'token-for-sasjsuser',
|
|
721
|
+
refreshToken: 'refresh-for-sasjsuser'
|
|
722
|
+
})];
|
|
723
|
+
});
|
|
724
|
+
}); }
|
|
725
|
+
};
|
|
726
|
+
})];
|
|
727
|
+
case 1:
|
|
728
|
+
calls = (_a.sent()).calls;
|
|
729
|
+
actAs = __spreadArray([], __read(document.querySelectorAll('#accounts button')), false).find(function (candidate) { return candidate.dataset.user === 'sasjsuser'; });
|
|
730
|
+
expect(actAs).toBeTruthy();
|
|
731
|
+
actAs.click();
|
|
732
|
+
return [4 /*yield*/, settle(function () {
|
|
733
|
+
return sessionStorage.getItem('mock-console-token') === 'token-for-sasjsuser';
|
|
734
|
+
})];
|
|
735
|
+
case 2:
|
|
736
|
+
_a.sent();
|
|
737
|
+
expect(sessionStorage.getItem('mock-console-token')).toBe('token-for-sasjsuser');
|
|
738
|
+
return [4 /*yield*/, selectRoute('GET /SASjsApi/user')];
|
|
739
|
+
case 3:
|
|
740
|
+
_a.sent();
|
|
741
|
+
return [4 /*yield*/, sendAndWait(calls)];
|
|
742
|
+
case 4:
|
|
743
|
+
_a.sent();
|
|
744
|
+
expect(calls[0].init.headers.authorization).toBe('Bearer token-for-sasjsuser');
|
|
745
|
+
return [2 /*return*/];
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
}); });
|
|
749
|
+
it('registers the versioned worker, waits for control, and hands over the drive', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
750
|
+
var requested, host;
|
|
751
|
+
return __generator(this, function (_a) {
|
|
752
|
+
switch (_a.label) {
|
|
753
|
+
case 0:
|
|
754
|
+
requested = stubScripts().requested;
|
|
755
|
+
host = withServiceWorker();
|
|
756
|
+
return [4 /*yield*/, loadConsole(function () {
|
|
757
|
+
document.head.innerHTML =
|
|
758
|
+
'<meta name="sasjs-mock-version" content="1.2.3" />';
|
|
759
|
+
window.__sasjsMockReady = Promise.resolve({ booted: true });
|
|
760
|
+
})];
|
|
761
|
+
case 1:
|
|
762
|
+
_a.sent();
|
|
763
|
+
return [4 /*yield*/, settle(function () { return host.sw.register.mock.calls.length === 1; })
|
|
764
|
+
// the app's loader registers this exact URL: two URLs for one scope replace
|
|
765
|
+
// each other, which is an install nobody asked for
|
|
766
|
+
];
|
|
767
|
+
case 2:
|
|
768
|
+
_a.sent();
|
|
769
|
+
// the app's loader registers this exact URL: two URLs for one scope replace
|
|
770
|
+
// each other, which is an install nobody asked for
|
|
771
|
+
expect(host.sw.register.mock.calls[0][0]).toContain('sw.js?v=1.2.3');
|
|
772
|
+
expect(host.sw.register.mock.calls[0][1]).toEqual(expect.objectContaining({ scope: '/', updateViaCache: 'none' }));
|
|
773
|
+
// a registration completes before the navigation commits, so the page waits
|
|
774
|
+
host.takeControl();
|
|
775
|
+
return [4 /*yield*/, settle(function () { return requested.some(function (url) { return url.includes('host/persist.js'); }); })];
|
|
776
|
+
case 3:
|
|
777
|
+
_a.sent();
|
|
778
|
+
expect(requested.some(function (url) { return url.includes('host/persist.js'); })).toBe(true);
|
|
779
|
+
return [2 /*return*/];
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
}); });
|
|
783
|
+
it('registers the plain worker URL while the page carries the placeholder', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
784
|
+
var host;
|
|
785
|
+
return __generator(this, function (_a) {
|
|
786
|
+
switch (_a.label) {
|
|
787
|
+
case 0:
|
|
788
|
+
host = withServiceWorker();
|
|
789
|
+
// the placeholder is what a hand-copied asset carries: it is not a version
|
|
790
|
+
return [4 /*yield*/, loadConsole(function () {
|
|
791
|
+
document.head.innerHTML =
|
|
792
|
+
'<meta name="sasjs-mock-version" content="__MOCK_VERSION__" />';
|
|
793
|
+
})];
|
|
794
|
+
case 1:
|
|
795
|
+
// the placeholder is what a hand-copied asset carries: it is not a version
|
|
796
|
+
_a.sent();
|
|
797
|
+
return [4 /*yield*/, settle(function () { return host.sw.register.mock.calls.length === 1; })];
|
|
798
|
+
case 2:
|
|
799
|
+
_a.sent();
|
|
800
|
+
expect(host.sw.register.mock.calls[0][0]).not.toContain('?v=');
|
|
801
|
+
// let the page past its wait: a registration nobody claims leaves a 4s timer
|
|
802
|
+
// that fires during whatever test runs next, and its reload throws there
|
|
803
|
+
host.takeControl();
|
|
804
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 20); })];
|
|
805
|
+
case 3:
|
|
806
|
+
_a.sent();
|
|
807
|
+
return [2 /*return*/];
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
}); });
|
|
811
|
+
it('says so when the browser refuses the registration', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
812
|
+
var error, host;
|
|
813
|
+
return __generator(this, function (_a) {
|
|
814
|
+
switch (_a.label) {
|
|
815
|
+
case 0:
|
|
816
|
+
error = jest.spyOn(console, 'error').mockImplementation(function () { });
|
|
817
|
+
host = withServiceWorker({ registerFails: true });
|
|
818
|
+
return [4 /*yield*/, loadConsole()];
|
|
819
|
+
case 1:
|
|
820
|
+
_a.sent();
|
|
821
|
+
return [4 /*yield*/, settle(function () { return error.mock.calls.length > 0; })];
|
|
822
|
+
case 2:
|
|
823
|
+
_a.sent();
|
|
824
|
+
expect(error.mock.calls[0][0]).toContain('could not register');
|
|
825
|
+
expect(host.sw.register).toHaveBeenCalled();
|
|
826
|
+
error.mockRestore();
|
|
827
|
+
return [2 /*return*/];
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
}); });
|
|
831
|
+
it('says so when the drive could not be handed over', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
832
|
+
var error, requested, host;
|
|
833
|
+
return __generator(this, function (_a) {
|
|
834
|
+
switch (_a.label) {
|
|
835
|
+
case 0:
|
|
836
|
+
error = jest.spyOn(console, 'error').mockImplementation(function () { });
|
|
837
|
+
requested = stubScripts().requested;
|
|
838
|
+
host = withServiceWorker();
|
|
839
|
+
return [4 /*yield*/, loadConsole(function () {
|
|
840
|
+
;
|
|
841
|
+
window.__sasjsMockReady = Promise.reject(new Error('no storage in this browser'));
|
|
842
|
+
})];
|
|
843
|
+
case 1:
|
|
844
|
+
_a.sent();
|
|
845
|
+
host.takeControl();
|
|
846
|
+
return [4 /*yield*/, settle(function () {
|
|
847
|
+
return error.mock.calls.some(function (call) { return /handed over/.test(String(call[0])); });
|
|
848
|
+
})];
|
|
849
|
+
case 2:
|
|
850
|
+
_a.sent();
|
|
851
|
+
expect(error.mock.calls.some(function (call) { return /handed over/.test(String(call[0])); })).toBe(true);
|
|
852
|
+
expect(requested.some(function (url) { return url.includes('host/persist.js'); })).toBe(true);
|
|
853
|
+
error.mockRestore();
|
|
854
|
+
return [2 /*return*/];
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
}); });
|
|
858
|
+
it('prefills the sign-in route from the demo administrator', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
859
|
+
var signIn, username, password;
|
|
860
|
+
return __generator(this, function (_a) {
|
|
861
|
+
switch (_a.label) {
|
|
862
|
+
case 0:
|
|
863
|
+
signIn = [
|
|
864
|
+
{
|
|
865
|
+
id: 'POST /SASLogon/login',
|
|
866
|
+
method: 'POST',
|
|
867
|
+
path: '/SASLogon/login',
|
|
868
|
+
tag: 'Auth',
|
|
869
|
+
summary: 'Sign in',
|
|
870
|
+
params: [
|
|
871
|
+
{ name: 'username', in: 'body', required: true, type: 'string' },
|
|
872
|
+
{ name: 'password', in: 'body', required: true, type: 'string' }
|
|
873
|
+
],
|
|
874
|
+
requestBodies: ['application/json'],
|
|
875
|
+
responses: [{ status: 200, description: 'Ok' }],
|
|
876
|
+
implemented: true
|
|
877
|
+
}
|
|
878
|
+
];
|
|
879
|
+
return [4 /*yield*/, loadConsole(function () { }, signIn)];
|
|
880
|
+
case 1:
|
|
881
|
+
_a.sent();
|
|
882
|
+
return [4 /*yield*/, selectRoute('POST /SASLogon/login')
|
|
883
|
+
// the credentials are published on purpose, and this is where a visitor needs
|
|
884
|
+
// them: the route they sign in with
|
|
885
|
+
];
|
|
886
|
+
case 2:
|
|
887
|
+
_a.sent();
|
|
888
|
+
username = document.querySelector('#form [data-name="username"]');
|
|
889
|
+
password = document.querySelector('#form [data-name="password"]');
|
|
890
|
+
expect(username.value).toBe('sasjsadmin');
|
|
891
|
+
expect(password.value).toBe('Orion123');
|
|
892
|
+
return [2 /*return*/];
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
}); });
|
|
896
|
+
it('puts a query parameter in the URL, and leaves it off when empty', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
897
|
+
var withQuery, calls;
|
|
898
|
+
return __generator(this, function (_a) {
|
|
899
|
+
switch (_a.label) {
|
|
900
|
+
case 0:
|
|
901
|
+
withQuery = [
|
|
902
|
+
{
|
|
903
|
+
id: 'GET /SASjsApi/user/list',
|
|
904
|
+
method: 'GET',
|
|
905
|
+
path: '/SASjsApi/user/list',
|
|
906
|
+
tag: 'User',
|
|
907
|
+
summary: 'List with a filter',
|
|
908
|
+
params: [
|
|
909
|
+
{ name: 'search', in: 'query', required: false, type: 'string' }
|
|
910
|
+
],
|
|
911
|
+
requestBodies: [],
|
|
912
|
+
responses: [{ status: 200, description: 'Ok' }],
|
|
913
|
+
implemented: true
|
|
914
|
+
}
|
|
915
|
+
];
|
|
916
|
+
return [4 /*yield*/, loadConsole(function () { }, withQuery)];
|
|
917
|
+
case 1:
|
|
918
|
+
calls = (_a.sent()).calls;
|
|
919
|
+
return [4 /*yield*/, selectRoute('GET /SASjsApi/user/list')];
|
|
920
|
+
case 2:
|
|
921
|
+
_a.sent();
|
|
922
|
+
return [4 /*yield*/, sendAndWait(calls)];
|
|
923
|
+
case 3:
|
|
924
|
+
_a.sent();
|
|
925
|
+
expect(calls[0].url).toMatch(/\/SASjsApi\/user\/list$/);
|
|
926
|
+
// the button is disabled while a request is in flight, and a disabled button
|
|
927
|
+
// ignores a click, so wait for the first answer to land
|
|
928
|
+
return [4 /*yield*/, settle(function () { return !document.getElementById('send').disabled; })];
|
|
929
|
+
case 4:
|
|
930
|
+
// the button is disabled while a request is in flight, and a disabled button
|
|
931
|
+
// ignores a click, so wait for the first answer to land
|
|
932
|
+
_a.sent();
|
|
933
|
+
fill('search', 'demo reviewer');
|
|
934
|
+
return [4 /*yield*/, sendAndWait(calls, 2)];
|
|
935
|
+
case 5:
|
|
936
|
+
_a.sent();
|
|
937
|
+
expect(calls[1].url).toBe('/SASjsApi/user/list?search=demo+reviewer');
|
|
938
|
+
return [2 /*return*/];
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
}); });
|
|
942
|
+
it('says so when the host refuses to act as an account', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
943
|
+
var alert, error, actAs;
|
|
944
|
+
return __generator(this, function (_a) {
|
|
945
|
+
switch (_a.label) {
|
|
946
|
+
case 0:
|
|
947
|
+
alert = jest.spyOn(window, 'alert').mockImplementation(function () { });
|
|
948
|
+
error = jest.spyOn(console, 'error').mockImplementation(function () { });
|
|
949
|
+
return [4 /*yield*/, loadConsole(function () {
|
|
950
|
+
;
|
|
951
|
+
window.SASjsMock = {
|
|
952
|
+
identity: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
953
|
+
return [2 /*return*/, ({ fixture: fixture, subject: null })];
|
|
954
|
+
}); }); },
|
|
955
|
+
as: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
956
|
+
return __generator(this, function (_a) {
|
|
957
|
+
throw new Error('no such account');
|
|
958
|
+
});
|
|
959
|
+
}); }
|
|
960
|
+
};
|
|
961
|
+
})];
|
|
962
|
+
case 1:
|
|
963
|
+
_a.sent();
|
|
964
|
+
actAs = __spreadArray([], __read(document.querySelectorAll('#accounts button')), false).find(function (candidate) { return candidate.dataset.user === 'sasjsuser'; });
|
|
965
|
+
actAs.click();
|
|
966
|
+
return [4 /*yield*/, settle(function () { return alert.mock.calls.length > 0; })];
|
|
967
|
+
case 2:
|
|
968
|
+
_a.sent();
|
|
969
|
+
expect(String(alert.mock.calls[0][0])).toContain('no such account');
|
|
970
|
+
expect(document.getElementById('who').textContent).not.toContain('sasjsuser');
|
|
971
|
+
alert.mockRestore();
|
|
972
|
+
error.mockRestore();
|
|
973
|
+
return [2 /*return*/];
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
}); });
|
|
977
|
+
it('refuses a route whose required field is empty, instead of sending the placeholder', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
978
|
+
var calls;
|
|
979
|
+
return __generator(this, function (_a) {
|
|
980
|
+
switch (_a.label) {
|
|
981
|
+
case 0: return [4 /*yield*/, loadConsole()];
|
|
982
|
+
case 1:
|
|
983
|
+
calls = (_a.sent()).calls;
|
|
984
|
+
return [4 /*yield*/, selectRoute('POST /SASjsApi/user')
|
|
985
|
+
// username is required, and nothing is typed into it
|
|
986
|
+
];
|
|
987
|
+
case 2:
|
|
988
|
+
_a.sent();
|
|
989
|
+
document.getElementById('send').click();
|
|
990
|
+
return [4 /*yield*/, settle(function () { return document.getElementById('status').textContent === 'missing input'; })];
|
|
991
|
+
case 3:
|
|
992
|
+
_a.sent();
|
|
993
|
+
expect(calls).toHaveLength(0);
|
|
994
|
+
expect(document.getElementById('body').textContent).toContain('username');
|
|
995
|
+
return [2 /*return*/];
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}); });
|
|
999
|
+
it('refuses a number that is not a number, rather than sending null', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1000
|
+
var withNumber, calls;
|
|
1001
|
+
return __generator(this, function (_a) {
|
|
1002
|
+
switch (_a.label) {
|
|
1003
|
+
case 0:
|
|
1004
|
+
withNumber = [
|
|
1005
|
+
{
|
|
1006
|
+
id: 'POST /SASjsApi/code/trigger',
|
|
1007
|
+
method: 'POST',
|
|
1008
|
+
path: '/SASjsApi/code/trigger',
|
|
1009
|
+
tag: 'Code',
|
|
1010
|
+
summary: 'Trigger a scheduled run',
|
|
1011
|
+
params: [
|
|
1012
|
+
{
|
|
1013
|
+
name: 'expiresAfterMins',
|
|
1014
|
+
in: 'body',
|
|
1015
|
+
required: false,
|
|
1016
|
+
type: 'number'
|
|
1017
|
+
}
|
|
1018
|
+
],
|
|
1019
|
+
requestBodies: ['application/json'],
|
|
1020
|
+
responses: [{ status: 200, description: 'Ok' }],
|
|
1021
|
+
implemented: true
|
|
1022
|
+
}
|
|
1023
|
+
];
|
|
1024
|
+
return [4 /*yield*/, loadConsole(function () { }, withNumber)];
|
|
1025
|
+
case 1:
|
|
1026
|
+
calls = (_a.sent()).calls;
|
|
1027
|
+
return [4 /*yield*/, selectRoute('POST /SASjsApi/code/trigger')];
|
|
1028
|
+
case 2:
|
|
1029
|
+
_a.sent();
|
|
1030
|
+
fill('expiresAfterMins', 'ten');
|
|
1031
|
+
document.getElementById('send').click();
|
|
1032
|
+
return [4 /*yield*/, settle(function () {
|
|
1033
|
+
return /must be a number/.test(document.getElementById('body').textContent || '');
|
|
1034
|
+
})
|
|
1035
|
+
// JSON.stringify writes NaN as null, which the host would read as "absent"
|
|
1036
|
+
];
|
|
1037
|
+
case 3:
|
|
1038
|
+
_a.sent();
|
|
1039
|
+
// JSON.stringify writes NaN as null, which the host would read as "absent"
|
|
1040
|
+
expect(calls).toHaveLength(0);
|
|
1041
|
+
return [2 /*return*/];
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
}); });
|
|
1045
|
+
it('does not demand an upload it cannot take', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1046
|
+
var upload, calls;
|
|
1047
|
+
return __generator(this, function (_a) {
|
|
1048
|
+
switch (_a.label) {
|
|
1049
|
+
case 0:
|
|
1050
|
+
upload = [
|
|
1051
|
+
{
|
|
1052
|
+
id: 'POST /SASjsApi/drive/deploy/upload',
|
|
1053
|
+
method: 'POST',
|
|
1054
|
+
path: '/SASjsApi/drive/deploy/upload',
|
|
1055
|
+
tag: 'Drive',
|
|
1056
|
+
summary: 'Upload a deployment',
|
|
1057
|
+
params: [
|
|
1058
|
+
{ name: 'file', in: 'formData', required: true, type: 'string' }
|
|
1059
|
+
],
|
|
1060
|
+
requestBodies: ['multipart/form-data'],
|
|
1061
|
+
responses: [{ status: 200, description: 'Ok' }],
|
|
1062
|
+
implemented: true
|
|
1063
|
+
}
|
|
1064
|
+
];
|
|
1065
|
+
return [4 /*yield*/, loadConsole(function () { }, upload)];
|
|
1066
|
+
case 1:
|
|
1067
|
+
calls = (_a.sent()).calls;
|
|
1068
|
+
return [4 /*yield*/, selectRoute('POST /SASjsApi/drive/deploy/upload')];
|
|
1069
|
+
case 2:
|
|
1070
|
+
_a.sent();
|
|
1071
|
+
document.getElementById('send').click();
|
|
1072
|
+
return [4 /*yield*/, settle(function () {
|
|
1073
|
+
return calls.length > 0 ||
|
|
1074
|
+
/does not send/.test(document.getElementById('form').textContent || '');
|
|
1075
|
+
})];
|
|
1076
|
+
case 3:
|
|
1077
|
+
_a.sent();
|
|
1078
|
+
expect(document.getElementById('status').textContent).not.toBe('missing input');
|
|
1079
|
+
return [2 /*return*/];
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
}); });
|
|
1083
|
+
it('clears the drive and starts again when the URL asks it to', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1084
|
+
var reset;
|
|
1085
|
+
return __generator(this, function (_a) {
|
|
1086
|
+
switch (_a.label) {
|
|
1087
|
+
case 0:
|
|
1088
|
+
reset = jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
1089
|
+
return [2 /*return*/];
|
|
1090
|
+
}); }); });
|
|
1091
|
+
sessionStorage.setItem('mock-console-token', 'stale');
|
|
1092
|
+
sessionStorage.setItem('mock-console-reloaded', '1');
|
|
1093
|
+
// jsdom does not navigate, and the redirect is the last thing the page does:
|
|
1094
|
+
// what matters here is that the drive was reset before it
|
|
1095
|
+
return [4 /*yield*/, loadConsole(function () {
|
|
1096
|
+
history.replaceState({}, '', '/api/?reset=1');
|
|
1097
|
+
window.SASjsMock = { reset: reset };
|
|
1098
|
+
})];
|
|
1099
|
+
case 1:
|
|
1100
|
+
// jsdom does not navigate, and the redirect is the last thing the page does:
|
|
1101
|
+
// what matters here is that the drive was reset before it
|
|
1102
|
+
_a.sent();
|
|
1103
|
+
return [4 /*yield*/, settle(function () { return reset.mock.calls.length > 0; })];
|
|
1104
|
+
case 2:
|
|
1105
|
+
_a.sent();
|
|
1106
|
+
expect(reset).toHaveBeenCalled();
|
|
1107
|
+
expect(sessionStorage.length).toBe(0);
|
|
1108
|
+
history.replaceState({}, '', '/api/');
|
|
1109
|
+
return [2 /*return*/];
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
}); });
|
|
1113
|
+
it('waits for the delete when there is no host to ask, and says so if it is blocked', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1114
|
+
var blocked;
|
|
1115
|
+
return __generator(this, function (_a) {
|
|
1116
|
+
switch (_a.label) {
|
|
1117
|
+
case 0:
|
|
1118
|
+
blocked = {};
|
|
1119
|
+
global.indexedDB = {
|
|
1120
|
+
deleteDatabase: jest.fn(function () {
|
|
1121
|
+
setTimeout(function () { return blocked.onblocked && blocked.onblocked(); }, 5);
|
|
1122
|
+
return blocked;
|
|
1123
|
+
})
|
|
1124
|
+
};
|
|
1125
|
+
return [4 /*yield*/, loadConsole(function () {
|
|
1126
|
+
// no host bridge on this page at all
|
|
1127
|
+
delete window.SASjsMock;
|
|
1128
|
+
history.replaceState({}, '', '/api/?reset=1');
|
|
1129
|
+
})];
|
|
1130
|
+
case 1:
|
|
1131
|
+
_a.sent();
|
|
1132
|
+
return [4 /*yield*/, settle(function () {
|
|
1133
|
+
return /could not be reset/.test(document.getElementById('routes').textContent || '');
|
|
1134
|
+
})];
|
|
1135
|
+
case 2:
|
|
1136
|
+
_a.sent();
|
|
1137
|
+
expect(global.indexedDB.deleteDatabase).toHaveBeenCalledWith('sasjs-mock-host');
|
|
1138
|
+
expect(document.getElementById('routes').textContent).toContain('still holds the drive');
|
|
1139
|
+
history.replaceState({}, '', '/api/');
|
|
1140
|
+
delete global.indexedDB;
|
|
1141
|
+
return [2 /*return*/];
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
}); });
|
|
1145
|
+
it('leaves the drive alone for any other value of the reset flag', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1146
|
+
var reset;
|
|
1147
|
+
return __generator(this, function (_a) {
|
|
1148
|
+
switch (_a.label) {
|
|
1149
|
+
case 0:
|
|
1150
|
+
reset = jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
1151
|
+
return [2 /*return*/];
|
|
1152
|
+
}); }); });
|
|
1153
|
+
sessionStorage.setItem('mock-console-token', 'kept');
|
|
1154
|
+
return [4 /*yield*/, loadConsole(function () {
|
|
1155
|
+
history.replaceState({}, '', '/api/?reset=0');
|
|
1156
|
+
window.SASjsMock = { reset: reset };
|
|
1157
|
+
})];
|
|
1158
|
+
case 1:
|
|
1159
|
+
_a.sent();
|
|
1160
|
+
return [4 /*yield*/, settle(function () { return document.querySelectorAll('#routes .route').length > 0; })];
|
|
1161
|
+
case 2:
|
|
1162
|
+
_a.sent();
|
|
1163
|
+
expect(reset).not.toHaveBeenCalled();
|
|
1164
|
+
expect(sessionStorage.getItem('mock-console-token')).toBe('kept');
|
|
1165
|
+
history.replaceState({}, '', '/api/');
|
|
1166
|
+
return [2 /*return*/];
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
}); });
|
|
1170
|
+
it('omits an untouched optional boolean on a patch, and sends it when chosen', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1171
|
+
var patch, calls, untouched;
|
|
1172
|
+
return __generator(this, function (_a) {
|
|
1173
|
+
switch (_a.label) {
|
|
1174
|
+
case 0:
|
|
1175
|
+
patch = [
|
|
1176
|
+
{
|
|
1177
|
+
id: 'PATCH /SASjsApi/user/{uid}',
|
|
1178
|
+
method: 'PATCH',
|
|
1179
|
+
path: '/SASjsApi/user/{uid}',
|
|
1180
|
+
tag: 'User',
|
|
1181
|
+
summary: 'Update a user',
|
|
1182
|
+
params: [
|
|
1183
|
+
{ name: 'uid', in: 'path', required: true, type: 'string' },
|
|
1184
|
+
{
|
|
1185
|
+
name: 'displayName',
|
|
1186
|
+
in: 'body',
|
|
1187
|
+
required: true,
|
|
1188
|
+
type: 'string'
|
|
1189
|
+
},
|
|
1190
|
+
{ name: 'username', in: 'body', required: true, type: 'string' },
|
|
1191
|
+
{ name: 'password', in: 'body', required: true, type: 'string' },
|
|
1192
|
+
{ name: 'isAdmin', in: 'body', required: false, type: 'boolean' }
|
|
1193
|
+
],
|
|
1194
|
+
requestBodies: ['application/json'],
|
|
1195
|
+
responses: [{ status: 200, description: 'Ok' }],
|
|
1196
|
+
implemented: true
|
|
1197
|
+
}
|
|
1198
|
+
];
|
|
1199
|
+
return [4 /*yield*/, loadConsole(function () { }, patch)];
|
|
1200
|
+
case 1:
|
|
1201
|
+
calls = (_a.sent()).calls;
|
|
1202
|
+
return [4 /*yield*/, selectRoute('PATCH /SASjsApi/user/{uid}')];
|
|
1203
|
+
case 2:
|
|
1204
|
+
_a.sent();
|
|
1205
|
+
fill('uid', 'user-restricted');
|
|
1206
|
+
fill('displayName', 'Restricted user');
|
|
1207
|
+
fill('username', 'sasjsuser');
|
|
1208
|
+
fill('password', 'Orion123');
|
|
1209
|
+
return [4 /*yield*/, sendAndWait(calls)];
|
|
1210
|
+
case 3:
|
|
1211
|
+
_a.sent();
|
|
1212
|
+
untouched = JSON.parse(calls[0].init.body);
|
|
1213
|
+
expect(untouched.isAdmin).toBeUndefined();
|
|
1214
|
+
expect(calls[0].url).toMatch(/\/SASjsApi\/user\/user-restricted$/);
|
|
1215
|
+
// and choosing a value sends it, as a boolean rather than the string
|
|
1216
|
+
return [4 /*yield*/, settle(function () { return !document.getElementById('send').disabled; })];
|
|
1217
|
+
case 4:
|
|
1218
|
+
// and choosing a value sends it, as a boolean rather than the string
|
|
1219
|
+
_a.sent();
|
|
1220
|
+
document.querySelector('#form select[data-name="isAdmin"]').value = 'true';
|
|
1221
|
+
return [4 /*yield*/, sendAndWait(calls, 2)];
|
|
1222
|
+
case 5:
|
|
1223
|
+
_a.sent();
|
|
1224
|
+
expect(JSON.parse(calls[1].init.body).isAdmin).toBe(true);
|
|
1225
|
+
return [2 /*return*/];
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
}); });
|
|
1229
|
+
it('sends a named shape as JSON, while an enum stays the word that was typed', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1230
|
+
var deploy, calls, body;
|
|
1231
|
+
return __generator(this, function (_a) {
|
|
1232
|
+
switch (_a.label) {
|
|
1233
|
+
case 0:
|
|
1234
|
+
deploy = [
|
|
1235
|
+
{
|
|
1236
|
+
id: 'POST /SASjsApi/drive/deploy',
|
|
1237
|
+
method: 'POST',
|
|
1238
|
+
path: '/SASjsApi/drive/deploy',
|
|
1239
|
+
tag: 'Drive',
|
|
1240
|
+
summary: 'Deploy a bundle',
|
|
1241
|
+
params: [
|
|
1242
|
+
{ name: 'appLoc', in: 'body', required: true, type: 'string' },
|
|
1243
|
+
{ name: 'fileTree', in: 'body', required: true, type: 'FileTree' },
|
|
1244
|
+
{ name: 'runTime', in: 'body', required: false, type: 'RunTimeType' }
|
|
1245
|
+
],
|
|
1246
|
+
requestBodies: ['application/json'],
|
|
1247
|
+
responses: [{ status: 200, description: 'Ok' }],
|
|
1248
|
+
implemented: true
|
|
1249
|
+
}
|
|
1250
|
+
];
|
|
1251
|
+
return [4 /*yield*/, loadConsole(function () { }, deploy)];
|
|
1252
|
+
case 1:
|
|
1253
|
+
calls = (_a.sent()).calls;
|
|
1254
|
+
return [4 /*yield*/, selectRoute('POST /SASjsApi/drive/deploy')];
|
|
1255
|
+
case 2:
|
|
1256
|
+
_a.sent();
|
|
1257
|
+
fill('appLoc', '/Public/app/demo');
|
|
1258
|
+
fill('fileTree', JSON.stringify({
|
|
1259
|
+
members: [{ name: 'services', type: 'folder', members: [] }]
|
|
1260
|
+
}));
|
|
1261
|
+
fill('runTime', 'sas');
|
|
1262
|
+
return [4 /*yield*/, sendAndWait(calls)];
|
|
1263
|
+
case 3:
|
|
1264
|
+
_a.sent();
|
|
1265
|
+
body = JSON.parse(calls[0].init.body);
|
|
1266
|
+
expect(typeof body.fileTree).toBe('object');
|
|
1267
|
+
expect(body.fileTree.members[0].name).toBe('services');
|
|
1268
|
+
// an enum is not JSON and must not be coerced
|
|
1269
|
+
expect(body.runTime).toBe('sas');
|
|
1270
|
+
return [2 /*return*/];
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
}); });
|
|
1274
|
+
it('names the account it falls back to when the fixture ships no administrator', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1275
|
+
var plain, note, back;
|
|
1276
|
+
return __generator(this, function (_a) {
|
|
1277
|
+
switch (_a.label) {
|
|
1278
|
+
case 0:
|
|
1279
|
+
plain = {
|
|
1280
|
+
format: 1,
|
|
1281
|
+
users: [
|
|
1282
|
+
{ uid: 'u1', username: 'plain1', password: 'Orion123', isAdmin: false },
|
|
1283
|
+
{ uid: 'u2', username: 'plain2', password: 'Orion123', isAdmin: false }
|
|
1284
|
+
]
|
|
1285
|
+
};
|
|
1286
|
+
return [4 /*yield*/, loadConsole(function () {
|
|
1287
|
+
;
|
|
1288
|
+
window.SASjsMock = {
|
|
1289
|
+
identity: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
1290
|
+
return [2 /*return*/, ({ fixture: plain, subject: 'plain1' })];
|
|
1291
|
+
}); }); },
|
|
1292
|
+
as: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
1293
|
+
return [2 /*return*/, ({ accessToken: 't', refreshToken: 'r' })];
|
|
1294
|
+
}); }); }
|
|
1295
|
+
};
|
|
1296
|
+
}, operations, plain)];
|
|
1297
|
+
case 1:
|
|
1298
|
+
_a.sent();
|
|
1299
|
+
note = document.getElementById('accounts').textContent || '';
|
|
1300
|
+
expect(note).toContain('no administrator');
|
|
1301
|
+
expect(note).toContain('plain1');
|
|
1302
|
+
__spreadArray([], __read(document.querySelectorAll('#accounts button')), false).find(function (button) { return button.dataset.user === 'plain2'; }).click();
|
|
1303
|
+
return [4 /*yield*/, (function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1304
|
+
var i, found;
|
|
1305
|
+
return __generator(this, function (_a) {
|
|
1306
|
+
switch (_a.label) {
|
|
1307
|
+
case 0:
|
|
1308
|
+
i = 0;
|
|
1309
|
+
_a.label = 1;
|
|
1310
|
+
case 1:
|
|
1311
|
+
if (!(i < 200)) return [3 /*break*/, 4];
|
|
1312
|
+
found = __spreadArray([], __read(document.querySelectorAll('#accounts button')), false).find(function (button) { return /^Back to/.test(button.textContent || ''); });
|
|
1313
|
+
if (found)
|
|
1314
|
+
return [2 /*return*/, found];
|
|
1315
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 5); })];
|
|
1316
|
+
case 2:
|
|
1317
|
+
_a.sent();
|
|
1318
|
+
_a.label = 3;
|
|
1319
|
+
case 3:
|
|
1320
|
+
i++;
|
|
1321
|
+
return [3 /*break*/, 1];
|
|
1322
|
+
case 4: return [2 /*return*/, undefined];
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
}); })()];
|
|
1326
|
+
case 2:
|
|
1327
|
+
back = _a.sent();
|
|
1328
|
+
expect(back).toBeDefined();
|
|
1329
|
+
expect(back.textContent).toBe('Back to plain1');
|
|
1330
|
+
return [2 /*return*/];
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
}); });
|
|
1334
|
+
it('drops the token when the visitor goes back to the demo administrator', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1335
|
+
var calls, back;
|
|
1336
|
+
return __generator(this, function (_a) {
|
|
1337
|
+
switch (_a.label) {
|
|
1338
|
+
case 0:
|
|
1339
|
+
sessionStorage.setItem('mock-console-token', 'token-for-sasjsuser');
|
|
1340
|
+
global.SASjsMock = {
|
|
1341
|
+
as: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
1342
|
+
return [2 /*return*/, ({ accessToken: 'admin-token' })];
|
|
1343
|
+
}); }); },
|
|
1344
|
+
identity: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
1345
|
+
return [2 /*return*/, ({ subject: 'sasjsuser' })];
|
|
1346
|
+
}); }); }
|
|
1347
|
+
};
|
|
1348
|
+
return [4 /*yield*/, loadConsole()
|
|
1349
|
+
// the page offers the way back only while it is acting as someone
|
|
1350
|
+
];
|
|
1351
|
+
case 1:
|
|
1352
|
+
calls = (_a.sent()).calls;
|
|
1353
|
+
back = __spreadArray([], __read(document.querySelectorAll('#accounts button')), false).find(function (button) { return (button.textContent || '').includes('Back to the demo'); });
|
|
1354
|
+
expect(back).toBeDefined();
|
|
1355
|
+
back.click();
|
|
1356
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 10); })];
|
|
1357
|
+
case 2:
|
|
1358
|
+
_a.sent();
|
|
1359
|
+
expect(sessionStorage.getItem('mock-console-token')).toBeNull();
|
|
1360
|
+
return [4 /*yield*/, selectRoute('GET /SASjsApi/user')];
|
|
1361
|
+
case 3:
|
|
1362
|
+
_a.sent();
|
|
1363
|
+
return [4 /*yield*/, sendAndWait(calls)];
|
|
1364
|
+
case 4:
|
|
1365
|
+
_a.sent();
|
|
1366
|
+
expect(calls[0].init.headers.authorization).toBeUndefined();
|
|
1367
|
+
return [2 /*return*/];
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}); });
|
|
1371
|
+
});
|