@toa.io/userland 0.2.1-dev.3

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 (129) hide show
  1. package/example/components/echo/beacon/manifest.toa.yaml +9 -0
  2. package/example/components/echo/beacon/operations/signal.js +7 -0
  3. package/example/components/echo/beacon/samples/signal.yaml +7 -0
  4. package/example/components/math/calculations/manifest.toa.yaml +16 -0
  5. package/example/components/math/calculations/operations/add.js +7 -0
  6. package/example/components/math/calculations/operations/increment.js +15 -0
  7. package/example/components/math/calculations/samples/add.yaml +11 -0
  8. package/example/components/math/calculations/samples/increment.yaml +30 -0
  9. package/example/components/math/proxy/manifest.toa.yaml +11 -0
  10. package/example/components/math/proxy/operations/add.js +10 -0
  11. package/example/components/math/proxy/samples/add.yaml +11 -0
  12. package/example/components/tea/pots/manifest.toa.yaml +28 -0
  13. package/example/components/tea/pots/operations/same.js +13 -0
  14. package/example/components/tea/pots/receivers/store.orders.created.js +8 -0
  15. package/example/components/tea/pots/samples/messages/store.orders.created.yaml +37 -0
  16. package/example/components/tea/pots/samples/same.yaml +8 -0
  17. package/example/components/tea/pots/samples/transit.yaml +43 -0
  18. package/example/context.toa.yaml +3 -0
  19. package/example/samples/math.proxy.add.yaml +5 -0
  20. package/example/samples/messages/store.orders.created.yaml +8 -0
  21. package/example/stage/call.test.js +39 -0
  22. package/example/stage/events.test.js +48 -0
  23. package/example/stage/invoke.test.js +29 -0
  24. package/package.json +36 -0
  25. package/readme.md +9 -0
  26. package/samples/docs/sampling-dark.jpg +0 -0
  27. package/samples/docs/sampling-light.jpg +0 -0
  28. package/samples/notes.md +12 -0
  29. package/samples/package.json +5 -0
  30. package/samples/readme.md +66 -0
  31. package/samples/src/.replay/.suite/component.js +25 -0
  32. package/samples/src/.replay/.suite/index.js +7 -0
  33. package/samples/src/.replay/.suite/messages.js +39 -0
  34. package/samples/src/.replay/.suite/operation.js +28 -0
  35. package/samples/src/.replay/.suite/operations.js +18 -0
  36. package/samples/src/.replay/.suite/translate/.message/index.js +5 -0
  37. package/samples/src/.replay/.suite/translate/.message/request.js +31 -0
  38. package/samples/src/.replay/.suite/translate/.operation/calls.js +49 -0
  39. package/samples/src/.replay/.suite/translate/.operation/cleanup.js +16 -0
  40. package/samples/src/.replay/.suite/translate/.operation/events.js +16 -0
  41. package/samples/src/.replay/.suite/translate/.operation/index.js +11 -0
  42. package/samples/src/.replay/.suite/translate/.operation/prepare.js +14 -0
  43. package/samples/src/.replay/.suite/translate/.operation/specials/configuration.js +26 -0
  44. package/samples/src/.replay/.suite/translate/.operation/specials/index.js +5 -0
  45. package/samples/src/.replay/.suite/translate/index.js +7 -0
  46. package/samples/src/.replay/.suite/translate/message.js +26 -0
  47. package/samples/src/.replay/.suite/translate/operation.js +47 -0
  48. package/samples/src/.replay/.suite/translate/schemas/index.js +7 -0
  49. package/samples/src/.replay/.suite/translate/schemas/message.cos.yaml +10 -0
  50. package/samples/src/.replay/.suite/translate/schemas/operation.cos.yaml +23 -0
  51. package/samples/src/.replay/index.js +5 -0
  52. package/samples/src/.replay/suite.js +22 -0
  53. package/samples/src/.replay/test.js +17 -0
  54. package/samples/src/components.js +13 -0
  55. package/samples/src/context.js +18 -0
  56. package/samples/src/index.js +9 -0
  57. package/samples/src/replay.js +17 -0
  58. package/samples/src/suite/.read/index.js +7 -0
  59. package/samples/src/suite/.read/messages.js +35 -0
  60. package/samples/src/suite/.read/operations.js +45 -0
  61. package/samples/src/suite/.read/parse.js +24 -0
  62. package/samples/src/suite/components.js +28 -0
  63. package/samples/src/suite/context.js +19 -0
  64. package/samples/src/suite/index.js +7 -0
  65. package/samples/test/components.test.js +39 -0
  66. package/samples/test/context/components/ok/manifest.toa.yaml +2 -0
  67. package/samples/test/context/components/ok/samples/do.yaml +11 -0
  68. package/samples/test/context/components/ok/samples/dummies.dummy.do.yaml +11 -0
  69. package/samples/test/context/components/ok/samples/dummies.dummy.undo.yaml +7 -0
  70. package/samples/test/context/components/ok/samples/messages/somewhere.something.happened.yaml +6 -0
  71. package/samples/test/context/context.toa.yaml +3 -0
  72. package/samples/test/context/samples/dummies.dummy.observe.yaml +6 -0
  73. package/samples/test/context/samples/dummies.dummy.transit.yaml +10 -0
  74. package/samples/test/context/samples/messages/somewhere.something.happened.yaml +6 -0
  75. package/samples/test/context.fixtures.js +8 -0
  76. package/samples/test/context.test.js +78 -0
  77. package/samples/test/replay.fixtures.js +72 -0
  78. package/samples/test/replay.mock.js +7 -0
  79. package/samples/test/replay.test.js +116 -0
  80. package/samples/test/replay.translate.message.fixtures.js +24 -0
  81. package/samples/test/replay.translate.message.test.js +123 -0
  82. package/samples/test/replay.translate.mock.js +17 -0
  83. package/samples/test/replay.translate.operation.fixtures.js +107 -0
  84. package/samples/test/replay.translate.operation.test.js +68 -0
  85. package/samples/test/stage.mock.js +34 -0
  86. package/samples/test/suite.components.test.js +85 -0
  87. package/samples/test/suite.context.test.js +79 -0
  88. package/samples/test/suite.mock.js +10 -0
  89. package/samples/types/index.d.ts +8 -0
  90. package/samples/types/message.d.ts +24 -0
  91. package/samples/types/operation.d.ts +36 -0
  92. package/samples/types/replay.d.ts +13 -0
  93. package/samples/types/suite.d.ts +19 -0
  94. package/stage/package.json +5 -0
  95. package/stage/readme.md +41 -0
  96. package/stage/src/binding/binding.js +49 -0
  97. package/stage/src/binding/consumer.js +30 -0
  98. package/stage/src/binding/emitter.js +26 -0
  99. package/stage/src/binding/factory.js +49 -0
  100. package/stage/src/binding/index.js +10 -0
  101. package/stage/src/binding/label.js +12 -0
  102. package/stage/src/binding/producer.js +43 -0
  103. package/stage/src/binding/receiver.js +35 -0
  104. package/stage/src/component.js +18 -0
  105. package/stage/src/composition.js +19 -0
  106. package/stage/src/index.js +18 -0
  107. package/stage/src/manifest.js +12 -0
  108. package/stage/src/remote.js +22 -0
  109. package/stage/src/shutdown.js +19 -0
  110. package/stage/src/state.js +17 -0
  111. package/stage/test/binding/binding.test.js +15 -0
  112. package/stage/test/binding/consumer.test.js +52 -0
  113. package/stage/test/binding/emitter.test.js +54 -0
  114. package/stage/test/binding/producer.fixtures.js +10 -0
  115. package/stage/test/binding/producer.test.js +49 -0
  116. package/stage/test/binding/receiver.fixtures.js +7 -0
  117. package/stage/test/binding/receiver.test.js +54 -0
  118. package/stage/test/binding.mock.js +8 -0
  119. package/stage/test/boot.mock.js +16 -0
  120. package/stage/test/component.test.js +25 -0
  121. package/stage/test/composition.test.js +38 -0
  122. package/stage/test/manifest.test.js +24 -0
  123. package/stage/test/remote.test.js +32 -0
  124. package/stage/test/shutdown.test.js +66 -0
  125. package/stage/test/state.mock.js +8 -0
  126. package/stage/test/state.test.js +27 -0
  127. package/stage/types/binding.d.ts +20 -0
  128. package/stage/types/index.d.ts +28 -0
  129. package/stage/types/state.d.ts +14 -0
