@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,11 @@
1
+ title: Third case
2
+ input:
3
+ foo: bar
4
+ output:
5
+ baz: true
6
+ ---
7
+ title: Fourth case
8
+ input:
9
+ foo: qux
10
+ output:
11
+ baz: false
@@ -0,0 +1,7 @@
1
+ title: Should undo
2
+ input: 1
3
+ output: true
4
+ ---
5
+ title: Should not undo
6
+ input: 0
7
+ output: false
@@ -0,0 +1,6 @@
1
+ title: Foobar
2
+ payload:
3
+ foo: bar
4
+ input: foo
5
+ query:
6
+ id: bar
@@ -0,0 +1,3 @@
1
+ name: test
2
+ packages: components/*
3
+ registry: whatever.none
@@ -0,0 +1,6 @@
1
+ title: Should observe
2
+ query:
3
+ id: 57022f11306149d8b246c9ea4a164079
4
+ output:
5
+ id: 57022f11306149d8b246c9ea4a164079
6
+ bar: 2
@@ -0,0 +1,10 @@
1
+ title: First sample
2
+ input:
3
+ foo: 1
4
+ output:
5
+ bar: 2
6
+ ---
7
+ input:
8
+ foo: 2
9
+ output:
10
+ bar: 3
@@ -0,0 +1,6 @@
1
+ title: First sample
2
+ component: dummies.dummy
3
+ payload:
4
+ foo: 1
5
+ input:
6
+ bar: 2
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ const { flip } = require('@toa.io/generic')
4
+
5
+ const components = jest.fn(async () => flip())
6
+ const mock = { components: { components } }
7
+
8
+ exports.mock = mock
@@ -0,0 +1,78 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('node:path')
4
+ const { directory: { glob } } = require('@toa.io/filesystem')
5
+
6
+ const fixtures = require('./context.fixtures')
7
+ const { suite } = require('./suite.mock')
8
+ const { replay } = require('./replay.mock')
9
+
10
+ const mock = { suite, replay, ...fixtures.mock }
11
+
12
+ jest.mock('../src/suite', () => mock.suite)
13
+ jest.mock('../src/replay', () => mock.replay)
14
+ jest.mock('../src/components', () => mock.components)
15
+
16
+ const { context } = require('../src/context')
17
+
18
+ it('should be', async () => {
19
+ expect(context).toBeDefined()
20
+ })
21
+
22
+ const CONTEXT = resolve(__dirname, './context')
23
+ const COMPONENTS = resolve(CONTEXT, 'components/*')
24
+
25
+ /** @type {string[]} */
26
+ let paths
27
+
28
+ /** @type {boolean} */
29
+ let ok
30
+
31
+ beforeAll(async () => {
32
+ paths = await glob(COMPONENTS)
33
+ })
34
+
35
+ beforeEach(async () => {
36
+ jest.clearAllMocks()
37
+
38
+ mock.components.components.mockImplementation(async () => true)
39
+
40
+ ok = await context(CONTEXT)
41
+ })
42
+
43
+ it('should replay context components sample sets', async () => {
44
+ expect(mock.components.components).toHaveBeenCalledWith(paths)
45
+ })
46
+
47
+ it('should load integration suite', async () => {
48
+ expect(mock.suite.context).toHaveBeenCalledWith(CONTEXT)
49
+ })
50
+
51
+ it('should replay integration suite', async () => {
52
+ const suite = await mock.suite.context.mock.results[0].value
53
+
54
+ expect(replay.replay).toHaveBeenCalledWith(suite, paths)
55
+ })
56
+
57
+ it('should return false if components replay failed', async () => {
58
+ jest.clearAllMocks()
59
+
60
+ mock.components.components.mockImplementation(async () => false)
61
+
62
+ ok = await context(CONTEXT)
63
+
64
+ expect(ok).toStrictEqual(false)
65
+ expect(replay.replay).not.toHaveBeenCalled()
66
+ })
67
+
68
+ it('should return replay result if components replay ok', async () => {
69
+ jest.clearAllMocks()
70
+
71
+ mock.components.components.mockImplementation(async () => true)
72
+
73
+ ok = await context(CONTEXT)
74
+
75
+ const mocked = await mock.replay.replay.mock.results[0].value
76
+
77
+ expect(ok).toStrictEqual(mocked)
78
+ })
@@ -0,0 +1,72 @@
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
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const replay = jest.fn(async () => generate())
6
+
7
+ exports.replay = { replay }
@@ -0,0 +1,116 @@
1
+ 'use strict'
2
+
3
+ const { resolve } = require('node:path')
4
+ const clone = require('clone-deep')
5
+
6
+ const { stage } = require('./stage.mock')
7
+ const { translate } = require('./replay.translate.mock')
8
+ const mock = { stage, translate }
9
+
10
+ jest.mock('@toa.io/userland/stage', () => mock.stage)
11
+ jest.mock('../src/.replay/.suite/translate', () => mock.translate)
12
+
13
+ const fixtures = require('./replay.fixtures')
14
+ const { replay } = require('../src')
15
+
16
+ it('should be', () => {
17
+ expect(replay).toBeDefined()
18
+ })
19
+
20
+ let ok
21
+
22
+ /** @type {toa.samples.Suite} */
23
+ let suite
24
+
25
+ const path = resolve(__dirname, '../../example/components/math/calculations')
26
+ const paths = [path]
27
+
28
+ beforeEach(async () => {
29
+ jest.clearAllMocks()
30
+
31
+ suite = clone(fixtures.suite)
32
+ ok = await replay(suite, paths)
33
+ })
34
+
35
+ it('should boot composition', () => {
36
+ expect(stage.composition).toHaveBeenCalled()
37
+ expect(stage.composition).toHaveBeenCalledWith(paths)
38
+ })
39
+
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)
44
+
45
+ return mock.translate.operation.mock.results[index].value
46
+ }
47
+
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]
55
+
56
+ if (call[0] === component) return stage.remote.mock.results[n].value
57
+ }
58
+
59
+ throw new Error(`Remote ${component} hasn't been connected`)
60
+ }
61
+
62
+ for (const [id, set] of Object.entries(fixtures.suite.operations)) {
63
+ const remote = await find(id)
64
+
65
+ for (const [endpoint, samples] of Object.entries(set)) {
66
+ for (const sample of samples) {
67
+ const request = translation(sample)
68
+
69
+ expect(remote.invoke).toHaveBeenCalledWith(endpoint, request)
70
+ }
71
+ }
72
+ }
73
+ })
74
+
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)
98
+ }
99
+ }
100
+ })
101
+
102
+ it('should shutdown stage', () => {
103
+ expect(stage.shutdown).toHaveBeenCalled()
104
+ })
105
+
106
+ it('should return results', () => {
107
+ expect(ok).toStrictEqual(true)
108
+ })
109
+
110
+ it.each(['messages', 'operations'])('should not throw if no %s defined', async (key) => {
111
+ delete suite[key]
112
+
113
+ ok = await replay(suite, paths)
114
+
115
+ expect(ok).toStrictEqual(true)
116
+ })
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { flip } = require('@toa.io/generic')
5
+
6
+ /** @type {toa.samples.Message} */
7
+ const message = {
8
+ title: generate(),
9
+ payload: { [generate()]: generate() },
10
+ input: generate(),
11
+ query: { id: generate() },
12
+ request: {
13
+ current: {
14
+ id: generate()
15
+ }
16
+ }
17
+ }
18
+
19
+ const autonomous = flip()
20
+ const component = generate()
21
+
22
+ exports.message = message
23
+ exports.autonomous = autonomous
24
+ exports.component = component
@@ -0,0 +1,123 @@
1
+ 'use strict'
2
+
3
+ const clone = require('clone-deep')
4
+
5
+ const fixtures = require('./replay.translate.message.fixtures')
6
+ const { message: translate } = require('../src/.replay/.suite/translate')
7
+ const { generate } = require('randomstring')
8
+
9
+ it('should be', async () => {
10
+ expect(translate).toBeDefined()
11
+ })
12
+
13
+ /** @type {toa.samples.Message} */
14
+ let declaration
15
+
16
+ /** @type {toa.sampling.Message} */
17
+ let message
18
+
19
+ /** @type {toa.sampling.messages.Sample} */
20
+ let sample
21
+
22
+ beforeEach(() => {
23
+ jest.clearAllMocks()
24
+
25
+ declaration = clone(fixtures.message)
26
+ message = translate(declaration, fixtures.autonomous, fixtures.component)
27
+ sample = message.sample
28
+ })
29
+
30
+ it('should copy payload', async () => {
31
+ expect(message.payload).toStrictEqual(declaration.payload)
32
+ })
33
+
34
+ it('should copy title', async () => {
35
+ expect(sample.request.title).toStrictEqual(declaration.title)
36
+ })
37
+
38
+ it('should copy autonomous', async () => {
39
+ expect(sample.request.autonomous).toStrictEqual(fixtures.autonomous)
40
+ })
41
+
42
+ it('should copy component', async () => {
43
+ expect(sample.component).toStrictEqual(fixtures.component)
44
+ })
45
+
46
+ it.each(['input', 'query'])('should copy %s to request', async (key) => {
47
+ const value = declaration[key]
48
+
49
+ expect(sample.request.request[key]).toStrictEqual(value)
50
+ })
51
+
52
+ it.each(['input', 'query'])('should create request with %s', async (key) => {
53
+ const value = declaration[key]
54
+
55
+ delete declaration.request
56
+
57
+ const { sample } = translate(declaration, fixtures.autonomous, fixtures.component)
58
+
59
+ expect(sample.request.request[key]).toStrictEqual(value)
60
+ })
61
+
62
+ it('should keep request properties', async () => {
63
+ expect(sample.request.storage.current).toMatchObject(fixtures.message.request.current)
64
+ })
65
+
66
+ it('should add `terminate: true` if request is not defined (and sample is autonomous)',
67
+ async () => {
68
+ delete declaration.request
69
+
70
+ message = translate(declaration, fixtures.autonomous, fixtures.component)
71
+
72
+ expect(message.sample.request.terminate).toStrictEqual(true)
73
+ })
74
+
75
+ it('should not create undefined keys in request', async () => {
76
+ delete declaration.input
77
+
78
+ message = translate(declaration, fixtures.autonomous, fixtures.component)
79
+
80
+ expect('input' in message.sample.request.request).toStrictEqual(false)
81
+ })
82
+
83
+ it('should declare sample as authentic', async () => {
84
+ expect(sample.authentic).toStrictEqual(true)
85
+ })
86
+
87
+ describe('validation', () => {
88
+ /** @type {toa.samples.Message} */
89
+ let declaration
90
+
91
+ const check = () => translate(declaration, fixtures.autonomous, fixtures.component)
92
+
93
+ beforeEach(() => {
94
+ declaration = clone(fixtures.message)
95
+ })
96
+
97
+ it('should throw on schema mismatch', async () => {
98
+ // noinspection JSValidateTypes
99
+ declaration.title = { foo: 1 }
100
+
101
+ expect(check).toThrow('must be string')
102
+ })
103
+
104
+ it('should throw on request schema mismatch', async () => {
105
+ // noinspection JSValidateTypes
106
+ declaration.request = { title: { foo: 1 } }
107
+
108
+ expect(check).toThrow('must be string')
109
+ })
110
+
111
+ it.each(['input', 'query'])('should throw if request.%s is defined', async (key) => {
112
+ expect.assertions(1)
113
+
114
+ declaration[key] = { id: generate() }
115
+ declaration.request = /** @type {toa.samples.Operation} */ { [key]: { id: generate() } }
116
+
117
+ try {
118
+ check()
119
+ } catch (exception) {
120
+ expect(exception).toMatchObject({ path: '/request' })
121
+ }
122
+ })
123
+ })
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ // noinspection JSCheckFunctionSignatures
6
+ const operation = jest.fn((declaration) => {
7
+ const { input, ...sample } = declaration
8
+
9
+ return { input, sample }
10
+ })
11
+
12
+ // noinspection JSCheckFunctionSignatures
13
+ const message = jest.fn((declaration) => generate())
14
+
15
+ const translate = { operation, message }
16
+
17
+ exports.translate = translate
@@ -0,0 +1,107 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { flip } = require('@toa.io/generic')
5
+
6
+ const title = generate()
7
+ const input = generate()
8
+ const output = generate()
9
+ const reply = { output }
10
+
11
+ const context = {}
12
+
13
+ context.declaration = {
14
+ local: {
15
+ do: {
16
+ input: generate(),
17
+ output: generate()
18
+ },
19
+ undo: [
20
+ {
21
+ input: generate()
22
+ },
23
+ {
24
+ output: generate()
25
+ }
26
+ ]
27
+ }
28
+ }
29
+
30
+ context.sample = {
31
+ local: {
32
+ do: [
33
+ {
34
+ request: {
35
+ input: context.declaration.local.do.input
36
+ },
37
+ reply: {
38
+ output: context.declaration.local.do.output
39
+ }
40
+ }
41
+ ],
42
+ undo: [
43
+ {
44
+ request: {
45
+ input: context.declaration.local.undo[0].input
46
+ }
47
+ },
48
+ {
49
+ reply: {
50
+ output: context.declaration.local.undo[1].output
51
+ }
52
+ }
53
+ ]
54
+ }
55
+ }
56
+
57
+ const storage = {}
58
+
59
+ storage.declaration = {
60
+ current: flip() ? { foo: generate() } : [{ foo: generate() }], // object or array
61
+ next: { foo: generate() }
62
+ }
63
+
64
+ storage.sample = {
65
+ current: storage.declaration.current,
66
+ next: storage.declaration.next
67
+ }
68
+
69
+ const events = { declaration: {}, sample: {} }
70
+ const label = generate()
71
+
72
+ events.declaration[label] = { [generate()]: generate() }
73
+ events.sample[label] = { payload: events.declaration[label] }
74
+
75
+ const extension = () => ([{ permanent: flip() }])
76
+
77
+ /** @type {toa.samples.Extensions} */
78
+ const extensions = { [generate()]: extension() }
79
+
80
+ /** @type {toa.samples.Operation} */
81
+ const declaration = {
82
+ title,
83
+ input,
84
+ output,
85
+ ...context.declaration,
86
+ ...storage.declaration,
87
+ events: events.declaration,
88
+ extensions
89
+ }
90
+
91
+ /** @type {toa.sampling.Request} */
92
+ const expected = {
93
+ input,
94
+ sample: {
95
+ authentic: true,
96
+ autonomous: true,
97
+ title,
98
+ reply,
99
+ context: context.sample,
100
+ storage: storage.sample,
101
+ events: events.sample,
102
+ extensions
103
+ }
104
+ }
105
+
106
+ exports.declaration = declaration
107
+ exports.expected = expected
@@ -0,0 +1,68 @@
1
+ 'use strict'
2
+
3
+ const clone = require('clone-deep')
4
+ const { generate } = require('randomstring')
5
+
6
+ const fixtures = require('./replay.translate.operation.fixtures')
7
+ const { operation: translate } = require('../src/.replay/.suite/translate')
8
+
9
+ it('should be', () => {
10
+ expect(translate).toBeDefined()
11
+ })
12
+
13
+ /** @type {toa.samples.Operation} */
14
+ let declaration
15
+
16
+ /** @type {toa.sampling.Request} */
17
+ let request
18
+
19
+ /** @type {toa.sampling.request.Sample} */
20
+ let expected
21
+
22
+ const autonomous = true
23
+
24
+ beforeEach(() => {
25
+ declaration = clone(fixtures.declaration)
26
+ expected = clone(fixtures.expected)
27
+ request = translate(declaration, autonomous)
28
+ })
29
+
30
+ it('should translate input', () => {
31
+ expect(request).toStrictEqual(expected)
32
+ })
33
+
34
+ describe('validation', () => {
35
+ it('should not allow additional properties', () => {
36
+ declaration.foo = generate()
37
+
38
+ expect(() => translate(declaration, true)).toThrow('must NOT have additional properties')
39
+ })
40
+ })
41
+
42
+ describe('specials', () => {
43
+ it('should transform configuration object to permanent sample', async () => {
44
+ const configuration = { foo: generate() }
45
+ const declaration = /** @type {toa.samples.Operation} */ { configuration }
46
+ const request = translate(declaration, true)
47
+
48
+ expect(request.sample.extensions.configuration).toStrictEqual([{
49
+ result: configuration,
50
+ permanent: true
51
+ }])
52
+ })
53
+
54
+ it('should throw if configuration is provided twice', async () => {
55
+ const configuration = { foo: generate() }
56
+
57
+ const declaration = {
58
+ configuration,
59
+ extensions: {
60
+ configuration: [{
61
+ result: configuration
62
+ }]
63
+ }
64
+ }
65
+
66
+ expect(() => translate(declaration, true)).toThrow('ambiguous')
67
+ })
68
+ })