@toa.io/userland 1.1.0-dev.0 → 1.1.1-alpha.56

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.
Files changed (33) hide show
  1. package/example/components/echo/operations/signal.js +11 -3
  2. package/example/components/echo/samples/signal.yaml +0 -3
  3. package/example/components/math.calculations/samples/add.yaml +1 -2
  4. package/example/components/math.calculations/samples/assets/ab.yaml +2 -0
  5. package/example/components/math.calculations/samples/increment.yaml +3 -6
  6. package/example/components/web/operations/get.js +4 -0
  7. package/example/components/web/samples/get.yaml +2 -0
  8. package/package.json +10 -9
  9. package/samples/readme.md +37 -3
  10. package/samples/src/.replay/.suite/component.js +3 -8
  11. package/samples/src/.replay/.suite/operation.js +1 -1
  12. package/samples/src/.replay/.suite/operations.js +5 -3
  13. package/samples/src/.replay/.suite/translate/.operation/.prepare/cast.test.js +0 -1
  14. package/samples/src/.replay/index.js +2 -0
  15. package/samples/src/.replay/stage.js +60 -0
  16. package/samples/src/.replay/suite.js +3 -2
  17. package/samples/src/.replay/test.js +5 -3
  18. package/samples/src/components.js +1 -1
  19. package/samples/src/context.js +2 -2
  20. package/samples/src/replay.js +4 -5
  21. package/samples/src/suite/components.js +3 -3
  22. package/samples/test/components.test.js +2 -2
  23. package/samples/test/context.test.js +2 -2
  24. package/samples/test/replay.test.js +108 -72
  25. package/samples/test/stage.mock.js +10 -14
  26. package/samples/test/suite.components.test.js +5 -31
  27. package/samples/types/replay.d.ts +3 -3
  28. package/samples/types/suite.d.ts +3 -1
  29. package/stage/src/component.js +7 -2
  30. package/stage/test/component.test.js +1 -1
  31. package/stage/types/index.d.ts +2 -1
  32. package/example/components/tea.pots/samples/messages/store.orders.created.yaml +0 -37
  33. package/samples/test/replay.fixtures.js +0 -72
@@ -1,7 +1,15 @@
1
1
  'use strict'
2
2
 
