@sasjs/cli 6.22.0 → 6.23.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 +3 -0
- package/build/commands/build/internal/mockSite/assets/sw.js +113 -9
- package/build/commands/build/internal/mockSite/mockSite.d.ts +2 -0
- package/build/commands/build/internal/mockSite/mockSite.js +8 -2
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +37 -0
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +138 -1
- package/package.json +1 -1
|
@@ -1525,6 +1525,9 @@ const api = {
|
|
|
1525
1525
|
bytes: [...FILES.keys()].reduce((total, abs) => total + entrySizeAt(abs), 0)
|
|
1526
1526
|
}),
|
|
1527
1527
|
run: (payload) => runProgram(payload),
|
|
1528
|
+
// the file a program names, so the host can tell a .sas program from a .js one
|
|
1529
|
+
// and refuse the former instead of compiling SAS source as JavaScript
|
|
1530
|
+
resolveProgram: (program) => resolveProgram(program),
|
|
1528
1531
|
// the drive's bytes, for @sasjs/api's services: the rules that decide a
|
|
1529
1532
|
// status are the package's, so this host hands over storage and nothing else
|
|
1530
1533
|
driveStore,
|
|
@@ -181,19 +181,21 @@ const SEEDED_HEADER_LINE = String(R.HEADERS_SEED)
|
|
|
181
181
|
* it.
|
|
182
182
|
*/
|
|
183
183
|
const artifactOf = (result, debug) => {
|
|
184
|
-
if (debug)
|
|
184
|
+
if (debug) {
|
|
185
|
+
// webout SEP log SEP [listing] - the framing @sasjs/adapter and the Studio's
|
|
186
|
+
// parser both split on, so a recorded session reads the same as a real one.
|
|
187
|
+
const framed = [result.webout, LOGS_SEPARATOR, result.log, LOGS_SEPARATOR]
|
|
188
|
+
|
|
189
|
+
if (result.print) framed.push(result.print)
|
|
190
|
+
|
|
185
191
|
return {
|
|
186
|
-
contents:
|
|
187
|
-
result.webout,
|
|
188
|
-
LOGS_SEPARATOR,
|
|
189
|
-
result.log,
|
|
190
|
-
LOGS_SEPARATOR
|
|
191
|
-
].join('\n'),
|
|
192
|
+
contents: framed.join('\n'),
|
|
192
193
|
// the frame is text, and the service's own type and disposition are not
|
|
193
194
|
// applied to it - a download there would put the frame in the file
|
|
194
195
|
headers: { 'content-type': 'text/plain' },
|
|
195
196
|
log: result.log
|
|
196
197
|
}
|
|
198
|
+
}
|
|
197
199
|
|
|
198
200
|
const headers = parseHeaders(result.headers)
|
|
199
201
|
let contents = result.webout
|
|
@@ -231,6 +233,66 @@ const persistChanges = async () => {
|
|
|
231
233
|
await broadcast({ type: 'sasjs-persist', files })
|
|
232
234
|
}
|
|
233
235
|
|
|
236
|
+
// ----------------------------------------------------------- the SAS transcript
|
|
237
|
+
/**
|
|
238
|
+
* A recorded SAS session, for a project that ships one.
|
|
239
|
+
*
|
|
240
|
+
* A browser has no SAS binary. Rather than let a Run on a .sas program be a
|
|
241
|
+
* surprise, a project may record a session once and commit it as
|
|
242
|
+
* `sas-transcript.json`: the build copies it into the site and this answers a
|
|
243
|
+
* run of a program it names with that session's log, listing and webout. The
|
|
244
|
+
* recording is the demo's own data, so nothing here is invented for a project
|
|
245
|
+
* that does not ship one - those runs reach the runner and are refused exactly
|
|
246
|
+
* as before.
|
|
247
|
+
*
|
|
248
|
+
* Shape: `{ "<program path>": {"log": "...", "print": "...", "webout": "..."} }`,
|
|
249
|
+
* all three strings. The path is matched as written apart from the
|
|
250
|
+
* `/sasjs/drive/files` prefix and the `.sas` suffix, so a recording keeps the
|
|
251
|
+
* path a person sees; a bare file name is not enough, because that would answer
|
|
252
|
+
* every file of that name in the drive.
|
|
253
|
+
*/
|
|
254
|
+
const SAS_TRANSCRIPT = 'host/sas-transcript.json'
|
|
255
|
+
let TRANSCRIPT = null
|
|
256
|
+
|
|
257
|
+
const transcript = async () => {
|
|
258
|
+
if (TRANSCRIPT) return TRANSCRIPT
|
|
259
|
+
try {
|
|
260
|
+
const response = await fetch(SAS_TRANSCRIPT, { cache: 'no-cache' })
|
|
261
|
+
TRANSCRIPT = response.ok ? await response.json() : {}
|
|
262
|
+
} catch (e) {
|
|
263
|
+
TRANSCRIPT = {}
|
|
264
|
+
}
|
|
265
|
+
return TRANSCRIPT
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The drive path without its mount prefix or extension.
|
|
270
|
+
*
|
|
271
|
+
* A recording is written with the path a person sees (`/sasjs/drive/files/...`,
|
|
272
|
+
* ending in `.sas`); the route hands the request the path relative to the app,
|
|
273
|
+
* and Studio asks for it without the extension. Comparing the logical path makes
|
|
274
|
+
* those the same program without widening the match to the file name alone,
|
|
275
|
+
* which would answer every `report.sas` in the drive with a session recorded for
|
|
276
|
+
* some other file.
|
|
277
|
+
*/
|
|
278
|
+
const logicalPath = (value) =>
|
|
279
|
+
String(value || '')
|
|
280
|
+
.replace(/^\/sasjs\/drive\/files/i, '')
|
|
281
|
+
.replace(/\.sas$/i, '')
|
|
282
|
+
|
|
283
|
+
/** The recording naming this program, if the project shipped one. */
|
|
284
|
+
const transcriptFor = async (programPath) => {
|
|
285
|
+
const requested = logicalPath(programPath)
|
|
286
|
+
if (!requested) return undefined
|
|
287
|
+
|
|
288
|
+
const recorded = await transcript()
|
|
289
|
+
|
|
290
|
+
for (const key of Object.keys(recorded))
|
|
291
|
+
if (logicalPath(key) === requested) return recorded[key]
|
|
292
|
+
|
|
293
|
+
return undefined
|
|
294
|
+
}
|
|
295
|
+
|
|
234
296
|
// ---------------------------------------------------------------- the runner
|
|
235
297
|
/**
|
|
236
298
|
* The work a run does, over the browser's drive.
|
|
@@ -250,6 +312,19 @@ class BrowserRuntime extends API.MemorySessionHost {
|
|
|
250
312
|
async run(request) {
|
|
251
313
|
const program = request.program || {}
|
|
252
314
|
const params = Object.assign({}, request.vars || {})
|
|
315
|
+
|
|
316
|
+
// A SAS program the recording does not name. This host has no SAS binary, so
|
|
317
|
+
// the answer is the refusal a client already understands - not a JavaScript
|
|
318
|
+
// SyntaxError quoting the SAS source, which is what handing it to the runner
|
|
319
|
+
// would produce. The file is resolved because the request may name the
|
|
320
|
+
// program without its extension.
|
|
321
|
+
if (program.code === undefined) {
|
|
322
|
+
const resolved = R.resolveProgram(program.path)
|
|
323
|
+
|
|
324
|
+
if (resolved && /\.sas$/i.test(resolved))
|
|
325
|
+
throw runFailure('"runTime" must be one of [js]')
|
|
326
|
+
}
|
|
327
|
+
|
|
253
328
|
const payload = { params, tables: tablesOf(request.files) }
|
|
254
329
|
if (program.code !== undefined) payload.code = program.code
|
|
255
330
|
else payload.program = program.path
|
|
@@ -516,8 +591,10 @@ const SERVICES = {
|
|
|
516
591
|
identity: IDENTITY,
|
|
517
592
|
records: RECORDS,
|
|
518
593
|
runtime: API.createRuntimeService(IDENTITY_STORE, RUNTIME, {
|
|
519
|
-
//
|
|
520
|
-
//
|
|
594
|
+
// The one run time this host executes. A recorded SAS session is answered
|
|
595
|
+
// before the runner (see SAS_TRANSCRIPT below), not by claiming a `sas` run
|
|
596
|
+
// time - a .sas program with no recording must still be refused, and a route
|
|
597
|
+
// that accepted `sas` would compile SAS source as JavaScript instead.
|
|
521
598
|
runTimes: ['js']
|
|
522
599
|
}),
|
|
523
600
|
session: API.createSessionService(IDENTITY_STORE, RUNTIME),
|
|
@@ -860,6 +937,33 @@ const runRoute = async (routeId, payload, subject) => {
|
|
|
860
937
|
runRequest.program = query._program || body._program
|
|
861
938
|
}
|
|
862
939
|
|
|
940
|
+
// A recorded SAS session is answered here, ahead of the run time check: a
|
|
941
|
+
// transcript is data, not a run time, and this host holds only `js`. Accepting
|
|
942
|
+
// `sas` instead would make the package compile SAS source as JavaScript and
|
|
943
|
+
// hand back a SyntaxError where every other .sas program gets a refusal.
|
|
944
|
+
// Neither a code run nor a trigger: a trigger has to answer with the session
|
|
945
|
+
// record the route builds, which a client parses as JSON, so a recording is not
|
|
946
|
+
// an answer there.
|
|
947
|
+
if (!code && !trigger) {
|
|
948
|
+
const recording = await transcriptFor(runRequest.program)
|
|
949
|
+
|
|
950
|
+
if (recording)
|
|
951
|
+
return artifactResponse(
|
|
952
|
+
artifactOf(
|
|
953
|
+
{
|
|
954
|
+
webout: recording.webout || '',
|
|
955
|
+
log: recording.log || '',
|
|
956
|
+
print: recording.print || '',
|
|
957
|
+
// the seed every session starts with, so a run without `_debug`
|
|
958
|
+
// answers text/html rather than leaving the type to the browser -
|
|
959
|
+
// the same rule runtime-core applies to a service that declares none
|
|
960
|
+
headers: SEEDED_HEADER_LINE
|
|
961
|
+
},
|
|
962
|
+
vars._debug !== undefined
|
|
963
|
+
)
|
|
964
|
+
)
|
|
965
|
+
}
|
|
966
|
+
|
|
863
967
|
if (trigger) {
|
|
864
968
|
runRequest.expiresAfterMins =
|
|
865
969
|
query.expiresAfterMins !== undefined
|
|
@@ -9,6 +9,8 @@ export interface MockSiteResult {
|
|
|
9
9
|
webFileCount: number;
|
|
10
10
|
/** True when a captured drive snapshot was shipped with the site. */
|
|
11
11
|
snapshot: boolean;
|
|
12
|
+
/** True when a recorded SAS session was shipped, for .sas runs to answer. */
|
|
13
|
+
sasTranscript: boolean;
|
|
12
14
|
}
|
|
13
15
|
/**
|
|
14
16
|
* The webout() block is the one @sasjs/utils generates for the server, emitted
|
|
@@ -683,7 +683,7 @@ var patchIndexHtml = function (html, version, basePath) {
|
|
|
683
683
|
*/
|
|
684
684
|
function buildMockSite(target) {
|
|
685
685
|
return __awaiter(this, void 0, void 0, function () {
|
|
686
|
-
var projectDir, appLocRaw, appLoc, buildFolder, basePath, webSourceRaw, webFolder, outputFolder, streamConfig, excluded, _a, seed, skipped, jsPrograms, sasPrograms, driveEntries, webFileCount, hostFolder, _b, _c, asset, hostVersion, buildStamp, swPath, snapshotSource, hasSnapshot, indexPath, _d, patched, warnings;
|
|
686
|
+
var projectDir, appLocRaw, appLoc, buildFolder, basePath, webSourceRaw, webFolder, outputFolder, streamConfig, excluded, _a, seed, skipped, jsPrograms, sasPrograms, driveEntries, webFileCount, hostFolder, _b, _c, asset, hostVersion, buildStamp, swPath, snapshotSource, hasSnapshot, transcriptSource, hasTranscript, indexPath, _d, patched, warnings;
|
|
687
687
|
var e_8, _e;
|
|
688
688
|
var _f, _g, _h, _j, _k, _l;
|
|
689
689
|
return __generator(this, function (_m) {
|
|
@@ -854,6 +854,10 @@ function buildMockSite(target) {
|
|
|
854
854
|
if (hasSnapshot) {
|
|
855
855
|
fs_1.default.copyFileSync(snapshotSource, path_1.default.join(hostFolder, 'vfs-snapshot.json'));
|
|
856
856
|
}
|
|
857
|
+
transcriptSource = path_1.default.join(buildFolder, 'sas-transcript.json');
|
|
858
|
+
hasTranscript = fs_1.default.existsSync(transcriptSource);
|
|
859
|
+
if (hasTranscript)
|
|
860
|
+
fs_1.default.copyFileSync(transcriptSource, path_1.default.join(hostFolder, 'sas-transcript.json'));
|
|
857
861
|
// 3. the drive, laid out from the compile output
|
|
858
862
|
fs_1.default.writeFileSync(path_1.default.join(hostFolder, 'vfs-seed.json'), JSON.stringify({ format: exports.DRIVE_FORMAT, files: seed }));
|
|
859
863
|
fs_1.default.writeFileSync(path_1.default.join(hostFolder, 'mock-config.json'), JSON.stringify({
|
|
@@ -863,6 +867,7 @@ function buildMockSite(target) {
|
|
|
863
867
|
jsPrograms: jsPrograms,
|
|
864
868
|
programFolders: PROGRAM_FOLDERS,
|
|
865
869
|
snapshot: hasSnapshot,
|
|
870
|
+
sasTranscript: hasTranscript,
|
|
866
871
|
buildStamp: buildStamp,
|
|
867
872
|
builtAt: new Date().toISOString()
|
|
868
873
|
}, null, 2));
|
|
@@ -876,7 +881,8 @@ function buildMockSite(target) {
|
|
|
876
881
|
driveEntries: driveEntries,
|
|
877
882
|
jsPrograms: jsPrograms,
|
|
878
883
|
webFileCount: webFileCount,
|
|
879
|
-
snapshot: hasSnapshot
|
|
884
|
+
snapshot: hasSnapshot,
|
|
885
|
+
sasTranscript: hasTranscript
|
|
880
886
|
}];
|
|
881
887
|
}
|
|
882
888
|
});
|
|
@@ -642,6 +642,43 @@ describe('browser-hosted build', function () {
|
|
|
642
642
|
}
|
|
643
643
|
});
|
|
644
644
|
}); });
|
|
645
|
+
it('ships a recorded SAS session when the project has one', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
646
|
+
var recording, result;
|
|
647
|
+
var _a;
|
|
648
|
+
return __generator(this, function (_b) {
|
|
649
|
+
switch (_b.label) {
|
|
650
|
+
case 0:
|
|
651
|
+
recording = (_a = {},
|
|
652
|
+
_a["/sasjs/drive/files".concat(appLoc, "/services/public/report.sas")] = {
|
|
653
|
+
log: 'NOTE: Copyright (c) 2016 by SAS Institute Inc., Cary, NC, USA.',
|
|
654
|
+
print: 'The SAS System',
|
|
655
|
+
webout: '{"SYSSITE":"00000000"}'
|
|
656
|
+
},
|
|
657
|
+
_a);
|
|
658
|
+
write(path_1.default.join(root, 'sasjsbuild', 'sas-transcript.json'), JSON.stringify(recording));
|
|
659
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
660
|
+
case 1:
|
|
661
|
+
result = _b.sent();
|
|
662
|
+
expect(result.sasTranscript).toBe(true);
|
|
663
|
+
expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(out(), 'host', 'sas-transcript.json'), 'utf8'))).toEqual(recording);
|
|
664
|
+
expect(readConfig().sasTranscript).toBe(true);
|
|
665
|
+
return [2 /*return*/];
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
}); });
|
|
669
|
+
it('ships no recording when the project has none', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
670
|
+
var result;
|
|
671
|
+
return __generator(this, function (_a) {
|
|
672
|
+
switch (_a.label) {
|
|
673
|
+
case 0: return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
674
|
+
case 1:
|
|
675
|
+
result = _a.sent();
|
|
676
|
+
expect(result.sasTranscript).toBe(false);
|
|
677
|
+
expect(fs_1.default.existsSync(path_1.default.join(out(), 'host', 'sas-transcript.json'))).toBe(false);
|
|
678
|
+
return [2 /*return*/];
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
}); });
|
|
645
682
|
it('ships no snapshot when the project has none', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
646
683
|
var result;
|
|
647
684
|
return __generator(this, function (_a) {
|
|
@@ -163,7 +163,7 @@ var loadWorker = function (base) {
|
|
|
163
163
|
};
|
|
164
164
|
require('../assets/sw.js');
|
|
165
165
|
global.fetch = jest.fn(function (input) { return __awaiter(void 0, void 0, void 0, function () {
|
|
166
|
-
var target;
|
|
166
|
+
var target, fixture;
|
|
167
167
|
var _a;
|
|
168
168
|
return __generator(this, function (_b) {
|
|
169
169
|
target = typeof input === 'string' ? input : (input && input.url) || String(input);
|
|
@@ -175,6 +175,10 @@ var loadWorker = function (base) {
|
|
|
175
175
|
})];
|
|
176
176
|
if (target.includes('index.html'))
|
|
177
177
|
return [2 /*return*/, new Response('<!doctype html><html><head><title>App</title></head><body>home</body></html>', { status: 200, headers: { 'content-type': 'text/html' } })];
|
|
178
|
+
if (target.includes('sas-transcript')) {
|
|
179
|
+
fixture = global.__sasTranscript;
|
|
180
|
+
return [2 /*return*/, fixture ? jsonResponse(fixture) : new Response('', { status: 404 })];
|
|
181
|
+
}
|
|
178
182
|
return [2 /*return*/, target.includes('vfs-seed')
|
|
179
183
|
? jsonResponse({
|
|
180
184
|
files: (_a = {},
|
|
@@ -286,6 +290,104 @@ describe('service worker host', function () {
|
|
|
286
290
|
}
|
|
287
291
|
});
|
|
288
292
|
}); });
|
|
293
|
+
it('answers a recorded SAS session with its log, listing and webout', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
294
|
+
var worker, response, body;
|
|
295
|
+
var _a, _b;
|
|
296
|
+
return __generator(this, function (_c) {
|
|
297
|
+
switch (_c.label) {
|
|
298
|
+
case 0:
|
|
299
|
+
// A browser has no SAS binary. A project may ship the transcript of one real
|
|
300
|
+
// session, and a program it names is answered with it - framed the way the
|
|
301
|
+
// adapter and the Studio split the three parts: webout SEP log SEP listing.
|
|
302
|
+
;
|
|
303
|
+
global.__sasTranscript = (_a = {},
|
|
304
|
+
_a["".concat(appLoc, "/services/public/report.sas")] = {
|
|
305
|
+
webout: '{"SYSSITE":"00000000"}',
|
|
306
|
+
log: 'NOTE: Copyright (c) 2016 by SAS Institute Inc., Cary, NC, USA.',
|
|
307
|
+
print: 'The SAS System\n\n Obs customer\n 1 Northwind Ltd'
|
|
308
|
+
},
|
|
309
|
+
_a);
|
|
310
|
+
worker = loadWorker();
|
|
311
|
+
worker.runtime.load((_b = {},
|
|
312
|
+
_b[drivePath('services/public/report.sas')] = 'data work.class; run;',
|
|
313
|
+
_b));
|
|
314
|
+
return [4 /*yield*/, worker.dispatch('/SASjsApi/stp/execute?_program=' +
|
|
315
|
+
encodeURIComponent("".concat(appLoc, "/services/public/report")) +
|
|
316
|
+
'&_debug=131')];
|
|
317
|
+
case 1:
|
|
318
|
+
response = _c.sent();
|
|
319
|
+
expect(response.status).toBe(200);
|
|
320
|
+
return [4 /*yield*/, response.text()];
|
|
321
|
+
case 2:
|
|
322
|
+
body = _c.sent();
|
|
323
|
+
expect(body).toContain('NOTE: Copyright');
|
|
324
|
+
expect(body).toContain('Northwind Ltd');
|
|
325
|
+
expect(body).toContain('"SYSSITE":"00000000"');
|
|
326
|
+
delete global.__sasTranscript;
|
|
327
|
+
return [2 /*return*/];
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}); });
|
|
331
|
+
it('gives a recorded run the seeded content type when not debugging', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
332
|
+
var worker, response, _a;
|
|
333
|
+
var _b, _c;
|
|
334
|
+
return __generator(this, function (_d) {
|
|
335
|
+
switch (_d.label) {
|
|
336
|
+
case 0:
|
|
337
|
+
// Without `_debug` the body is the webout alone, and it still has to carry
|
|
338
|
+
// the type a session is seeded with - an empty header set leaves the choice
|
|
339
|
+
// to the browser, which is the case the seed exists to prevent.
|
|
340
|
+
;
|
|
341
|
+
global.__sasTranscript = (_b = {},
|
|
342
|
+
_b["".concat(appLoc, "/services/public/report.sas")] = {
|
|
343
|
+
webout: '{"SYSSITE":"00000000"}',
|
|
344
|
+
log: 'NOTE: recorded',
|
|
345
|
+
print: 'The SAS System'
|
|
346
|
+
},
|
|
347
|
+
_b);
|
|
348
|
+
worker = loadWorker();
|
|
349
|
+
worker.runtime.load((_c = {},
|
|
350
|
+
_c[drivePath('services/public/report.sas')] = 'data work.class; run;',
|
|
351
|
+
_c));
|
|
352
|
+
return [4 /*yield*/, worker.dispatch('/SASjsApi/stp/execute?_program=' +
|
|
353
|
+
encodeURIComponent("".concat(appLoc, "/services/public/report")))];
|
|
354
|
+
case 1:
|
|
355
|
+
response = _d.sent();
|
|
356
|
+
expect(response.status).toBe(200);
|
|
357
|
+
expect(response.headers.get('content-type')).toContain('text/html');
|
|
358
|
+
_a = expect;
|
|
359
|
+
return [4 /*yield*/, response.text()];
|
|
360
|
+
case 2:
|
|
361
|
+
_a.apply(void 0, [_d.sent()]).toBe('{"SYSSITE":"00000000"}');
|
|
362
|
+
delete global.__sasTranscript;
|
|
363
|
+
return [2 /*return*/];
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}); });
|
|
367
|
+
it('refuses a .sas program no recording names, as it always did', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
368
|
+
var worker, response, body;
|
|
369
|
+
var _a;
|
|
370
|
+
return __generator(this, function (_b) {
|
|
371
|
+
switch (_b.label) {
|
|
372
|
+
case 0:
|
|
373
|
+
worker = loadWorker();
|
|
374
|
+
worker.runtime.load((_a = {},
|
|
375
|
+
_a[drivePath('services/public/other.sas')] = 'data work.class; run;',
|
|
376
|
+
_a));
|
|
377
|
+
return [4 /*yield*/, worker.dispatch('/SASjsApi/stp/execute?_program=' +
|
|
378
|
+
encodeURIComponent("".concat(appLoc, "/services/public/other")))];
|
|
379
|
+
case 1:
|
|
380
|
+
response = _b.sent();
|
|
381
|
+
expect(response.status).toBe(400);
|
|
382
|
+
return [4 /*yield*/, response.text()];
|
|
383
|
+
case 2:
|
|
384
|
+
body = _b.sent();
|
|
385
|
+
expect(body).toContain('runTime');
|
|
386
|
+
expect(body).not.toContain('SyntaxError');
|
|
387
|
+
return [2 /*return*/];
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}); });
|
|
289
391
|
/**
|
|
290
392
|
* A worker whose drive holds one service that answers with a download, the
|
|
291
393
|
* way a streaming service does on the server: the body goes to the webout
|
|
@@ -1059,6 +1161,41 @@ describe('service worker host', function () {
|
|
|
1059
1161
|
}
|
|
1060
1162
|
});
|
|
1061
1163
|
}); });
|
|
1164
|
+
it('keeps a trigger a JSON answer, not the recorded transcript', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1165
|
+
var worker, response, body;
|
|
1166
|
+
var _a, _b;
|
|
1167
|
+
return __generator(this, function (_c) {
|
|
1168
|
+
switch (_c.label) {
|
|
1169
|
+
case 0:
|
|
1170
|
+
// A recording answers a run, not a trigger: the trigger route answers with
|
|
1171
|
+
// the session record a client parses as JSON, so returning the framed
|
|
1172
|
+
// webout there would break triggerProgram and the Studio.
|
|
1173
|
+
;
|
|
1174
|
+
global.__sasTranscript = (_a = {},
|
|
1175
|
+
_a["".concat(appLoc, "/services/public/report.sas")] = {
|
|
1176
|
+
webout: '{"SYSSITE":"00000000"}',
|
|
1177
|
+
log: 'NOTE: recorded',
|
|
1178
|
+
print: 'The SAS System'
|
|
1179
|
+
},
|
|
1180
|
+
_a);
|
|
1181
|
+
worker = loadWorker();
|
|
1182
|
+
worker.runtime.load((_b = {},
|
|
1183
|
+
_b[drivePath('services/public/report.sas')] = 'data work.class; run;',
|
|
1184
|
+
_b));
|
|
1185
|
+
return [4 /*yield*/, worker.dispatch('/SASjsApi/stp/trigger?_program=' +
|
|
1186
|
+
encodeURIComponent("".concat(appLoc, "/services/public/report")), runRequest({ params: {} }))];
|
|
1187
|
+
case 1:
|
|
1188
|
+
response = _c.sent();
|
|
1189
|
+
expect(response.headers.get('content-type')).toContain('application/json');
|
|
1190
|
+
return [4 /*yield*/, response.json()];
|
|
1191
|
+
case 2:
|
|
1192
|
+
body = _c.sent();
|
|
1193
|
+
expect(body).not.toHaveProperty('webout');
|
|
1194
|
+
delete global.__sasTranscript;
|
|
1195
|
+
return [2 /*return*/];
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
}); });
|
|
1062
1199
|
it('refuses a deploy upload with no archive, and one that is not a zip', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1063
1200
|
var dispatch, empty, _a, form, notZip, _b;
|
|
1064
1201
|
return __generator(this, function (_c) {
|
package/package.json
CHANGED