@toa.io/userland 0.10.1-dev.0 → 0.20.0-alpha.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.
Files changed (68) hide show
  1. package/example/.env +9 -0
  2. package/example/components/echo/manifest.toa.yaml +7 -0
  3. package/example/components/echo/operations/get.js +7 -0
  4. package/example/components/echo/operations/resolve.js +7 -0
  5. package/example/components/echo/operations/signal.js +11 -3
  6. package/example/components/echo/samples/get.yaml +12 -0
  7. package/example/components/echo/samples/signal.yaml +0 -3
  8. package/example/components/math.calculations/operations/increment.js +1 -3
  9. package/example/components/math.calculations/samples/add.yaml +1 -2
  10. package/example/components/math.calculations/samples/assets/ab.yaml +2 -0
  11. package/example/components/math.calculations/samples/increment.yaml +3 -6
  12. package/example/components/tea.pots/manifest.toa.yaml +2 -2
  13. package/example/components/web/manifest.toa.yaml +8 -0
  14. package/example/components/web/operations/get.js +14 -0
  15. package/example/components/web/samples/get.yaml +8 -0
  16. package/example/context.toa.yaml +7 -0
  17. package/example/stage/call.test.js +5 -4
  18. package/example/stage/events.test.js +6 -3
  19. package/example/stage/invoke.test.js +4 -0
  20. package/package.json +10 -9
  21. package/samples/notes.md +2 -2
  22. package/samples/readme.md +74 -8
  23. package/samples/src/.replay/.suite/component.js +3 -8
  24. package/samples/src/.replay/.suite/operation.js +1 -1
  25. package/samples/src/.replay/.suite/operations.js +5 -3
  26. package/samples/src/.replay/.suite/translate/.operation/.prepare/cast.js +20 -0
  27. package/samples/src/.replay/.suite/translate/.operation/.prepare/cast.test.js +94 -0
  28. package/samples/src/.replay/.suite/translate/.operation/.prepare/expand.js +14 -0
  29. package/samples/src/.replay/.suite/translate/.operation/.prepare/index.js +7 -0
  30. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/.aspect.js +26 -0
  31. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/configuration.js +5 -0
  32. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/http.js +5 -0
  33. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/index.js +9 -0
  34. package/samples/src/.replay/.suite/translate/.operation/.prepare/shortcuts/state.js +5 -0
  35. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/async.js +3 -0
  36. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/cast.js +26 -0
  37. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/index.js +5 -0
  38. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/map.js +3 -0
  39. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/set.js +3 -0
  40. package/samples/src/.replay/.suite/translate/.operation/.prepare/types/sync.js +3 -0
  41. package/samples/src/.replay/.suite/translate/.operation/prepare.js +4 -4
  42. package/samples/src/.replay/index.js +2 -0
  43. package/samples/src/.replay/stage.js +60 -0
  44. package/samples/src/.replay/suite.js +3 -2
  45. package/samples/src/.replay/test.js +5 -3
  46. package/samples/src/components.js +1 -1
  47. package/samples/src/context.js +2 -2
  48. package/samples/src/replay.js +4 -5
  49. package/samples/src/suite/components.js +3 -3
  50. package/samples/test/components.test.js +2 -2
  51. package/samples/test/context.test.js +2 -2
  52. package/samples/test/replay.test.js +108 -72
  53. package/samples/test/replay.translate.message.test.js +2 -6
  54. package/samples/test/replay.translate.operation.test.js +1 -1
  55. package/samples/test/stage.mock.js +10 -14
  56. package/samples/test/suite.components.test.js +5 -31
  57. package/samples/types/operation.d.ts +2 -2
  58. package/samples/types/replay.d.ts +3 -3
  59. package/samples/types/suite.d.ts +5 -2
  60. package/stage/src/binding/binding.js +14 -13
  61. package/stage/src/component.js +7 -2
  62. package/stage/src/index.js +0 -3
  63. package/stage/test/component.test.js +1 -1
  64. package/stage/types/index.d.ts +2 -1
  65. package/example/components/tea.pots/samples/messages/store.orders.created.yaml +0 -37
  66. package/samples/src/.replay/.suite/translate/.operation/specials/configuration.js +0 -26
  67. package/samples/src/.replay/.suite/translate/.operation/specials/index.js +0 -5
  68. package/samples/test/replay.fixtures.js +0 -72
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { aspect } = require('./.aspect')
4
+
5
+ exports.configuration = aspect('configuration')
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { aspect } = require('./.aspect')
4
+
5
+ exports.http = aspect('http')
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const { configuration } = require('./configuration')
4
+ const { state } = require('./state')
5
+ const { http } = require('./http')
6
+
7
+ exports.configuration = configuration
8
+ exports.state = state
9
+ exports.http = http
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { aspect } = require('./.aspect')
4
+
5
+ exports.state = aspect('state')
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = (value) => async () => value
@@ -0,0 +1,26 @@
1
+ 'use strict'
2
+
3
+ const { map } = require('@toa.io/generic')
4
+
5
+ const types = {
6
+ Map: require('./map'),
7
+ Set: require('./set'),
8
+ sync: require('./sync'),
9
+ async: require('./async')
10
+ }
11
+
12
+ function cast (value) {
13
+ return map(value, replace)
14
+ }
15
+
16
+ function replace (key, value) {
17
+ const [name, type] = key.split(':')
18
+
19
+ if (!(type in types)) return
20
+
21
+ const typed = types[type](value)
22
+
23
+ return [name, typed]
24
+ }
25
+
26
+ exports.cast = cast
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { cast } = require('./cast')
4
+
5
+ exports.cast = cast
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = (value) => new Map(Object.entries(value))
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = (value) => new Set(value)
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ module.exports = (value) => () => value
@@ -1,14 +1,14 @@
1
1
  'use strict'
2
2
 
3
- const specials = require('./specials')
3
+ const { cast, expand } = require('./.prepare')
4
4
 
5
5
  /**
6
6
  * @param {toa.samples.Operation & Object} declaration
7
7
  */
8
8
  const prepare = (declaration) => {
9
- for (const [keyword, prepare] of Object.entries(specials)) {
10
- if (keyword in declaration) prepare(declaration)
11
- }
9
+ expand(declaration)
10
+
11
+ if ('extensions' in declaration) cast(declaration.extensions)
12
12
  }
13
13
 
14
14
  exports.prepare = prepare
@@ -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 && options.autonomous !== true) 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
+ }
@@ -108,16 +108,12 @@ describe('validation', () => {
108
108
  expect(check).toThrow('must be string')
109
109
  })
110
110
 
111
- it.each(['input', 'query'])('should throw if request.%s is defined', async (key) => {
111
+ it.each([['input', 'query']])('should throw if request.%s is defined', async (key) => {
112
112
  expect.assertions(1)
113
113
 
114
114
  declaration[key] = { id: generate() }
115
115
  declaration.request = /** @type {toa.samples.Operation} */ { [key]: { id: generate() } }
116
116
 
117
- try {
118
- check()
119
- } catch (exception) {
120
- expect(exception).toMatchObject({ path: '/request' })
121
- }
117
+ expect(() => check()).toThrow('/request')
122
118
  })
123
119
  })
@@ -35,7 +35,7 @@ describe('validation', () => {
35
35
  it('should not allow additional properties', () => {
36
36
  declaration.foo = generate()
37
37
 
38
- expect(() => translate(declaration, true)).toThrow('must NOT have additional properties')
38
+ expect(() => translate(declaration, true)).toThrow('not expected')
39
39
  })
40
40
  })
41
41
 
@@ -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 }