3
- async function computation (input, context) {
4
- return context.configuration.signal
3
+ class Computation {
4
+ #context
5
+
6
+ async mount (context) {
7
+ this.#context = context
8
+ }
9
+
10
+ async execute () {
11
+ return this.#context.configuration.signal
12
+ }
5
13
  }
6
14
 
7
- exports.computation = computation
15
+ exports.Computation = Computation
@@ -1,6 +1,3 @@
1
- title: Should echo
2
- output: quack
3
- ---
4
1
  title: Should substitute configuration
5
2
  output: woof
6
3
  configuration:
@@ -1,7 +1,6 @@
1
1
  title: Should add numbers
2
2
  input:
3
- a: 1
4
- b: 2
3
+ $import: assets/ab.yaml
5
4
  output: 3
6
5
  ---
7
6
  title: Should add negative numbers
@@ -1,6 +1,7 @@
1
1
  title: Should increment number
2
2
  input:
3
3
  value: 1
4
+ times: 1
4
5
  output: 2
5
6
  local:
6
7
  add:
@@ -9,12 +10,7 @@ local:
9
10
  b: 1
10
11
  output: 2
11
12
  ---
12
- title: Should increment with actual call
13
- input:
14
- value: 2
15
- output: 3
16
- ---
17
- title: Should increment twice (with second actual call)
13
+ title: Should increment twice
18
14
  input:
19
15
  value: 0
20
16
  times: 2
@@ -28,3 +24,4 @@ local:
28
24
  - input:
29
25
  a: 1
30
26
  b: 1
27
+ output: 2
@@ -2,6 +2,10 @@
2
2
 
3
3
  async function computation (_, context) {
4
4
  const response = await context.http.dev.path.to.resource.get()
5
+ const type = response.headers.get('content-type')
6
+
7
+ if (type !== 'application/json') return { error: { code: 0, message: 'Unsupported media type ' + type } }
8
+
5
9
  const output = await response.json()
6
10
 
7
11
  return { output }
@@ -1,5 +1,7 @@
1
1
  title: Should return response
2
2
  http:
3
+ headers:
4
+ get:sync: 'application/json'
3
5
  json:async:
4
6
  foo: bar
5
7
  output:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/userland",
3
- "version": "1.1.0-dev.0",
3
+ "version": "1.1.1-alpha.56+a80d81d4",
4
4
  "description": "Toa development kit",
5
5
  "homepage": "https://toa.io",
6
6
  "author": {
@@ -24,14 +24,15 @@
24
24
  "main": "src/index.js",
25
25
  "types": "types/index.d.ts",
26
26
  "dependencies": {
27
- "@toa.io/boot": "1.0.1-dev.0",
28
- "@toa.io/core": "1.0.1-dev.0",
29
- "@toa.io/filesystem": "1.0.1-dev.0",
30
- "@toa.io/generic": "0.10.0-dev.0",
31
- "@toa.io/norm": "1.0.1-dev.0",
32
- "@toa.io/schemas": "0.8.3-dev.0",
33
- "@toa.io/yaml": "0.7.5-dev.0",
27
+ "@toa.io/boot": "1.0.2-alpha.56+a80d81d4",
28
+ "@toa.io/core": "1.0.2-alpha.56+a80d81d4",
29
+ "@toa.io/filesystem": "1.0.2-alpha.56+a80d81d4",
30
+ "@toa.io/generic": "0.10.1-alpha.56+a80d81d4",
31
+ "@toa.io/norm": "1.0.2-alpha.56+a80d81d4",
32
+ "@toa.io/schemas": "0.8.4-alpha.56+a80d81d4",
33
+ "@toa.io/storages.null": "0.6.0",
34
+ "@toa.io/yaml": "0.7.6-alpha.56+a80d81d4",
34
35
  "tap": "16.3.4"
35
36
  },
36
- "gitHead": "5e7998b39ea6111a2cf7d38fad8aeae71d742621"
37
+ "gitHead": "a80d81d4d9da9fec07606154c959dd6512de4c76"
37
38
  }
package/samples/readme.md CHANGED
@@ -36,7 +36,7 @@ Message Sample is an object containing receiver's input (`payload`) to be substi
36
36
  outcomes (`input` and `query`) to be verified. Message sample may contain the corresponding operation
37
37
  sample. See its [schema](./src/.replay/.suite/translate/schemas/message.cos.yaml).
38
38
 
39
- > Message samples are always [autonomous](#autonomy).
39
+ > Message samples are only supported at [context level](#autonomy).
40
40
 
41
41
  ### Declaration
42
42
 
@@ -92,6 +92,40 @@ context (so as required extensions). See [examples](../example/samples).
92
92
 
93
93
  ## Replay
94
94
 
95
- Samples may be *replayed* using [`toa replay`](/runtime/cli/readme.md#replay) command.
96
-
97
95
  > Replaying samples requires local deployment environment.
96
+
97
+ ### CLI
98
+
99
+ Samples may be replayed using [`toa replay`](/runtime/cli/readme.md#replay) command.
100
+
101
+ See [features](/features/cli/replay.feature).
102
+
103
+ ### Framework
104
+
105
+ `async components(paths: string[], options?): boolean`
106
+
107
+ Replay component samples.
108
+
109
+ `async components(paths: string[], options?): boolean`
110
+
111
+ Replay context and its components' samples.
112
+
113
+ #### Options
114
+
115
+ <dl>
116
+ <dt><code><strong>id</strong>: string</code></dt>
117
+ <dd>Replay samples for a specified component</dd>
118
+
119
+ <dt><code><strong>integration</strong>: string</code></dt>
120
+ <dd>Replay samples for a specified component only</dd>
121
+ <dd></dd>
122
+
123
+ <dt><code><strong>operation</strong>: string</code></dt>
124
+ <dd>Replay samples for specified operation</dd>
125
+
126
+ <dt><code><strong>title</strong>: string</code></dt>
127
+ <dd>Replay samples with titles matching given regexp</dd>
128
+
129
+ </dl>
130
+
131
+ See [types](types/suite.d.ts).
@@ -5,21 +5,16 @@ const stage = require('@toa.io/userland/stage')
5
5
  const { operation } = require('./operation')
6
6
 
7
7
  /**
8
- *
9
- * @param {string} id
8
+ * @param {toa.core.Component} component
10
9
  * @param {toa.samples.operations.Set} set
11
10
  * @param {boolean} autonomous
12
11
  * @returns {Function}
13
12
  */
14
- const component = (id, set, autonomous) =>
13
+ const component = (component, set, autonomous) =>
15
14
  async (test) => {
16
- const remote = await stage.remote(id)
17
-
18
15
  for (const [endpoint, samples] of Object.entries(set)) {
19
- await test.test(endpoint, operation(remote, endpoint, samples, autonomous))
16
+ await test.test(endpoint, operation(component, endpoint, samples, autonomous))
20
17
  }
21
-
22
- await remote.disconnect()
23
18
  }
24
19
 
25
20
  exports.component = component
@@ -19,7 +19,7 @@ const operation = (remote, endpoint, samples, autonomous) =>
19
19
  const request = translate.operation(operation, autonomous)
20
20
  const name = operation.title ?? 'Sample #' + n
21
21
 
22
- await test.test(name, async () => remote.invoke(endpoint, request))
22
+ await test.test(name, async () => await remote.invoke(endpoint, request))
23
23
  }
24
24
 
25
25
  test.end()
@@ -4,15 +4,17 @@ const { component } = require('./component')
4
4
 
5
5
  /**
6
6
  * @param {toa.samples.suite.Operations} operations
7
+ * @param {Record<string, toa.core.Component>} components
7
8
  * @param {boolean} autonomous
8
9
  * @return {Function}
9
10
  */
10
- const operations = (operations, autonomous) =>
11
+ const operations = (operations, components, autonomous) =>
11
12
  async (test) => {
12
13
  for (const [id, set] of Object.entries(operations)) {
13
- await test.test(id, component(id, set, autonomous))
14
- }
14
+ const remote = components[id]
15
15
 
16
+ await test.test(id, component(remote, set, autonomous))
17
+ }
16
18
  }
17
19
 
18
20
  exports.operations = operations
@@ -92,4 +92,3 @@ it('should cast to async Function', async () => {
92
92
  expect(promise).toBeInstanceOf(Promise)
93
93
  await expect(promise).resolves.toStrictEqual('hello')
94
94
  })
95
-
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('./test')
4
+ const stage = require('./stage')
4
5
 
5
6
  exports.test = test
7
+ exports.stage = stage
@@ -0,0 +1,60 @@
1
+ 'use strict'
2
+
3
+ const stage = require('@toa.io/userland/stage')
4
+ const storage = require.resolve('@toa.io/storages.null')
5
+
6
+ /**
7
+ * @param {string[]} paths
8
+ * @param {boolean} autonomous
9
+ * @return {Promise<Record<string, toa.core.Component>>}
10
+ */
11
+ async function setup (paths, autonomous) {
12
+ if (!autonomous) return remotes(paths)
13
+ else return components(paths)
14
+ }
15
+
16
+ async function remotes (paths) {
17
+ await stage.composition(paths)
18
+
19
+ const remotes = {}
20
+
21
+ for (const path of paths) {
22
+ const id = await getId(path)
23
+
24
+ remotes[id] = await stage.remote(id)
25
+ }
26
+
27
+ return remotes
28
+ }
29
+
30
+ async function components (paths) {
31
+ const components = {}
32
+
33
+ process.env.TOA_SAMPLING_AUTONOMOUS = '1'
34
+
35
+ for (const path of paths) {
36
+ const id = await getId(path)
37
+
38
+ components[id] = await stage.component(path, { storage })
39
+ }
40
+
41
+ delete process.env.TOA_SAMPLING_AUTONOMOUS
42
+
43
+ return components
44
+ }
45
+
46
+ async function getId (path) {
47
+ const manifest = await stage.manifest(path)
48
+
49
+ return manifest.locator.id
50
+ }
51
+
52
+ /**
53
+ * @return {Promise<void>}
54
+ */
55
+ async function shutdown () {
56
+ await stage.shutdown()
57
+ }
58
+
59
+ exports.setup = setup
60
+ exports.shutdown = shutdown
@@ -4,12 +4,13 @@ const replay = require('./.suite')
4
4
 
5
5
  /**
6
6
  * @param {toa.samples.Suite} suite
7
+ * @param {Record<string, toa.core.Component>} components
7
8
  * @returns {Function}
8
9
  */
9
- const suite = (suite) =>
10
+ const suite = (suite, components) =>
10
11
  async (test) => {
11
12
  if ('operations' in suite) {
12
- await test.test('Operations', replay.operations(suite.operations, suite.autonomous))
13
+ await test.test('Operations', replay.operations(suite.operations, components, suite.autonomous))
13
14
  }
14
15
 
15
16
  if ('messages' in suite) {
@@ -6,12 +6,14 @@ const replay = require('./suite')
6
6
 
7
7
  /**
8
8
  * @param {toa.samples.Suite} suite
9
+ * @param {Record<string, toa.core.Component>} components
10
+ * @param {object} options
9
11
  * @return {Promise<boolean>}
10
12
  */
11
- const test = async (suite) => {
12
- const { ok } = await tap.test(suite.title, replay.suite(suite))
13
+ const test = async (suite, components, options) => {
14
+ const result = await tap.test(suite.title, options, replay.suite(suite, components))
13
15
 
14
- return ok
16
+ return result === null ? false : result.ok
15
17
  }
16
18
 
17
19
  exports.test = test
@@ -7,7 +7,7 @@ const { replay } = require('./replay')
7
7
  const components = async (paths, options = {}) => {
8
8
  const suite = await load(paths, options)
9
9
 
10
- return await replay(suite, paths)
10
+ return await replay(suite, paths, options.runner)
11
11
  }
12
12
 
13
13
  exports.components = components
@@ -9,13 +9,13 @@ const { replay } = require('./replay')
9
9
  /** @type {toa.samples.replay.context} */
10
10
  const context = async (path, options = {}) => {
11
11
  const context = await norm.context(path)
12
- const paths = context.components.map((component) => component.path)
13
12
  const suite = await load(path, options)
13
+ const paths = context.components.map((component) => component.path)
14
14
 
15
15
  let ok = true
16
16
 
17
17
  if (options.integration !== true) ok = await test.components(paths, options)
18
- if (ok) ok = await replay(suite, paths)
18
+ if (ok) ok = await replay(suite, paths, options.runner)
19
19
 
20
20
  return ok
21
21
  }
@@ -1,13 +1,12 @@
1
1
  'use strict'
2
2
 
3
- const stage = require('@toa.io/userland/stage')
4
- const { test } = require('./.replay')
3
+ const { test, stage } = require('./.replay')
5
4
 
6
5
  /** @type {toa.samples.replay.replay} */
7
- const replay = async (suite, paths) => {
8
- await stage.composition(paths)
6
+ const replay = async (suite, paths, options = undefined) => {
7
+ const components = await stage.setup(paths, suite.autonomous)
9
8
 
10
- const ok = await test(suite)
9
+ const ok = await test(suite, components, options)
11
10
 
12
11
  await stage.shutdown()
13
12
 
@@ -12,7 +12,7 @@ const read = require('./.read')
12
12
  */
13
13
  const components = async (paths, options = {}) => {
14
14
  /** @type {toa.samples.Suite} */
15
- const suite = { title: 'Component samples', autonomous: true, operations: {}, messages: {} }
15
+ const suite = { title: 'Component samples', autonomous: true, operations: {} }
16
16
 
17
17
  for (const path of paths) {
18
18
  const manifest = await norm.component(path)
@@ -20,9 +20,9 @@ const components = async (paths, options = {}) => {
20
20
  options.id = manifest.locator.id
21
21
 
22
22
  const operations = await read.operations(path, options)
23
- const messages = await read.messages(path, options)
23
+ // const messages = await read.messages(path, options)
24
24
 
25
- merge(suite, { operations, messages })
25
+ merge(suite, { operations })
26
26
  }
27
27
 
28
28
  return suite
@@ -18,7 +18,7 @@ it('should be', () => {
18
18
  const paths = [generate()]
19
19
 
20
20
  /** @type {toa.samples.suite.Options} */
21
- const options = { component: generate() }
21
+ const options = { component: generate(), runner: { [generate()]: generate() } }
22
22
 
23
23
  /** @type {boolean} */
24
24
  let result
@@ -34,7 +34,7 @@ it('should load suite', async () => {
34
34
  it('should replay suite', async () => {
35
35
  const suite = await mock.suite.components.mock.results[0].value
36
36
 
37
- expect(mock.replay.replay).toHaveBeenCalledWith(suite, paths)
37
+ expect(mock.replay.replay).toHaveBeenCalledWith(suite, paths, options.runner)
38
38
  })
39
39
 
40
40
  it('should return result', async () => {
@@ -27,7 +27,7 @@ const COMPONENTS = resolve(CONTEXT, 'components/*')
27
27
  let paths
28
28
 
29
29
  /** @type {toa.samples.suite.Options} */
30
- const options = { component: generate() }
30
+ const options = { component: generate(), runner: { [generate()]: generate() } }
31
31
 
32
32
  /** @type {boolean} */
33
33
  let ok
@@ -55,7 +55,7 @@ it('should load integration suite', async () => {
55
55
  it('should replay integration suite', async () => {
56
56
  const suite = await mock.suite.context.mock.results[0].value
57
57
 
58
- expect(replay.replay).toHaveBeenCalledWith(suite, paths)
58
+ expect(replay.replay).toHaveBeenCalledWith(suite, paths, options.runner)
59
59
  })
60
60
 
61
61
  it('should return false if components replay failed', async () => {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { resolve } = require('node:path')
4
- const clone = require('clone-deep')
4
+ const { directory } = require('@toa.io/filesystem')
5
5
 
6
6
  const { stage } = require('./stage.mock')
7
7
  const { translate } = require('./replay.translate.mock')
@@ -10,7 +10,8 @@ const mock = { stage, translate }
10
10
  jest.mock('@toa.io/userland/stage', () => mock.stage)
11
11
  jest.mock('../src/.replay/.suite/translate', () => mock.translate)
12
12
 
13
- const fixtures = require('./replay.fixtures')
13
+ const load = require('../src/suite')
14
+
14
15
  const { replay } = require('../src')
15
16
 
16
17
  it('should be', () => {
@@ -22,95 +23,130 @@ let ok
22
23
  /** @type {toa.samples.Suite} */
23
24
  let suite
24
25
 
25
- const path = resolve(__dirname, '../../example/components/math/calculations')
26
- const paths = [path]
27
-
28
- beforeEach(async () => {
26
+ beforeEach(() => {
29
27
  jest.clearAllMocks()
30
-
31
- suite = clone(fixtures.suite)
32
- ok = await replay(suite, paths)
33
28
  })
34
29
 
35
- it('should boot composition', () => {
36
- expect(stage.composition).toHaveBeenCalled()
37
- expect(stage.composition).toHaveBeenCalledWith(paths)
38
- })
30
+ describe('autonomous', () => {
31
+ let paths
39
32
 
40
- it('should invoke operations with translated samples', async () => {
41
- const translation = (declaration) => {
42
- const find = (call) => call[0].input === declaration.input
43
- const index = mock.translate.operation.mock.calls.findIndex(find)
33
+ beforeEach(async () => {
34
+ const path = resolve(__dirname, '../../example/components/tea.pots')
44
35
 
45
- return mock.translate.operation.mock.results[index].value
46
- }
36
+ paths = [path]
37
+ suite = await load.components(paths)
38
+ ok = await replay(suite, paths)
39
+ })
47
40
 
48
- /**
49
- * @param {string} component
50
- * @return {Promise<jest.MockedObject<toa.core.Component>>}
51
- */
52
- const find = async (component) => {
53
- for (let n = 0; n < stage.remote.mock.calls.length; n++) {
54
- const call = stage.remote.mock.calls[n]
41
+ it('should not boot composition for autonomous suite', async () => {
42
+ expect(stage.composition).not.toHaveBeenCalled()
43
+ })
55
44
 
56
- if (call[0] === component) return stage.remote.mock.results[n].value
45
+ it('should invoke operations with translated samples', async () => {
46
+ const component = await stage.component.mock.results[0].value
47
+
48
+ for (const set of Object.values(suite.operations)) {
49
+ for (const [endpoint, samples] of Object.entries(set)) {
50
+ for (const sample of samples) {
51
+ const request = translation(sample)
52
+
53
+ expect(component.invoke).toHaveBeenCalledWith(endpoint, request)
54
+ }
55
+ }
57
56
  }
57
+ })
58
+ })
58
59
 
59
- throw new Error(`Remote ${component} hasn't been connected`)
60
- }
60
+ describe('integration', () => {
61
+ let paths
61
62
 
62
- for (const [id, set] of Object.entries(fixtures.suite.operations)) {
63
- const remote = await find(id)
63
+ beforeEach(async () => {
64
+ const path = resolve(__dirname, '../../example')
64
65
 
65
- for (const [endpoint, samples] of Object.entries(set)) {
66
- for (const sample of samples) {
67
- const request = translation(sample)
66
+ paths = await directory.directories(resolve(path, 'components'))
67
+ suite = await load.context(path)
68
+ ok = await replay(suite, paths)
69
+ })
70
+
71
+ it('should boot composition', () => {
72
+ expect(stage.composition).toHaveBeenCalled()
73
+ expect(stage.composition).toHaveBeenCalledWith(paths)
74
+ })
75
+
76
+ it('should invoke operations with translated samples', async () => {
77
+ /**
78
+ * @param {string} component
79
+ * @return {jest.MockedObject<toa.core.Component>}
80
+ */
81
+ const getRemote = (component) => {
82
+ for (let n = 0; n < stage.remote.mock.calls.length; n++) {
83
+ const call = stage.remote.mock.calls[n]
68
84
 
69
- expect(remote.invoke).toHaveBeenCalledWith(endpoint, request)
85
+ if (call[0] === component) return stage.remote.mock.results[n].value
70
86
  }
87
+
88
+ throw new Error(`Remote ${component} hasn't been connected`)
71
89
  }
72
- }
73
- })
74
90
 
75
- it('should emit translated messages', async () => {
76
- /**
77
- * @param {toa.samples.Message} declaration
78
- * @param {string} id
79
- * @returns {{index: number, message: toa.sampling.Message}}
80
- */
81
- const translation = (declaration) => {
82
- const find = (call) => call[0].payload === declaration.payload
83
- const index = mock.translate.message.mock.calls.findIndex(find)
84
- const message = mock.translate.message.mock.results[index].value
85
- const call = mock.translate.message.mock.calls[index]
86
-
87
- expect(call[1]).toStrictEqual(fixtures.suite.autonomous)
88
-
89
- return { index, message }
90
- }
91
-
92
- for (const [label, samples] of Object.entries(fixtures.suite.messages)) {
93
- for (const sample of samples) {
94
- const { index, message } = translation(sample)
95
-
96
- expect(stage.binding.binding.emit)
97
- .toHaveBeenNthCalledWith(index + 1, label, message)
91
+ for (const [id, set] of Object.entries(suite.operations)) {
92
+ const remote = await getRemote(id)
93
+
94
+ for (const [endpoint, samples] of Object.entries(set)) {
95
+ for (const sample of samples) {
96
+ const request = translation(sample)
97
+
98
+ expect(remote.invoke).toHaveBeenCalledWith(endpoint, request)
99
+ }
100
+ }
101
+ }
102
+ })
103
+
104
+ it('should emit translated messages', async () => {
105
+ /**
106
+ * @param {toa.samples.Message} declaration
107
+ * @returns {{index: number, message: toa.sampling.Message}}
108
+ */
109
+ const translation = (declaration) => {
110
+ const find = (call) => call[0].payload === declaration.payload
111
+ const index = mock.translate.message.mock.calls.findIndex(find)
112
+ const message = mock.translate.message.mock.results[index].value
113
+ const call = mock.translate.message.mock.calls[index]
114
+
115
+ expect(call[1]).toStrictEqual(suite.autonomous)
116
+
117
+ return { index, message }
98
118
  }
99
- }
100
- })
101
119
 
102
- it('should shutdown stage', () => {
103
- expect(stage.shutdown).toHaveBeenCalled()
104
- })
120
+ for (const [label, samples] of Object.entries(suite.messages)) {
121
+ for (const sample of samples) {
122
+ const { index, message } = translation(sample)
105
123
 
106
- it('should return results', () => {
107
- expect(ok).toStrictEqual(true)
108
- })
124
+ expect(stage.binding.binding.emit)
125
+ .toHaveBeenNthCalledWith(index + 1, label, message)
126
+ }
127
+ }
128
+ })
129
+
130
+ it('should shutdown stage', () => {
131
+ expect(stage.shutdown).toHaveBeenCalled()
132
+ })
109
133
 
110
- it.each(['messages', 'operations'])('should not throw if no %s defined', async (key) => {
111
- delete suite[key]
134
+ it('should return results', () => {
135
+ expect(ok).toStrictEqual(true)
136
+ })
112
137
 
113
- ok = await replay(suite, paths)
138
+ it.each(['messages', 'operations'])('should not throw if no %s defined', async (key) => {
139
+ delete suite[key]
114
140
 
115
- expect(ok).toStrictEqual(true)
141
+ ok = await replay(suite, paths)
142
+
143
+ expect(ok).toStrictEqual(true)
144
+ })
116
145
  })
146
+
147
+ const translation = (declaration) => {
148
+ const find = (call) => call[0].input === declaration.input
149
+ const index = mock.translate.operation.mock.calls.findIndex(find)
150
+
151
+ return mock.translate.operation.mock.results[index].value
152
+ }
@@ -2,33 +2,29 @@
2
2
 
3
3
  'use strict'
4
4
 
5
- const { generate } = require('randomstring')
6
5
  const { Locator } = require('@toa.io/core')
6
+ const stage = jest.requireActual('@toa.io/userland/stage')
7
7
 
8
- const locator = () => {
9
- const name = generate()
10
- const namespace = generate()
11
-
12
- return new Locator(name, namespace)
13
- }
14
-
15
- // stage
16
- const manifest = jest.fn(async (path) => ({ path, locator: locator() }))
8
+ const manifest = jest.fn(async (path) => stage.manifest(path))
17
9
  const composition = jest.fn()
18
10
  const shutdown = jest.fn()
19
11
 
20
12
  const remote = jest.fn(async (id) => {
21
13
  const [namespace, name] = id.split('.')
22
14
  const locator = new Locator(name, namespace)
23
- const invoke = jest.fn(async (operation, request) => request.reply)
15
+ const invoke = jest.fn(async (endpoint, request) => request.reply)
24
16
  const disconnect = jest.fn(async () => undefined)
25
17
 
26
18
  return { locator, invoke, disconnect }
27
19
  })
28
20
 
21
+ const component = jest.fn(async () => {
22
+ const invoke = jest.fn(async (endpoint, request) => request.reply)
23
+
24
+ return { invoke }
25
+ })
26
+
29
27
  const emit = jest.fn()
30
28
  const binding = { binding: { emit } }
31
29
 
32
- const stage = { manifest, composition, remote, shutdown, binding }
33
-
34
- exports.stage = stage
30
+ exports.stage = { manifest, composition, component, remote, shutdown, binding }
@@ -34,7 +34,7 @@ it('should define suite as autonomous', async () => {
34
34
  expect(suite.autonomous).toStrictEqual(true)
35
35
  })
36
36
 
37
- it('should load component samples', async () => {
37
+ it('should load operation samples', async () => {
38
38
  const expected = await operations()
39
39
 
40
40
  expect(Object.keys(suite.operations)).toStrictEqual([component])
@@ -47,12 +47,6 @@ it('should load component samples', async () => {
47
47
  expect(set.undo).toStrictEqual(expected.undo)
48
48
  })
49
49
 
50
- it('should load message samples', async () => {
51
- const expected = await messages()
52
-
53
- expect(suite.messages).toStrictEqual(expected)
54
- })
55
-
56
50
  describe('options', () => {
57
51
  const paths = [dummy, pot]
58
52
 
@@ -63,11 +57,6 @@ describe('options', () => {
63
57
  suite = await components(paths, options)
64
58
 
65
59
  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
60
  })
72
61
 
73
62
  it('should filter samples by operation name', async () => {
@@ -101,16 +90,14 @@ describe('options', () => {
101
90
  expect(suite.operations['dummies.dummy'].undo[0].title).toStrictEqual('Should not undo')
102
91
  })
103
92
 
104
- it('should filter message samples by title', async () => {
93
+ it('should filter operation samples by title', async () => {
105
94
  /** @type {toa.samples.suite.Options} */
106
- const options = { title: 'Something happened with a dummy' }
95
+ const options = { title: 'Should not undo' }
107
96
 
108
97
  suite = await components(paths, options)
109
98
 
110
- const messages = suite.messages['somewhere.something.happened']
111
-
112
- expect(messages.length).toStrictEqual(1)
113
- expect(messages[0].title).toStrictEqual(options.title)
99
+ expect(Object.keys(suite.operations['dummies.dummy']).length).toStrictEqual(1)
100
+ expect(suite.operations['dummies.dummy'].undo[0].title).toStrictEqual(options.title)
114
101
  })
115
102
  })
116
103
 
@@ -133,16 +120,3 @@ const operations = async () => {
133
120
  do: [...do1, ...do2], undo
134
121
  }
135
122
  }
136
-
137
- /**
138
- *
139
- * @returns {Promise<toa.samples.messages.Set>}
140
- */
141
- const messages = async () => {
142
- const label = 'somewhere.something.happened'
143
- const file = resolve(dummy, 'samples/messages', label + '.yaml')
144
- const declarations = await yaml.load.all(file)
145
- const messages = declarations.map((sample) => ({ component, ...sample }))
146
-
147
- return { [label]: messages }
148
- }
@@ -2,9 +2,9 @@ import * as _suite from './suite'
2
2
 
3
3
  declare namespace toa.samples.replay {
4
4
 
5
- type components = (paths: string[], options?: _suite.Options) => Promise<boolean>
6
- type context = (path: string, options?: _suite.Options) => Promise<boolean>
7
- type replay = (suite: _suite.Suite, paths: string[]) => Promise<boolean>
5
+ type components = (paths: string[], options?: _suite.Options) => Promise<boolean>
6
+ type context = (path: string, options?: _suite.Options) => Promise<boolean>
7
+ type replay = (suite: _suite.Suite, paths: string[], options?: object) => Promise<boolean>
8
8
 
9
9
  }
10
10
 
@@ -1,3 +1,4 @@
1
+ import * as _core from '@toa.io/core/types'
1
2
  import * as _operations from './operation'
2
3
  import * as _messages from './message'
3
4
 
@@ -12,11 +13,12 @@ declare namespace toa.samples {
12
13
  component?: string
13
14
  operation?: string
14
15
  title?: string
16
+ runner?: object
15
17
  }
16
18
  }
17
19
 
18
20
  type Suite = {
19
- title: string
21
+ title?: string
20
22
  autonomous: boolean
21
23
  operations?: suite.Operations
22
24
  messages?: _messages.Set
@@ -4,8 +4,10 @@ const boot = require('@toa.io/boot')
4
4
  const { state } = require('./state')
5
5
 
6
6
  /** @type {toa.stage.Component} */
7
- const component = async (path) => {
8
- const manifest = await boot.manifest(path)
7
+ const component = async (path, options) => {
8
+ options = Object.assign({}, DEFAULTS, options)
9
+
10
+ const manifest = await boot.manifest(path, options)
9
11
  const component = await boot.component(manifest)
10
12
 
11
13
  await component.connect()
@@ -15,4 +17,7 @@ const component = async (path) => {
15
17
  return component
16
18
  }
17
19
 
20
+ const binding = require.resolve('./binding')
21
+ const DEFAULTS = { bindings: [binding] }
22
+
18
23
  exports.component = component
@@ -18,7 +18,7 @@ it('should boot component', async () => {
18
18
  const path = generate()
19
19
  const component = await stage.component(path)
20
20
 
21
- expect(mock.boot.manifest).toHaveBeenCalledWith(path)
21
+ expect(mock.boot.manifest.mock.calls[0][0]).toStrictEqual(path)
22
22
  expect(mock.boot.component).toHaveBeenCalledWith(await mock.boot.manifest.mock.results[0].value)
23
23
  expect(component).toStrictEqual(await mock.boot.component.mock.results[0].value)
24
24
  expect(component.connect).toHaveBeenCalled()
@@ -1,9 +1,10 @@
1
1
  import * as _core from '@toa.io/core/types'
2
2
  import * as _norm from '@toa.io/norm/types'
3
+ import * as _composition from '@toa.io/boot/types/composition'
3
4
 
4
5
  declare namespace toa.stage {
5
6
  type Manifest = (path: string) => Promise<_norm.Component>
6
- type Component = (path: string) => Promise<_core.Component>
7
+ type Component = (path: string, options?: _composition.Options) => Promise<_core.Component>
7
8
  type Composition = (paths: string[]) => Promise<void>
8
9
  type Remote = (id: string) => Promise<_core.Component>
9
10
  type Shutdown = () => Promise<void>
@@ -1,37 +0,0 @@
1
- title: Should book a pot
2
- payload:
3
- pot: 1b9d7983bc204c8f8928d843a666a642
4
- input:
5
- booked: true
6
- query:
7
- id: 1b9d7983bc204c8f8928d843a666a642
8
- ---
9
- title: Should book a pot (with request sample)
10
- payload:
11
- pot: 1b9d7983bc204c8f8928d843a666a642
12
- input:
13
- booked: true
14
- query:
15
- id: 1b9d7983bc204c8f8928d843a666a642
16
- request:
17
- current:
18
- id: 1b9d7983bc204c8f8928d843a666a642
19
- material: glass
20
- booked: false
21
- next:
22
- id: 1b9d7983bc204c8f8928d843a666a642
23
- material: glass
24
- booked: true
25
- ---
26
- title: Should book a pot (without receiver output verification)
27
- payload:
28
- pot: 1b9d7983bc204c8f8928d843a666a642
29
- request:
30
- current:
31
- id: 1b9d7983bc204c8f8928d843a666a642
32
- material: glass
33
- booked: false
34
- next:
35
- id: 1b9d7983bc204c8f8928d843a666a642
36
- material: glass
37
- booked: true
@@ -1,72 +0,0 @@
1
- 'use strict'
2
-
3
- const { generate } = require('randomstring')
4
- const { random } = require('@toa.io/generic')
5
-
6
- /** @type {toa.samples.Suite} */
7
- const suite = { autonomous: true, operations: {} }
8
-
9
- /**
10
- * @returns {toa.samples.Operation[]}
11
- */
12
- const ops = () => {
13
- const samples = []
14
-
15
- for (let i = 0; i < random(3) + 1; i++) {
16
- const sample = {
17
- input: generate(),
18
- output: generate(),
19
- local: generate(),
20
- current: generate(),
21
- next: generate()
22
- }
23
-
24
- samples.push(sample)
25
- }
26
-
27
- return samples
28
- }
29
-
30
- /**
31
- * @returns {toa.samples.Message[]}
32
- */
33
- const msgs = () => {
34
- /** @type {toa.samples.Message[]} */
35
- const samples = []
36
-
37
- for (let i = 0; i < random(3) + 1; i++) {
38
- const sample = /** @type {toa.samples.Message} */ {
39
- component: generate(),
40
- payload: generate(),
41
- input: generate()
42
- }
43
-
44
- samples.push(sample)
45
- }
46
-
47
- return samples
48
- }
49
-
50
- // components
51
- for (let i = 0; i < random(3) + 1; i++) {
52
- const id = generate() + '.' + generate()
53
- const operations = {}
54
- const messages = {}
55
-
56
- for (let j = 0; j < random(3) + 1; j++) {
57
- const endpoint = generate()
58
- const label = generate()
59
-
60
- operations[endpoint] = ops()
61
- messages[label] = msgs()
62
- }
63
-
64
- suite.operations[id] = operations
65
- }
66
-
67
- /** @type {string} */
68
- const label = generate()
69
-
70
- suite.messages = { [label]: msgs() }
71
-
72
- exports.suite = suite