@@ -0,0 +1,9 @@
1
+ name: beacon
2
+ namespace: echo
3
+
4
+ operations:
5
+ signal:
6
+ output: string
7
+
8
+ configuration:
9
+ signal: quack
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ async function observation (input, none, context) {
4
+ return context.configuration.signal
5
+ }
6
+
7
+ exports.observation = observation
@@ -0,0 +1,7 @@
1
+ title: Should echo
2
+ output: quack
3
+ ---
4
+ title: Should substitute configuration
5
+ output: woof
6
+ configuration:
7
+ signal: woof
@@ -0,0 +1,16 @@
1
+ name: calculations
2
+ namespace: math
3
+
4
+ entity: null
5
+
6
+ operations:
7
+ add:
8
+ input:
9
+ a: number
10
+ b: number
11
+ output: number
12
+ increment:
13
+ input:
14
+ value: number
15
+ times: 1
16
+ output: number
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ async function observation (input) {
4
+ return input.a + input.b
5
+ }
6
+
7
+ exports.observation = observation
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ async function observation (input, none, context) {
4
+ let value = input.value
5
+
6
+ for (let i = 0; i < input.times; i++) {
7
+ const reply = await context.local.add({ input: { a: value, b: 1 } })
8
+
9
+ value = reply.output
10
+ }
11
+
12
+ return value
13
+ }
14
+
15
+ exports.observation = observation
@@ -0,0 +1,11 @@
1
+ title: Should add numbers
2
+ input:
3
+ a: 1
4
+ b: 2
5
+ output: 3
6
+ ---
7
+ title: Should add negative numbers
8
+ input:
9
+ a: -1
10
+ b: -2
11
+ output: -3
@@ -0,0 +1,30 @@
1
+ title: Should increment number
2
+ input:
3
+ value: 1
4
+ output: 2
5
+ local:
6
+ add:
7
+ input:
8
+ a: 1
9
+ b: 1
10
+ output: 2
11
+ ---
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)
18
+ input:
19
+ value: 0
20
+ times: 2
21
+ output: 2
22
+ local:
23
+ add:
24
+ - input:
25
+ a: 0
26
+ b: 1
27
+ output: 1
28
+ - input:
29
+ a: 1
30
+ b: 1
@@ -0,0 +1,11 @@
1
+ name: proxy
2
+ namespace: math
3
+
4
+ entity: null
5
+
6
+ operations:
7
+ add:
8
+ input:
9
+ a: number
10
+ b: number
11
+ output: number
@@ -0,0 +1,10 @@
1
+ 'use strict'
2
+
3
+ async function observation (input, none, context) {
4
+ const request = { input }
5
+
6
+ // noinspection JSUnresolvedVariable
7
+ return context.remote.math.calculations.add(request)
8
+ }
9
+
10
+ exports.observation = observation
@@ -0,0 +1,11 @@
1
+ title: Should add numbers
2
+ input:
3
+ a: 1
4
+ b: 2
5
+ output: 3
6
+ remote:
7
+ math.calculations.add:
8
+ input:
9
+ a: 1
10
+ b: 2
11
+ output: 3
@@ -0,0 +1,28 @@
1
+ name: pots
2
+ namespace: tea
3
+
4
+ entity:
5
+ storage: mongodb
6
+ schema:
7
+ material*: [glass, ceramic, steel]
8
+ volume: number
9
+ booked: false
10
+
11
+ operations:
12
+ transit:
13
+ concurrency: none
14
+ input:
15
+ material: .
16
+ volume: .
17
+ booked: .
18
+ same:
19
+ input: .material
20
+ output: array
21
+
22
+ receivers:
23
+ store.orders.created:
24
+ transition: transit
25
+ binding: amqp # avoid discovery
26
+
27
+ configuration:
28
+ step: 0.5
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ async function observation (input, none, context) {
4
+ const criteria = `material==${input}`
5
+ const query = { criteria, limit }
6
+
7
+ // noinspection JSUnresolvedFunction
8
+ return context.local.enumerate({ query })
9
+ }
10
+
11
+ const limit = 10
12
+
13
+ exports.observation = observation
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ const request = async (payload) => ({
4
+ input: { booked: true },
5
+ query: { id: payload.pot }
6
+ })
7
+
8
+ exports.request = request
@@ -0,0 +1,37 @@
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
@@ -0,0 +1,8 @@
1
+ title: Should find same material
2
+ input: steel
3
+ local:
4
+ enumerate:
5
+ query:
6
+ criteria: 'material==steel'
7
+ limit: 10
8
+ output: []
@@ -0,0 +1,43 @@
1
+ title: Should create
2
+ input:
3
+ material: glass
4
+ volume: 1.5
5
+ next:
6
+ material: glass
7
+ volume: 1.5
8
+ ---
9
+ title: Should update
10
+ input:
11
+ material: steel
12
+ current:
13
+ material: glass
14
+ volume: 1.5
15
+ next:
16
+ material: steel
17
+ volume: 1.5
18
+ ---
19
+ title: Should emit `created` event
20
+ input:
21
+ material: glass
22
+ volume: 1.5
23
+ next:
24
+ material: glass
25
+ volume: 1.5
26
+ events:
27
+ created:
28
+ material: glass
29
+ volume: 1.5
30
+ ---
31
+ title: Should emit `updated` event
32
+ input:
33
+ material: steel
34
+ current:
35
+ material: glass
36
+ volume: 1.5
37
+ next:
38
+ material: steel
39
+ volume: 1.5
40
+ events:
41
+ updated:
42
+ material: steel
43
+ volume: 1.5
@@ -0,0 +1,3 @@
1
+ name: examples
2
+ packages: components/*/*
3
+ registry: example
@@ -0,0 +1,5 @@
1
+ title: Should add numbers
2
+ input:
3
+ a: 1
4
+ b: 2
5
+ output: 3
@@ -0,0 +1,8 @@
1
+ title: Should book a pot
2
+ component: tea.pots
3
+ payload:
4
+ pot: a959effedd5d4c34a3ee9f640dab2d7e
5
+ input:
6
+ booked: true
7
+ query:
8
+ id: a959effedd5d4c34a3ee9f640dab2d7e
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('node:path')
4
+ const { exceptions: { RequestContractException } } = require('@toa.io/core')
5
+ const stage = require('@toa.io/userland/stage')
6
+
7
+ const root = resolve(__dirname, '../components')
8
+
9
+ /** @type {toa.core.Component} */
10
+ let remote
11
+
12
+ beforeAll(async () => {
13
+ const path = resolve(root, 'math/calculations')
14
+
15
+ await stage.composition([path])
16
+
17
+ remote = await stage.remote('math.calculations')
18
+ })
19
+
20
+ afterAll(async () => {
21
+ await stage.shutdown()
22
+ })
23
+
24
+ it('should call endpoint', async () => {
25
+ const a = Math.random()
26
+ const b = Math.random()
27
+
28
+ const reply = await remote.invoke('add', { input: { a, b } })
29
+
30
+ expect(reply.output).toStrictEqual(a + b)
31
+ })
32
+
33
+ it('should throw on invalid input', async () => {
34
+ const a = 'not a number'
35
+ const b = 'neither'
36
+
37
+ await expect(remote.invoke('add', { input: { a, b } }))
38
+ .rejects.toBeInstanceOf(RequestContractException)
39
+ })
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('node:path')
4
+ const stage = require('@toa.io/userland/stage')
5
+
6
+ const binding = stage.binding.binding
7
+ const root = resolve(__dirname, '../components')
8
+
9
+ /** @type {toa.core.Component} */
10
+ let remote
11
+
12
+ beforeAll(async () => {
13
+ const path = resolve(root, 'tea/pots')
14
+
15
+ await stage.composition([path])
16
+
17
+ remote = await stage.remote('tea.pots')
18
+ })
19
+
20
+ afterAll(async () => {
21
+ await stage.shutdown()
22
+ })
23
+
24
+ it('should receive event', async () => {
25
+ const created = await remote.invoke('transit', { input: { material: 'glass' } })
26
+ const id = created.output.id
27
+ const payload = { pot: id }
28
+ const message = { payload }
29
+ const request = { query: { id } }
30
+
31
+ await binding.emit('store.orders.created', message)
32
+
33
+ const reply = await remote.invoke('observe', request)
34
+
35
+ expect(reply.output.booked).toStrictEqual(true)
36
+ })
37
+
38
+ it('should emit event', async () => {
39
+ expect.assertions(1)
40
+
41
+ const material = 'steel'
42
+
43
+ await binding.subscribe('tea.pots.created', (pot) => {
44
+ expect(pot.payload.material).toStrictEqual(material)
45
+ })
46
+
47
+ await remote.invoke('transit', { input: { material } })
48
+ })
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('node:path')
4
+ const stage = require('@toa.io/userland/stage')
5
+
6
+ const root = resolve(__dirname, '../components')
7
+
8
+ /** @type {toa.core.Component} */
9
+ let component
10
+
11
+ beforeAll(async () => {
12
+ const path = resolve(root, 'math/calculations')
13
+
14
+ component = await stage.component(path)
15
+ })
16
+
17
+ afterAll(async () => {
18
+ await stage.shutdown()
19
+ })
20
+
21
+ it('should invoke', async () => {
22
+ const a = Math.random()
23
+ const b = Math.random()
24
+
25
+ const reply = await component.invoke('add', { input: { a, b } })
26
+
27
+ expect(reply.exception).toBeUndefined()
28
+ expect(reply.output).toStrictEqual(a + b)
29
+ })
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@toa.io/userland",
3
+ "version": "0.2.1-dev.3",
4
+ "description": "Toa development kit",
5
+ "homepage": "https://toa.io",
6
+ "author": {
7
+ "name": "Artem Gurtovoi",
8
+ "url": "https://github.com/temich"
9
+ },
10
+ "license": "MIT",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/toa-io/toa.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/toa-io/toa/issues"
17
+ },
18
+ "engines": {
19
+ "node": ">= 12.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "main": "src/index.js",
25
+ "types": "types/index.d.ts",
26
+ "dependencies": {
27
+ "@toa.io/boot": "*",
28
+ "@toa.io/core": "*",
29
+ "@toa.io/filesystem": "*",
30
+ "@toa.io/generic": "*",
31
+ "@toa.io/norm": "*",
32
+ "@toa.io/schemas": "*",
33
+ "@toa.io/yaml": "*",
34
+ "tap": "16.3.4"
35
+ }
36
+ }
package/readme.md ADDED
@@ -0,0 +1,9 @@
1
+ # Toa development kit
2
+
3
+ - [Stage](./stage): integration tests framework (Node.js)
4
+ - [Samples](./samples): YAML-based declarative tests
5
+ - Features[^1]: Gherkin steps definition ([cucumber.js](https://github.com/cucumber/cucumber-js))
6
+
7
+ See [example](./example).
8
+
9
+ [^1]: not yet implemented
Binary file
Binary file
@@ -0,0 +1,12 @@
1
+ # Developer notes
2
+
3
+ ## Extending Replay
4
+
5
+ 1. Update [`example`](../example). `toa replay` should either fail or throw exception.
6
+ 2. Add [feature](./features). It should fail.
7
+ 3. If the new decorator is being developed:
8
+ 1. Add the [decorator](/extensions/sampling/docs/replay.md).
9
+ 2. Add [boot extension](/runtime/boot/src/extensions).
10
+ 4. Update `suite` [translation](./src/.suite/.component/translate.js).
11
+ 5. Ensure feature is passing.
12
+ 6. Ensure example is passing with `toa replay`.
@@ -0,0 +1,5 @@
1
+ {
2
+ "private": true,
3
+ "main": "src/index.js",
4
+ "types": "types/index.d.ts"
5
+ }
@@ -0,0 +1,66 @@
1
+ # Samples
2
+
3
+ ## TL;DR
4
+
5
+ <a href="https://miro.com/app/board/uXjVOoy0ImU=/?moveToWidget=3458764532091744292&cot=14">
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset="./docs/sampling-dark.jpg">
8
+ <img alt="4D" width="640" height="435" src="./docs/sampling-light.jpg">
9
+ </picture>
10
+ </a>
11
+
12
+ See [features](/features/replay).
13
+
14
+ ## Operation Samples
15
+
16
+ Sample is an object containing values of operation inputs (i.e.: request, context outputs and
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/.component/sample.cos.yaml).
19
+
20
+ > Although `input` and `output` are declared as arbitrary values, they must conform to the
21
+ > corresponding
22
+ > operation schemas.
23
+
24
+ ### Declaration
25
+
26
+ > Samples must be declared
27
+ > as [multi-document YAML files](https://yaml.org/spec/1.2.2/#22-structures).
28
+
29
+ Operation samples must be located under the `samples` directory in the component or context root.
30
+ Sample file names must follow the convention: `namespace.name.operation.yaml`, that is, be an
31
+ endpoint of the operation samples to be applied to. For component-level sample files `namespace`
32
+ and `name` must match corresponding component, therefore are optional.
33
+
34
+ ## Message Samples
35
+
36
+ Message Sample is an object containing receiver's input (`payload`) to be substituted and
37
+ outcomes (`input` and `query`) to be verified. Message sample may contain corresponding operation
38
+ sample. See its [schema](#).
39
+
40
+ > Message samples are always [autonomous](#autonomy).
41
+
42
+ ### Declaration
43
+
44
+ Operation samples must be located under the `samples/messages` directory in the component or context
45
+ root. Samples file names must follow the convention `namesace.component.event.yaml`, that is, be a
46
+ label
47
+ of the event receiver is consuming.
48
+
49
+ ## Autonomy
50
+
51
+ Component level samples are *autonomous*, namely, does not assume actual remote calls as
52
+ replaying of component-level samples will boot only that component. Remote call attempt not declared
53
+ within sample will cause an exception.
54
+ See [examples](../example/components/math/calculations/samples).
55
+
56
+ For context level samples (integration samples), remote calls with non-declared outputs will be
57
+ actually performed. Replaying these samples will boot the composition with all components of the
58
+ context (so as required extensions). See [examples](../example/samples).
59
+
60
+ > Integration samples are more flexible and less sensitive to implementation details.
61
+
62
+ ## Replay
63
+
64
+ Samples may be *replayed* using [`toa replay`](/runtime/cli/readme.md#replay) command.
65
+
66
+ > Replaying samples requires local deployment environment.
@@ -0,0 +1,25 @@
1
+ 'use strict'
2
+
3
+ const stage = require('@toa.io/userland/stage')
4
+
5
+ const { operation } = require('./operation')
6
+
7
+ /**
8
+ *
9
+ * @param {string} id
10
+ * @param {toa.samples.operations.Set} set
11
+ * @param {boolean} autonomous
12
+ * @returns {Function}
13
+ */
14
+ const component = (id, set, autonomous) =>
15
+ async (test) => {
16
+ const remote = await stage.remote(id)
17
+
18
+ for (const [endpoint, samples] of Object.entries(set)) {
19
+ await test.test(endpoint, operation(remote, endpoint, samples, autonomous))
20
+ }
21
+
22
+ await remote.disconnect()
23
+ }
24
+
25
+ exports.component = component
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const { operations } = require('./operations')
4
+ const { messages } = require('./messages')
5
+
6
+ exports.operations = operations
7
+ exports.messages = messages
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+
3
+ const { binding } = require('@toa.io/userland/stage').binding
4
+
5
+ const translate = require('./translate')
6
+
7
+ /**
8
+ * @param {toa.samples.messages.Set} messages
9
+ * @param {boolean} autonomous
10
+ * @return {Function}
11
+ */
12
+ const messages = (messages, autonomous) =>
13
+ async (test) => {
14
+ for (const [label, samples] of Object.entries(messages)) {
15
+ await test.test(label, message(label, samples, autonomous))
16
+ }
17
+ }
18
+
19
+ /**
20
+ * @param {string} label
21
+ * @param {toa.samples.Message[]} samples
22
+ * @param {boolean} autonomous
23
+ * @returns {function}
24
+ */
25
+ const message = (label, samples, autonomous) =>
26
+ async (test) => {
27
+ let n = 0
28
+
29
+ for (const sample of samples) {
30
+ n++
31
+
32
+ const message = translate.message(sample, autonomous)
33
+ const name = sample.title ?? 'Sample #' + n
34
+
35
+ await test.test(name, async () => binding.emit(label, message))
36
+ }
37
+ }
38
+
39
+ exports.messages = messages
@@ -0,0 +1,28 @@
1
+ 'use strict'
2
+
3
+ const translate = require('./translate')
4
+
5
+ /**
6
+ * @param {toa.core.Component} remote
7
+ * @param {string} endpoint
8
+ * @param {toa.samples.Operation[]} samples
9
+ * @param {boolean} autonomous
10
+ * @returns {Function}
11
+ */
12
+ const operation = (remote, endpoint, samples, autonomous) =>
13
+ async (test) => {
14
+ let n = 0
15
+
16
+ for (const operation of samples) {
17
+ n++
18
+
19
+ const request = translate.operation(operation, autonomous)
20
+ const name = operation.title ?? 'Sample #' + n
21
+
22
+ await test.test(name, async () => remote.invoke(endpoint, request))
23
+ }
24
+
25
+ test.end()
26
+ }
27
+
28
+ exports.operation = operation