@toa.io/userland 0.7.3 → 0.8.0-dev.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/package.json +2 -2
- package/samples/readme.md +3 -4
- package/samples/src/components.js +2 -2
- package/samples/src/context.js +8 -3
- package/samples/src/suite/.read/filter.js +15 -0
- package/samples/src/suite/.read/messages.js +9 -4
- package/samples/src/suite/.read/operations.js +12 -6
- package/samples/src/suite/.read/parse.js +0 -4
- package/samples/src/suite/components.js +7 -4
- package/samples/src/suite/context.js +4 -3
- package/samples/test/components.test.js +5 -2
- package/samples/test/context/components/dummy/samples/messages/somewhere.something.happened.yaml +6 -0
- package/samples/test/context/components/pot/manifest.toa.yaml +2 -0
- package/samples/test/context/components/pot/samples/do.yaml +11 -0
- package/samples/test/context/components/{ok → pot}/samples/messages/somewhere.something.happened.yaml +1 -1
- package/samples/test/context.test.js +7 -3
- package/samples/test/suite.components.test.js +67 -4
- package/samples/types/replay.d.ts +3 -3
- package/samples/types/suite.d.ts +9 -0
- /package/samples/test/context/components/{ok → dummy}/manifest.toa.yaml +0 -0
- /package/samples/test/context/components/{ok → dummy}/samples/do.yaml +0 -0
- /package/samples/test/context/components/{ok → dummy}/samples/dummies.dummy.do.yaml +0 -0
- /package/samples/test/context/components/{ok → dummy}/samples/dummies.dummy.undo.yaml +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/userland",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-dev.0",
|
|
4
4
|
"description": "Toa development kit",
|
|
5
5
|
"homepage": "https://toa.io",
|
|
6
6
|
"author": {
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"@toa.io/yaml": "0.7.1",
|
|
34
34
|
"tap": "16.3.4"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "c1fcff1dfbd211ed631d2f521a51bc1584af4b8c"
|
|
37
37
|
}
|
package/samples/readme.md
CHANGED
|
@@ -15,11 +15,10 @@ See [features](/features/replay).
|
|
|
15
15
|
|
|
16
16
|
Sample is an object containing values of operation inputs (i.e.: request, context outputs and
|
|
17
17
|
current state) to be substituted and outcomes (reply, context calls, next state and events emission)
|
|
18
|
-
to be verified. See its [schema](./src/.suite
|
|
18
|
+
to be verified. See its [schema](./src/.replay/.suite/translate/schemas/operation.cos.yaml).
|
|
19
19
|
|
|
20
20
|
> Although `input` and `output` are declared as arbitrary values, they must conform to the
|
|
21
|
-
> corresponding
|
|
22
|
-
> operation schemas.
|
|
21
|
+
> corresponding operation schemas.
|
|
23
22
|
|
|
24
23
|
### Declaration
|
|
25
24
|
|
|
@@ -35,7 +34,7 @@ and `name` must match corresponding component, therefore are optional.
|
|
|
35
34
|
|
|
36
35
|
Message Sample is an object containing receiver's input (`payload`) to be substituted and
|
|
37
36
|
outcomes (`input` and `query`) to be verified. Message sample may contain corresponding operation
|
|
38
|
-
sample. See its [schema](
|
|
37
|
+
sample. See its [schema](./src/.replay/.suite/translate/schemas/message.cos.yaml).
|
|
39
38
|
|
|
40
39
|
> Message samples are always [autonomous](#autonomy).
|
|
41
40
|
|
|
@@ -4,8 +4,8 @@ const { components: load } = require('./suite')
|
|
|
4
4
|
const { replay } = require('./replay')
|
|
5
5
|
|
|
6
6
|
/** @type {toa.samples.replay.components} */
|
|
7
|
-
const components = async (paths) => {
|
|
8
|
-
const suite = await load(paths)
|
|
7
|
+
const components = async (paths, options = {}) => {
|
|
8
|
+
const suite = await load(paths, options)
|
|
9
9
|
|
|
10
10
|
return await replay(suite, paths)
|
|
11
11
|
}
|
package/samples/src/context.js
CHANGED
|
@@ -7,12 +7,17 @@ const { context: load } = require('./suite')
|
|
|
7
7
|
const { replay } = require('./replay')
|
|
8
8
|
|
|
9
9
|
/** @type {toa.samples.replay.context} */
|
|
10
|
-
const context = async (path) => {
|
|
10
|
+
const context = async (path, options = {}) => {
|
|
11
11
|
const context = await norm.context(path)
|
|
12
12
|
const paths = context.components.map((component) => component.path)
|
|
13
|
-
const suite = await load(path)
|
|
13
|
+
const suite = await load(path, options)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
let ok = true
|
|
16
|
+
|
|
17
|
+
if (options.integration !== true) ok = await test.components(paths, options)
|
|
18
|
+
if (ok) ok = await replay(suite, paths)
|
|
19
|
+
|
|
20
|
+
return ok
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
exports.context = context
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template {{ title: string }} T
|
|
5
|
+
* @param {T[]} samples
|
|
6
|
+
* @param {string} expression
|
|
7
|
+
* @returns {T}
|
|
8
|
+
*/
|
|
9
|
+
function filter (samples, expression) {
|
|
10
|
+
const rx = new RegExp(expression)
|
|
11
|
+
|
|
12
|
+
return samples.filter((sample) => rx.test(sample.title))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
exports.filter = filter
|
|
@@ -4,12 +4,14 @@ const { join, basename } = require('node:path')
|
|
|
4
4
|
const { file: { glob } } = require('@toa.io/filesystem')
|
|
5
5
|
const yaml = require('@toa.io/yaml')
|
|
6
6
|
|
|
7
|
+
const { filter } = require('./filter')
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* @param {string} path
|
|
9
|
-
* @param {
|
|
11
|
+
* @param {toa.samples.suite.Options} options
|
|
10
12
|
* @returns {Promise<toa.samples.messages.Set>}
|
|
11
13
|
*/
|
|
12
|
-
const messages = async (path,
|
|
14
|
+
const messages = async (path, options) => {
|
|
13
15
|
/** @type {toa.samples.messages.Set} */
|
|
14
16
|
const messages = {}
|
|
15
17
|
|
|
@@ -18,9 +20,12 @@ const messages = async (path, id) => {
|
|
|
18
20
|
|
|
19
21
|
for (const file of files) {
|
|
20
22
|
const label = basename(file, EXTENSION)
|
|
21
|
-
const samples = /** @type {toa.samples.Message[]} */ await yaml.load.all(file)
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
let samples = /** @type {toa.samples.Message[]} */ await yaml.load.all(file)
|
|
25
|
+
|
|
26
|
+
if (options.id !== undefined) samples.forEach((sample) => (sample.component = options.id))
|
|
27
|
+
if (options.component !== undefined) samples = samples.filter((sample) => sample.component === options.component)
|
|
28
|
+
if (options.title !== undefined) samples = filter(samples, options.title)
|
|
24
29
|
|
|
25
30
|
messages[label] = samples
|
|
26
31
|
}
|
|
@@ -6,13 +6,14 @@ const { merge } = require('@toa.io/generic')
|
|
|
6
6
|
const yaml = require('@toa.io/yaml')
|
|
7
7
|
|
|
8
8
|
const { parse } = require('./parse')
|
|
9
|
+
const { filter } = require('./filter')
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @param {string} path
|
|
12
|
-
* @param {
|
|
13
|
+
* @param {toa.samples.suite.Options} options
|
|
13
14
|
* @returns {Promise<toa.samples.suite.Operations>}
|
|
14
15
|
*/
|
|
15
|
-
const operations = async (path,
|
|
16
|
+
const operations = async (path, options) => {
|
|
16
17
|
/** @type {toa.samples.suite.Operations} */
|
|
17
18
|
const operations = {}
|
|
18
19
|
|
|
@@ -21,10 +22,15 @@ const operations = async (path, id) => {
|
|
|
21
22
|
|
|
22
23
|
for (const file of files) {
|
|
23
24
|
const name = basename(file, EXTENSION)
|
|
24
|
-
const [component, operation] = parse(name, id)
|
|
25
|
+
const [component, operation] = parse(name, options.id)
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
if (options.component !== undefined && component !== options.component) continue
|
|
28
|
+
if (options.operation !== undefined && operation !== options.operation) continue
|
|
29
|
+
|
|
30
|
+
let samples = /** @type {toa.samples.Operation[]} */ await yaml.load.all(file)
|
|
31
|
+
|
|
32
|
+
if (options.title !== undefined) samples = filter(samples, options.title)
|
|
33
|
+
if (samples.length === 0) continue
|
|
28
34
|
|
|
29
35
|
if (operations[component] === undefined) operations[component] = {}
|
|
30
36
|
|
|
@@ -32,7 +38,7 @@ const operations = async (path, id) => {
|
|
|
32
38
|
const set = operations[component]
|
|
33
39
|
|
|
34
40
|
if (set[operation] === undefined) set[operation] = samples
|
|
35
|
-
else set[operation] = merge(set[operation], samples)
|
|
41
|
+
else set[operation] = /** @type {toa.samples.Operation[]} */ merge(set[operation], samples)
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
return operations
|
|
@@ -14,10 +14,6 @@ const parse = (name, def) => {
|
|
|
14
14
|
throw new Error(`Component id mismatch: '${id}' expected, '${component}' given`)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// if (id === undefined) {
|
|
18
|
-
// throw new Error('Sample file name must be an operation endpoint')
|
|
19
|
-
// }
|
|
20
|
-
|
|
21
17
|
return [id, endpoint]
|
|
22
18
|
}
|
|
23
19
|
|
|
@@ -7,17 +7,20 @@ const read = require('./.read')
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @param {string[]} paths
|
|
10
|
+
* @param {toa.samples.suite.Options} [options]
|
|
10
11
|
* @returns {Promise<toa.samples.Suite>}
|
|
11
12
|
*/
|
|
12
|
-
const components = async (paths) => {
|
|
13
|
+
const components = async (paths, options = {}) => {
|
|
13
14
|
/** @type {toa.samples.Suite} */
|
|
14
15
|
const suite = { title: 'Component samples', autonomous: true, operations: {}, messages: {} }
|
|
15
16
|
|
|
16
17
|
for (const path of paths) {
|
|
17
18
|
const manifest = await norm.component(path)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
|
|
20
|
+
options.id = manifest.locator.id
|
|
21
|
+
|
|
22
|
+
const operations = await read.operations(path, options)
|
|
23
|
+
const messages = await read.messages(path, options)
|
|
21
24
|
|
|
22
25
|
merge(suite, { operations, messages })
|
|
23
26
|
}
|
|
@@ -4,14 +4,15 @@ const read = require('./.read')
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @param {string} path
|
|
7
|
+
* @param {toa.samples.suite.Options} [options]
|
|
7
8
|
* @returns {Promise<toa.samples.Suite>}
|
|
8
9
|
*/
|
|
9
|
-
const context = async (path) => {
|
|
10
|
+
const context = async (path, options = {}) => {
|
|
10
11
|
/** @type {toa.samples.Suite} */
|
|
11
12
|
const suite = { title: 'Integration samples', autonomous: false }
|
|
12
13
|
|
|
13
|
-
suite.operations = await read.operations(path)
|
|
14
|
-
suite.messages = await read.messages(path)
|
|
14
|
+
suite.operations = await read.operations(path, options)
|
|
15
|
+
suite.messages = await read.messages(path, options)
|
|
15
16
|
|
|
16
17
|
return suite
|
|
17
18
|
}
|
|
@@ -17,15 +17,18 @@ it('should be', () => {
|
|
|
17
17
|
|
|
18
18
|
const paths = [generate()]
|
|
19
19
|
|
|
20
|
+
/** @type {toa.samples.suite.Options} */
|
|
21
|
+
const options = { component: generate() }
|
|
22
|
+
|
|
20
23
|
/** @type {boolean} */
|
|
21
24
|
let result
|
|
22
25
|
|
|
23
26
|
beforeAll(async () => {
|
|
24
|
-
result = await components(paths)
|
|
27
|
+
result = await components(paths, options)
|
|
25
28
|
})
|
|
26
29
|
|
|
27
30
|
it('should load suite', async () => {
|
|
28
|
-
expect(mock.suite.components).toHaveBeenCalledWith(paths)
|
|
31
|
+
expect(mock.suite.components).toHaveBeenCalledWith(paths, options)
|
|
29
32
|
})
|
|
30
33
|
|
|
31
34
|
it('should replay suite', async () => {
|
|
@@ -14,6 +14,7 @@ jest.mock('../src/replay', () => mock.replay)
|
|
|
14
14
|
jest.mock('../src/components', () => mock.components)
|
|
15
15
|
|
|
16
16
|
const { context } = require('../src/context')
|
|
17
|
+
const { generate } = require('randomstring')
|
|
17
18
|
|
|
18
19
|
it('should be', async () => {
|
|
19
20
|
expect(context).toBeDefined()
|
|
@@ -25,6 +26,9 @@ const COMPONENTS = resolve(CONTEXT, 'components/*')
|
|
|
25
26
|
/** @type {string[]} */
|
|
26
27
|
let paths
|
|
27
28
|
|
|
29
|
+
/** @type {toa.samples.suite.Options} */
|
|
30
|
+
const options = { component: generate() }
|
|
31
|
+
|
|
28
32
|
/** @type {boolean} */
|
|
29
33
|
let ok
|
|
30
34
|
|
|
@@ -37,15 +41,15 @@ beforeEach(async () => {
|
|
|
37
41
|
|
|
38
42
|
mock.components.components.mockImplementation(async () => true)
|
|
39
43
|
|
|
40
|
-
ok = await context(CONTEXT)
|
|
44
|
+
ok = await context(CONTEXT, options)
|
|
41
45
|
})
|
|
42
46
|
|
|
43
47
|
it('should replay context components sample sets', async () => {
|
|
44
|
-
expect(mock.components.components).toHaveBeenCalledWith(paths)
|
|
48
|
+
expect(mock.components.components).toHaveBeenCalledWith(paths, options)
|
|
45
49
|
})
|
|
46
50
|
|
|
47
51
|
it('should load integration suite', async () => {
|
|
48
|
-
expect(mock.suite.context).toHaveBeenCalledWith(CONTEXT)
|
|
52
|
+
expect(mock.suite.context).toHaveBeenCalledWith(CONTEXT, options)
|
|
49
53
|
})
|
|
50
54
|
|
|
51
55
|
it('should replay integration suite', async () => {
|
|
@@ -9,14 +9,16 @@ it('should be', () => {
|
|
|
9
9
|
expect(components).toBeDefined()
|
|
10
10
|
})
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const dummy = resolve(__dirname, 'context/components/dummy')
|
|
13
|
+
const pot = resolve(__dirname, 'context/components/pot')
|
|
14
14
|
const component = 'dummies.dummy'
|
|
15
15
|
|
|
16
16
|
/** @type {toa.samples.Suite} */
|
|
17
17
|
let suite
|
|
18
18
|
|
|
19
19
|
beforeAll(async () => {
|
|
20
|
+
const paths = [dummy]
|
|
21
|
+
|
|
20
22
|
suite = await components(paths)
|
|
21
23
|
})
|
|
22
24
|
|
|
@@ -51,11 +53,72 @@ it('should load message samples', async () => {
|
|
|
51
53
|
expect(suite.messages).toStrictEqual(expected)
|
|
52
54
|
})
|
|
53
55
|
|
|
56
|
+
describe('options', () => {
|
|
57
|
+
const paths = [dummy, pot]
|
|
58
|
+
|
|
59
|
+
it('should filter samples by component id', async () => {
|
|
60
|
+
/** @type {toa.samples.suite.Options} */
|
|
61
|
+
const options = { component: 'dummies.dummy' }
|
|
62
|
+
|
|
63
|
+
suite = await components(paths, options)
|
|
64
|
+
|
|
65
|
+
expect(suite.operations['dummies.pot']).toBeUndefined()
|
|
66
|
+
|
|
67
|
+
const messages = suite.messages['somewhere.something.happened']
|
|
68
|
+
|
|
69
|
+
expect(messages.length).toStrictEqual(1)
|
|
70
|
+
expect(messages[0].component).toStrictEqual('dummies.dummy')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should filter samples by operation name', async () => {
|
|
74
|
+
/** @type {toa.samples.suite.Options} */
|
|
75
|
+
const options = { operation: 'do' }
|
|
76
|
+
|
|
77
|
+
suite = await components(paths, options)
|
|
78
|
+
|
|
79
|
+
expect('undo' in suite.operations['dummies.dummy']).toStrictEqual(false)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should filter operation samples by title', async () => {
|
|
83
|
+
/** @type {toa.samples.suite.Options} */
|
|
84
|
+
const options = { title: 'Should not undo' }
|
|
85
|
+
|
|
86
|
+
suite = await components(paths, options)
|
|
87
|
+
|
|
88
|
+
expect('do' in suite.operations['dummies.dummy']).toStrictEqual(false)
|
|
89
|
+
expect(suite.operations['dummies.dummy'].undo.length).toStrictEqual(1)
|
|
90
|
+
expect(suite.operations['dummies.dummy'].undo[0].title).toStrictEqual(options.title)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should filter operation samples by title as regexp', async () => {
|
|
94
|
+
/** @type {toa.samples.suite.Options} */
|
|
95
|
+
const options = { title: 'Should [a-z]{2}t undo' }
|
|
96
|
+
|
|
97
|
+
suite = await components(paths, options)
|
|
98
|
+
|
|
99
|
+
expect(suite.operations['dummies.dummy']?.do).toBeUndefined()
|
|
100
|
+
expect(suite.operations['dummies.dummy'].undo.length).toStrictEqual(1)
|
|
101
|
+
expect(suite.operations['dummies.dummy'].undo[0].title).toStrictEqual('Should not undo')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should filter message samples by title', async () => {
|
|
105
|
+
/** @type {toa.samples.suite.Options} */
|
|
106
|
+
const options = { title: 'Something happened with a dummy' }
|
|
107
|
+
|
|
108
|
+
suite = await components(paths, options)
|
|
109
|
+
|
|
110
|
+
const messages = suite.messages['somewhere.something.happened']
|
|
111
|
+
|
|
112
|
+
expect(messages.length).toStrictEqual(1)
|
|
113
|
+
expect(messages[0].title).toStrictEqual(options.title)
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
54
117
|
/**
|
|
55
118
|
* @returns {Promise<toa.samples.operations.Set>}
|
|
56
119
|
*/
|
|
57
120
|
const operations = async () => {
|
|
58
|
-
const path = resolve(
|
|
121
|
+
const path = resolve(dummy, 'samples')
|
|
59
122
|
|
|
60
123
|
/** @type {toa.samples.Operation[]} */
|
|
61
124
|
const do1 = (await yaml.load.all(resolve(path, 'do.yaml')))
|
|
@@ -77,7 +140,7 @@ const operations = async () => {
|
|
|
77
140
|
*/
|
|
78
141
|
const messages = async () => {
|
|
79
142
|
const label = 'somewhere.something.happened'
|
|
80
|
-
const file = resolve(
|
|
143
|
+
const file = resolve(dummy, 'samples/messages', label + '.yaml')
|
|
81
144
|
const declarations = await yaml.load.all(file)
|
|
82
145
|
const messages = declarations.map((sample) => ({ component, ...sample }))
|
|
83
146
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import * as _suite from
|
|
1
|
+
import * as _suite from './suite'
|
|
2
2
|
|
|
3
3
|
declare namespace toa.samples.replay {
|
|
4
4
|
|
|
5
|
-
type components = (paths: string[]) => Promise<boolean>
|
|
6
|
-
type context = (path: string) => Promise<boolean>
|
|
5
|
+
type components = (paths: string[], options?: _suite.Options) => Promise<boolean>
|
|
6
|
+
type context = (path: string, options?: _suite.Options) => Promise<boolean>
|
|
7
7
|
type replay = (suite: _suite.Suite, paths: string[]) => Promise<boolean>
|
|
8
8
|
|
|
9
9
|
}
|
package/samples/types/suite.d.ts
CHANGED
|
@@ -5,6 +5,14 @@ declare namespace toa.samples {
|
|
|
5
5
|
|
|
6
6
|
namespace suite {
|
|
7
7
|
type Operations = Record<string, _operations.Set>
|
|
8
|
+
|
|
9
|
+
type Options = {
|
|
10
|
+
id?: string
|
|
11
|
+
integration?: boolean
|
|
12
|
+
component?: string
|
|
13
|
+
operation?: string
|
|
14
|
+
title?: string
|
|
15
|
+
}
|
|
8
16
|
}
|
|
9
17
|
|
|
10
18
|
type Suite = {
|
|
@@ -17,3 +25,4 @@ declare namespace toa.samples {
|
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
export type Suite = toa.samples.Suite
|
|
28
|
+
export type Options = toa.samples.suite.Options
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|