sounding 0.0.0 → 0.0.1
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/README.md +31 -4
- package/RESEARCH.md +743 -231
- package/index.js +58 -3
- package/lib/create-app-manager.js +329 -0
- package/lib/create-auth-helpers.js +159 -0
- package/lib/create-browser-manager.js +132 -0
- package/lib/create-expect.js +155 -0
- package/lib/create-helper-runner.js +55 -0
- package/lib/create-mail-capture.js +391 -0
- package/lib/create-mailbox.js +28 -0
- package/lib/create-request-client.js +549 -0
- package/lib/create-runtime.js +170 -0
- package/lib/create-test-api.js +228 -0
- package/lib/create-visit-client.js +114 -0
- package/lib/create-world-engine.js +300 -0
- package/lib/create-world-loader.js +128 -0
- package/lib/default-config.js +60 -0
- package/lib/define-world.js +37 -0
- package/lib/merge-config.js +25 -0
- package/lib/normalize-config.js +54 -0
- package/lib/resolve-datastore.js +97 -0
- package/package.json +17 -1
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
const { Transform } = require('node:stream')
|
|
2
|
+
const QS = require('node:querystring')
|
|
3
|
+
|
|
4
|
+
function isAbsoluteUrl(value) {
|
|
5
|
+
return /^https?:\/\//i.test(value)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isPlainObject(value) {
|
|
9
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function trimTrailingSlash(value) {
|
|
13
|
+
return value.replace(/\/$/, '')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function looksLikeJson({ contentType, body }) {
|
|
17
|
+
if (!body) {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (contentType?.includes('application/json')) {
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return /^[\[{]/.test(String(body).trim())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeBodyValue(value) {
|
|
29
|
+
if (value === undefined || value === null) {
|
|
30
|
+
return {
|
|
31
|
+
body: '',
|
|
32
|
+
data: undefined,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof value === 'string') {
|
|
37
|
+
return {
|
|
38
|
+
body: value,
|
|
39
|
+
data: undefined,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
body: JSON.stringify(value),
|
|
45
|
+
data: value,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeResponse({
|
|
50
|
+
raw,
|
|
51
|
+
status,
|
|
52
|
+
statusText = '',
|
|
53
|
+
headers = {},
|
|
54
|
+
url,
|
|
55
|
+
redirected = false,
|
|
56
|
+
responseBody,
|
|
57
|
+
}) {
|
|
58
|
+
const normalizedHeaders = new Headers(headers)
|
|
59
|
+
const contentType = normalizedHeaders.get('content-type') || ''
|
|
60
|
+
let { body, data } = normalizeBodyValue(responseBody)
|
|
61
|
+
|
|
62
|
+
if (data === undefined && looksLikeJson({ contentType, body })) {
|
|
63
|
+
data = JSON.parse(body)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
raw,
|
|
68
|
+
ok: status >= 200 && status < 400,
|
|
69
|
+
status,
|
|
70
|
+
statusText,
|
|
71
|
+
url,
|
|
72
|
+
redirected,
|
|
73
|
+
headers: normalizedHeaders,
|
|
74
|
+
body,
|
|
75
|
+
data,
|
|
76
|
+
header(name) {
|
|
77
|
+
return normalizedHeaders.get(name)
|
|
78
|
+
},
|
|
79
|
+
async text() {
|
|
80
|
+
return body
|
|
81
|
+
},
|
|
82
|
+
async json() {
|
|
83
|
+
return data
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function resolveRequestConfig({ sails, getConfig }) {
|
|
89
|
+
const soundingConfig =
|
|
90
|
+
(typeof getConfig === 'function' ? getConfig() : null) || sails?.config?.sounding || {}
|
|
91
|
+
|
|
92
|
+
return soundingConfig.request || {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveBaseUrl({ sails, getConfig, override }) {
|
|
96
|
+
if (override) {
|
|
97
|
+
return trimTrailingSlash(override)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const requestConfig = resolveRequestConfig({ sails, getConfig })
|
|
101
|
+
if (requestConfig.baseUrl) {
|
|
102
|
+
return trimTrailingSlash(requestConfig.baseUrl)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const soundingConfig =
|
|
106
|
+
(typeof getConfig === 'function' ? getConfig() : null) || sails?.config?.sounding || {}
|
|
107
|
+
|
|
108
|
+
if (soundingConfig.browser?.baseUrl) {
|
|
109
|
+
return trimTrailingSlash(soundingConfig.browser.baseUrl)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const address = sails?.hooks?.http?.server?.address?.()
|
|
113
|
+
if (address && typeof address === 'object' && address.port) {
|
|
114
|
+
const host =
|
|
115
|
+
!address.address || address.address === '::' || address.address === '0.0.0.0'
|
|
116
|
+
? '127.0.0.1'
|
|
117
|
+
: address.address
|
|
118
|
+
|
|
119
|
+
return `http://${host}:${address.port}`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (sails?.config?.port) {
|
|
123
|
+
return `http://127.0.0.1:${sails.config.port}`
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw new Error(
|
|
127
|
+
'Sounding could not resolve a base URL for HTTP request trials. Configure `sounding.request.baseUrl`, `sounding.browser.baseUrl`, or lift Sails with the HTTP hook.'
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resolveUrl({ sails, getConfig, target, baseUrl }) {
|
|
132
|
+
if (isAbsoluteUrl(target)) {
|
|
133
|
+
return target
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const resolvedBaseUrl = resolveBaseUrl({
|
|
137
|
+
sails,
|
|
138
|
+
getConfig,
|
|
139
|
+
override: baseUrl,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if (target.startsWith('/')) {
|
|
143
|
+
return `${resolvedBaseUrl}${target}`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return `${resolvedBaseUrl}/${target}`
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizePayload(method, payload) {
|
|
150
|
+
if (payload === undefined) {
|
|
151
|
+
return undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (['GET', 'HEAD', 'DELETE'].includes(method)) {
|
|
155
|
+
return payload
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (isPlainObject(payload) || Array.isArray(payload)) {
|
|
159
|
+
return payload
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return payload
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function resolveTransport({ sails, getConfig, target, options = {} }) {
|
|
166
|
+
if (options.transport) {
|
|
167
|
+
return options.transport
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (isAbsoluteUrl(target) || options.baseUrl) {
|
|
171
|
+
return 'http'
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const requestConfig = resolveRequestConfig({ sails, getConfig })
|
|
175
|
+
return requestConfig.transport || 'virtual'
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function normalizeVirtualUrl(method, target, payload) {
|
|
179
|
+
if (
|
|
180
|
+
(method === 'GET' || method === 'HEAD' || method === 'DELETE') &&
|
|
181
|
+
isPlainObject(payload)
|
|
182
|
+
) {
|
|
183
|
+
const stringifiedParams = QS.stringify(payload)
|
|
184
|
+
const queryStringPos = target.indexOf('?')
|
|
185
|
+
|
|
186
|
+
if (queryStringPos === -1) {
|
|
187
|
+
return `${target}?${stringifiedParams}`
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return `${target.substring(0, queryStringPos)}?${stringifiedParams}`
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return target
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function createFlash(session = {}) {
|
|
197
|
+
const flashStore = (session.__soundingFlashStore ||= {})
|
|
198
|
+
|
|
199
|
+
return function flash(key, value) {
|
|
200
|
+
if (arguments.length === 1) {
|
|
201
|
+
const messages = flashStore[key] || []
|
|
202
|
+
delete flashStore[key]
|
|
203
|
+
return messages
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
flashStore[key] ||= []
|
|
207
|
+
flashStore[key].push(value)
|
|
208
|
+
return flashStore[key]
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
class MockClientResponse extends Transform {
|
|
213
|
+
_transform(chunk, _encoding, next) {
|
|
214
|
+
this.push(chunk)
|
|
215
|
+
next()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function createVirtualTransport({ sails }) {
|
|
220
|
+
if (typeof sails?.router?.route !== 'function') {
|
|
221
|
+
throw new Error(
|
|
222
|
+
'Sounding could not find `sails.router.route()`. Virtual request transport requires a loaded Sails app.'
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
async send(method, target, payload, options = {}) {
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
const session = options.session || defaultSessionState()
|
|
230
|
+
const clientRes = new MockClientResponse()
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
clientRes.on('finish', () => {
|
|
234
|
+
try {
|
|
235
|
+
clientRes.body = clientRes.read()
|
|
236
|
+
clientRes.body = clientRes.body?.toString()
|
|
237
|
+
} catch {}
|
|
238
|
+
|
|
239
|
+
if (!clientRes.body) {
|
|
240
|
+
delete clientRes.body
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (
|
|
244
|
+
clientRes.body !== undefined &&
|
|
245
|
+
clientRes.headers?.['content-type'] === 'application/json'
|
|
246
|
+
) {
|
|
247
|
+
clientRes.body = JSON.parse(clientRes.body)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const status = clientRes.statusCode || 500
|
|
251
|
+
const responseBody = clientRes.body
|
|
252
|
+
|
|
253
|
+
resolve(
|
|
254
|
+
normalizeResponse({
|
|
255
|
+
raw: clientRes,
|
|
256
|
+
status,
|
|
257
|
+
statusText: clientRes.statusMessage || '',
|
|
258
|
+
headers: clientRes.headers || {},
|
|
259
|
+
url: target,
|
|
260
|
+
redirected: status >= 300 && status < 400,
|
|
261
|
+
responseBody,
|
|
262
|
+
})
|
|
263
|
+
)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
clientRes.on('error', (error) => {
|
|
267
|
+
reject(error || new Error('Error on virtual response stream'))
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
sails.router.route(
|
|
271
|
+
{
|
|
272
|
+
method,
|
|
273
|
+
url: normalizeVirtualUrl(method, target, normalizePayload(method, payload)),
|
|
274
|
+
body: ['GET', 'HEAD', 'DELETE'].includes(method)
|
|
275
|
+
? undefined
|
|
276
|
+
: normalizePayload(method, payload),
|
|
277
|
+
headers: {
|
|
278
|
+
...(options.headers || {}),
|
|
279
|
+
nosession: 'true',
|
|
280
|
+
},
|
|
281
|
+
session,
|
|
282
|
+
flash: createFlash(session),
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
_clientRes: clientRes,
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
} catch (error) {
|
|
289
|
+
reject(error)
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function defaultSessionState() {
|
|
298
|
+
return {}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function normalizeBodyAndHeaders(method, payload, headers) {
|
|
302
|
+
if (payload === undefined || method === 'GET' || method === 'HEAD') {
|
|
303
|
+
return {
|
|
304
|
+
body: undefined,
|
|
305
|
+
headers,
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (
|
|
310
|
+
typeof payload === 'string' ||
|
|
311
|
+
payload instanceof URLSearchParams ||
|
|
312
|
+
(typeof FormData !== 'undefined' && payload instanceof FormData) ||
|
|
313
|
+
payload instanceof ArrayBuffer ||
|
|
314
|
+
ArrayBuffer.isView(payload)
|
|
315
|
+
) {
|
|
316
|
+
return {
|
|
317
|
+
body: payload,
|
|
318
|
+
headers,
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (isPlainObject(payload) || Array.isArray(payload)) {
|
|
323
|
+
if (!headers.has('content-type')) {
|
|
324
|
+
headers.set('content-type', 'application/json')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
body: JSON.stringify(payload),
|
|
329
|
+
headers,
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
body: payload,
|
|
335
|
+
headers,
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function createHttpTransport({
|
|
340
|
+
sails,
|
|
341
|
+
getConfig,
|
|
342
|
+
fetchImplementation = globalThis.fetch,
|
|
343
|
+
}) {
|
|
344
|
+
if (typeof fetchImplementation !== 'function') {
|
|
345
|
+
throw new Error('Sounding could not find a fetch implementation for HTTP request trials.')
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
async send(method, target, payload, options = {}) {
|
|
350
|
+
const headers = new Headers({
|
|
351
|
+
accept: 'application/json',
|
|
352
|
+
...(options.headers || {}),
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
const { body, headers: finalHeaders } = normalizeBodyAndHeaders(method, payload, headers)
|
|
356
|
+
|
|
357
|
+
const response = await fetchImplementation(
|
|
358
|
+
resolveUrl({
|
|
359
|
+
sails,
|
|
360
|
+
getConfig,
|
|
361
|
+
target,
|
|
362
|
+
baseUrl: options.baseUrl,
|
|
363
|
+
}),
|
|
364
|
+
{
|
|
365
|
+
method,
|
|
366
|
+
redirect: options.redirect || 'manual',
|
|
367
|
+
...options,
|
|
368
|
+
headers: finalHeaders,
|
|
369
|
+
body,
|
|
370
|
+
}
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
return normalizeResponse({
|
|
374
|
+
raw: response,
|
|
375
|
+
status: response.status,
|
|
376
|
+
statusText: response.statusText,
|
|
377
|
+
headers: response.headers,
|
|
378
|
+
url: response.url,
|
|
379
|
+
redirected: response.redirected,
|
|
380
|
+
responseBody: await response.text(),
|
|
381
|
+
})
|
|
382
|
+
},
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function createRequestClient({
|
|
387
|
+
sails,
|
|
388
|
+
getConfig,
|
|
389
|
+
fetchImplementation = globalThis.fetch,
|
|
390
|
+
defaultHeaders = {},
|
|
391
|
+
defaultSession = {},
|
|
392
|
+
transportOverride,
|
|
393
|
+
} = {}) {
|
|
394
|
+
let virtualTransport = null
|
|
395
|
+
let httpTransport = null
|
|
396
|
+
|
|
397
|
+
function getVirtualTransport() {
|
|
398
|
+
virtualTransport ||= createVirtualTransport({ sails })
|
|
399
|
+
return virtualTransport
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function getHttpTransport() {
|
|
403
|
+
httpTransport ||= createHttpTransport({
|
|
404
|
+
sails,
|
|
405
|
+
getConfig,
|
|
406
|
+
fetchImplementation,
|
|
407
|
+
})
|
|
408
|
+
return httpTransport
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function send(method, target, payloadOrOptions, maybeOptions) {
|
|
412
|
+
const hasPayload = !['GET', 'HEAD'].includes(method)
|
|
413
|
+
const payload = hasPayload ? payloadOrOptions : undefined
|
|
414
|
+
const options = (hasPayload ? maybeOptions : payloadOrOptions) || {}
|
|
415
|
+
const headers = {
|
|
416
|
+
...defaultHeaders,
|
|
417
|
+
...(options.headers || {}),
|
|
418
|
+
}
|
|
419
|
+
const session = options.session
|
|
420
|
+
? {
|
|
421
|
+
...defaultSession,
|
|
422
|
+
...options.session,
|
|
423
|
+
}
|
|
424
|
+
: defaultSession
|
|
425
|
+
const transport = resolveTransport({
|
|
426
|
+
sails,
|
|
427
|
+
getConfig,
|
|
428
|
+
target,
|
|
429
|
+
options: {
|
|
430
|
+
...options,
|
|
431
|
+
transport: options.transport || transportOverride,
|
|
432
|
+
},
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
const transportOptions = {
|
|
436
|
+
...options,
|
|
437
|
+
headers,
|
|
438
|
+
session,
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (transport === 'virtual') {
|
|
442
|
+
return getVirtualTransport().send(method, target, payload, transportOptions)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (transport === 'http') {
|
|
446
|
+
return getHttpTransport().send(method, target, payload, transportOptions)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
throw new Error(`Unknown Sounding request transport: ${transport}`)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
get transport() {
|
|
454
|
+
return transportOverride || resolveRequestConfig({ sails, getConfig }).transport || 'virtual'
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
async request(method, target, options = {}) {
|
|
458
|
+
return send(method.toUpperCase(), target, undefined, options)
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
get(target, options = {}) {
|
|
462
|
+
return send('GET', target, options)
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
head(target, options = {}) {
|
|
466
|
+
return send('HEAD', target, options)
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
post(target, payload, options = {}) {
|
|
470
|
+
return send('POST', target, payload, options)
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
put(target, payload, options = {}) {
|
|
474
|
+
return send('PUT', target, payload, options)
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
patch(target, payload, options = {}) {
|
|
478
|
+
return send('PATCH', target, payload, options)
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
delete(target, payload, options = {}) {
|
|
482
|
+
return send('DELETE', target, payload, options)
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
withHeaders(headers = {}) {
|
|
486
|
+
return createRequestClient({
|
|
487
|
+
sails,
|
|
488
|
+
getConfig,
|
|
489
|
+
fetchImplementation,
|
|
490
|
+
defaultHeaders: {
|
|
491
|
+
...defaultHeaders,
|
|
492
|
+
...headers,
|
|
493
|
+
},
|
|
494
|
+
defaultSession,
|
|
495
|
+
transportOverride,
|
|
496
|
+
})
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
withSession(session = {}) {
|
|
500
|
+
return createRequestClient({
|
|
501
|
+
sails,
|
|
502
|
+
getConfig,
|
|
503
|
+
fetchImplementation,
|
|
504
|
+
defaultHeaders,
|
|
505
|
+
defaultSession: {
|
|
506
|
+
...defaultSession,
|
|
507
|
+
...session,
|
|
508
|
+
},
|
|
509
|
+
transportOverride,
|
|
510
|
+
})
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
using(transport) {
|
|
514
|
+
return createRequestClient({
|
|
515
|
+
sails,
|
|
516
|
+
getConfig,
|
|
517
|
+
fetchImplementation,
|
|
518
|
+
defaultHeaders,
|
|
519
|
+
defaultSession,
|
|
520
|
+
transportOverride: transport,
|
|
521
|
+
})
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
as(actor) {
|
|
525
|
+
if (!actor) {
|
|
526
|
+
return this
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const actorHeaders = actor.headers || actor.sounding?.headers || {}
|
|
530
|
+
const actorSession = actor.session ||
|
|
531
|
+
actor.sounding?.session || {
|
|
532
|
+
...(actor.id ? { userId: actor.id } : {}),
|
|
533
|
+
...(actor.team ? { teamId: actor.team } : {}),
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return this.withHeaders(actorHeaders).withSession(actorSession)
|
|
537
|
+
},
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
module.exports = {
|
|
542
|
+
createRequestClient,
|
|
543
|
+
createVirtualTransport,
|
|
544
|
+
createHttpTransport,
|
|
545
|
+
normalizeResponse,
|
|
546
|
+
resolveBaseUrl,
|
|
547
|
+
resolveTransport,
|
|
548
|
+
resolveUrl,
|
|
549
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const path = require('node:path')
|
|
2
|
+
|
|
3
|
+
const { createMailbox } = require('./create-mailbox')
|
|
4
|
+
const { createMailCapture } = require('./create-mail-capture')
|
|
5
|
+
const { createWorldEngine } = require('./create-world-engine')
|
|
6
|
+
const { loadWorldFiles } = require('./create-world-loader')
|
|
7
|
+
const { createHelperRunner } = require('./create-helper-runner')
|
|
8
|
+
const { createRequestClient } = require('./create-request-client')
|
|
9
|
+
const { createVisitClient } = require('./create-visit-client')
|
|
10
|
+
const { createBrowserManager } = require('./create-browser-manager')
|
|
11
|
+
const { createAuthHelpers } = require('./create-auth-helpers')
|
|
12
|
+
const { getDefaultConfig } = require('./default-config')
|
|
13
|
+
const { mergeConfig } = require('./merge-config')
|
|
14
|
+
const { normalizeUserConfig } = require('./normalize-config')
|
|
15
|
+
const { resolveDatastore } = require('./resolve-datastore')
|
|
16
|
+
|
|
17
|
+
function resolveConfig(sails) {
|
|
18
|
+
return mergeConfig(getDefaultConfig(), normalizeUserConfig(sails.config?.sounding || {}))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveAppPath(sails, config) {
|
|
22
|
+
const basePath = sails?.config?.appPath || process.cwd()
|
|
23
|
+
return path.resolve(basePath, config.app?.path || '.')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createRuntime(sails) {
|
|
27
|
+
const mailbox = createMailbox()
|
|
28
|
+
const world = createWorldEngine({ sails })
|
|
29
|
+
const helpers = createHelperRunner({ sails })
|
|
30
|
+
const request = createRequestClient({
|
|
31
|
+
sails,
|
|
32
|
+
getConfig: () => resolveConfig(sails),
|
|
33
|
+
})
|
|
34
|
+
const visit = createVisitClient({ request })
|
|
35
|
+
const browser = createBrowserManager({
|
|
36
|
+
sails,
|
|
37
|
+
getConfig: () => resolveConfig(sails),
|
|
38
|
+
appPathResolver: () => resolveAppPath(sails, resolveConfig(sails)),
|
|
39
|
+
})
|
|
40
|
+
const auth = createAuthHelpers({
|
|
41
|
+
sails,
|
|
42
|
+
world,
|
|
43
|
+
mailbox,
|
|
44
|
+
request,
|
|
45
|
+
})
|
|
46
|
+
const mailCapture = createMailCapture({
|
|
47
|
+
sails,
|
|
48
|
+
mailbox,
|
|
49
|
+
getConfig: () => resolveConfig(sails),
|
|
50
|
+
})
|
|
51
|
+
let bootState = null
|
|
52
|
+
let datastoreState = null
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
get config() {
|
|
56
|
+
return resolveConfig(sails)
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
get appPath() {
|
|
60
|
+
return resolveAppPath(sails, this.config)
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
get mailbox() {
|
|
64
|
+
return mailbox
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
get world() {
|
|
68
|
+
return world
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
get helpers() {
|
|
72
|
+
return helpers
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Temporary compatibility alias while the DX settles.
|
|
76
|
+
get helper() {
|
|
77
|
+
return helpers
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
get request() {
|
|
81
|
+
return request
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
get visit() {
|
|
85
|
+
return visit
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
get browser() {
|
|
89
|
+
return browser
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
get auth() {
|
|
93
|
+
return auth
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
configure() {
|
|
97
|
+
datastoreState = resolveDatastore({
|
|
98
|
+
sails,
|
|
99
|
+
soundingConfig: this.config,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
return datastoreState
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
get datastore() {
|
|
106
|
+
return datastoreState
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async boot(options = {}) {
|
|
110
|
+
if (!datastoreState) {
|
|
111
|
+
datastoreState = this.configure()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
world.reset({ preserveSequences: true })
|
|
115
|
+
const loadedWorldFiles = await loadWorldFiles({
|
|
116
|
+
world,
|
|
117
|
+
appPath: this.appPath,
|
|
118
|
+
config: this.config,
|
|
119
|
+
sails,
|
|
120
|
+
})
|
|
121
|
+
const captureInstalled = mailCapture.install()
|
|
122
|
+
|
|
123
|
+
bootState = {
|
|
124
|
+
bootedAt: new Date().toISOString(),
|
|
125
|
+
mode: options.mode || 'unit',
|
|
126
|
+
config: this.config,
|
|
127
|
+
datastore: datastoreState,
|
|
128
|
+
mail: {
|
|
129
|
+
captureEnabled: this.config.mail?.capture !== false,
|
|
130
|
+
captureInstalled,
|
|
131
|
+
},
|
|
132
|
+
world: {
|
|
133
|
+
loadedFiles: loadedWorldFiles,
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
sails,
|
|
139
|
+
...bootState,
|
|
140
|
+
helpers,
|
|
141
|
+
mailbox,
|
|
142
|
+
world,
|
|
143
|
+
request,
|
|
144
|
+
visit,
|
|
145
|
+
browser,
|
|
146
|
+
auth,
|
|
147
|
+
login: auth.login,
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
async lower() {
|
|
152
|
+
bootState = null
|
|
153
|
+
datastoreState = null
|
|
154
|
+
await browser.close()
|
|
155
|
+
mailCapture.uninstall()
|
|
156
|
+
mailbox.clear()
|
|
157
|
+
world.reset({ preserveSequences: true })
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
get state() {
|
|
161
|
+
return bootState
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
createRuntime,
|
|
168
|
+
resolveConfig,
|
|
169
|
+
resolveAppPath,
|
|
170
|
+
}
|