@toa.io/norm 1.0.0-alpha.33 → 1.0.0-alpha.331

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 (100) hide show
  1. package/CHANGELOG.md +389 -0
  2. package/package.json +9 -7
  3. package/src/.component/.expand/bridge.js +2 -6
  4. package/src/.component/.expand/entity.js +2 -9
  5. package/src/.component/.expand/events.js +2 -6
  6. package/src/.component/.expand/extensions.js +8 -9
  7. package/src/.component/.expand/index.js +7 -19
  8. package/src/.component/.expand/operations.js +2 -9
  9. package/src/.component/.expand/receivers.js +4 -7
  10. package/src/.component/.expand/version.js +72 -28
  11. package/src/.component/.normalize/entity.js +7 -0
  12. package/src/.component/.normalize/events.js +16 -4
  13. package/src/.component/.normalize/index.js +4 -9
  14. package/src/.component/.normalize/operations.js +13 -6
  15. package/src/.component/.normalize/receivers.js +4 -5
  16. package/src/.component/collapse.js +60 -9
  17. package/src/.component/defaults.js +5 -11
  18. package/src/.component/dependencies.js +14 -10
  19. package/src/.component/dereference.js +5 -49
  20. package/src/.component/expand.js +3 -9
  21. package/src/.component/extensions.js +9 -10
  22. package/src/.component/index.js +10 -21
  23. package/src/.component/merge.js +25 -15
  24. package/src/.component/migrations.js +57 -0
  25. package/src/.component/normalize.js +10 -6
  26. package/src/.component/schema.yaml +165 -70
  27. package/src/.component/validate.js +67 -17
  28. package/src/.context/.dependencies/connectors.js +5 -9
  29. package/src/.context/.dependencies/describe.js +4 -5
  30. package/src/.context/.dependencies/extensions.js +98 -20
  31. package/src/.context/.dependencies/index.js +3 -9
  32. package/src/.context/.dependencies/resolve.js +44 -7
  33. package/src/.context/complete.js +1 -5
  34. package/src/.context/converge.js +31 -0
  35. package/src/.context/dependencies.js +44 -6
  36. package/src/.context/dereference.js +11 -6
  37. package/src/.context/evict.js +40 -0
  38. package/src/.context/expand.js +37 -5
  39. package/src/.context/index.js +8 -15
  40. package/src/.context/normalize.js +9 -7
  41. package/src/.context/schema.yaml +183 -11
  42. package/src/.context/validate.js +11 -14
  43. package/src/component.js +33 -14
  44. package/src/context.js +47 -15
  45. package/src/definition.js +63 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +7 -9
  48. package/src/manifest.js +159 -0
  49. package/src/map.js +26 -0
  50. package/src/shortcuts.js +12 -15
  51. package/test/component/collapse.fixtures.js +30 -39
  52. package/test/component/collapse.test.js +90 -10
  53. package/test/component/dependencies.test.js +16 -14
  54. package/test/component/dereference.fixtures.js +10 -98
  55. package/test/component/dereference.test.js +10 -14
  56. package/test/component/dummies/extension/index.js +1 -5
  57. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  58. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  59. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  60. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  61. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  62. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  63. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  64. package/test/component/expand.fixtures.js +13 -13
  65. package/test/component/expand.test.js +17 -13
  66. package/test/component/migrations.test.js +62 -0
  67. package/test/component/normalize.fixtures.js +3 -7
  68. package/test/component/normalize.test.js +94 -28
  69. package/test/component/validate.fixtures.js +17 -15
  70. package/test/component/validate.test.js +311 -108
  71. package/test/context/complete.fixtures.js +2 -8
  72. package/test/context/complete.test.js +14 -8
  73. package/test/context/converge.test.js +57 -0
  74. package/test/context/dereference.fixtures.js +3 -9
  75. package/test/context/dereference.test.js +18 -7
  76. package/test/context/evict.fixtures.js +39 -0
  77. package/test/context/evict.test.js +73 -0
  78. package/test/context/expand.fixtures.js +1 -5
  79. package/test/context/expand.test.js +36 -8
  80. package/test/context/normalize.fixtures.js +2 -7
  81. package/test/context/normalize.test.js +33 -9
  82. package/test/context/validate.fixtures.js +1 -6
  83. package/test/context/validate.test.js +173 -35
  84. package/test/definition.test.js +35 -0
  85. package/test/dummies/bare/index.js +1 -0
  86. package/test/dummies/defined/definition.js +1 -0
  87. package/test/dummies/defined/index.js +1 -0
  88. package/test/dummies/defined/package.json +1 -0
  89. package/test/manifest.test.js +137 -0
  90. package/test/shortcuts.fixtures.js +4 -9
  91. package/test/shortcuts.test.js +17 -15
  92. package/test/system.test.js +31 -0
  93. package/types/component.d.ts +32 -5
  94. package/types/context/declaration.d.ts +34 -3
  95. package/types/context.d.ts +27 -13
  96. package/types/entity.d.ts +7 -0
  97. package/types/index.d.ts +18 -3
  98. package/src/.component/.expand/properties.js +0 -14
  99. package/src/.context/.dependencies/load.js +0 -36
  100. package/test/context/dependencies.load.test.js +0 -24
@@ -1,11 +1,10 @@
1
- // noinspection JSUnresolvedVariable
1
+ import { describe, it, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- 'use strict'
4
+ import clone from 'clone-deep'
4
5
 
5
- const clone = require('clone-deep')
6
-
7
- const { validate } = require('../../src/.component')
8
- const fixtures = require('./validate.fixtures')
6
+ import { validate } from '../../src/.component/index.js'
7
+ import * as fixtures from './validate.fixtures.js'
9
8
 
10
9
  let manifest
11
10
 
@@ -13,248 +12,452 @@ beforeEach(() => {
13
12
  manifest = clone(fixtures.ok)
14
13
  })
15
14
 
16
- it('should be ok', () => {
17
- expect(() => validate(manifest)).not.toThrow()
15
+ it('should be ok', async () => {
16
+ await assert.doesNotReject(validate(manifest))
18
17
  })
19
18
 
20
- it('should provide error', () => {
19
+ it('should provide error', async () => {
21
20
  manifest.foo = 'bar'
22
21
 
23
- expect(() => validate(manifest)).toThrow(/must NOT have additional property/)
22
+ await assert.rejects(validate(manifest))
24
23
  })
25
24
 
26
- it('should not have additional properties', () => {
25
+ it('should not have additional properties', async () => {
27
26
  manifest.foo = 'bar'
28
27
 
29
- expect(() => validate(manifest)).toThrow(/must NOT have additional property/)
28
+ await assert.rejects(validate(manifest))
30
29
  })
31
30
 
32
31
  describe('namespace', () => {
33
- it('should match token pattern', () => {
32
+ it('should match token pattern', async () => {
34
33
  manifest.namespace = '1'
35
- expect(() => validate(manifest)).toThrow(/must match pattern/)
34
+ await assert.rejects(validate(manifest), (error) =>
35
+ /must match pattern/.test(error.message)
36
+ )
36
37
 
37
38
  manifest.namespace = 'foo_'
38
- expect(() => validate(manifest)).toThrow(/must match pattern/)
39
+ await assert.rejects(validate(manifest), (error) =>
40
+ /must match pattern/.test(error.message)
41
+ )
39
42
 
40
43
  manifest.namespace = 'foo_bar'
41
- expect(() => validate(manifest)).toThrow(/must match pattern/)
44
+ await assert.rejects(validate(manifest), (error) =>
45
+ /must match pattern/.test(error.message)
46
+ )
42
47
 
43
48
  manifest.namespace = 'foo-'
44
- expect(() => validate(manifest)).toThrow(/must match pattern/)
49
+ await assert.rejects(validate(manifest), (error) =>
50
+ /must match pattern/.test(error.message)
51
+ )
45
52
 
46
53
  manifest.namespace = 'foo-bar'
47
- expect(() => validate(manifest)).toThrow('must match pattern')
54
+ await assert.rejects(validate(manifest), (error) =>
55
+ /must match pattern/.test(error.message)
56
+ )
48
57
 
49
58
  manifest.namespace = 'FooBar12'
50
- expect(() => validate(manifest)).not.toThrow()
59
+ await assert.doesNotReject(validate(manifest))
51
60
  })
52
61
 
53
- it('should forbid \'system\' namespace', () => {
62
+ it("should forbid 'system' namespace", async () => {
54
63
  manifest.namespace = 'system'
55
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
56
- })
57
-
58
- it('should set `default` namespace', async () => {
59
- delete manifest.namespace
60
-
61
- expect(() => validate(manifest)).not.toThrow()
62
- expect(manifest.namespace).toStrictEqual('default')
64
+ await assert.rejects(validate(manifest), (error) =>
65
+ /must NOT be valid/.test(error.message)
66
+ )
63
67
  })
64
68
  })
65
69
 
66
70
  describe('name', () => {
67
- it('should be optional', () => {
71
+ it('should be optional', async () => {
68
72
  delete manifest.name
69
- expect(() => validate(manifest)).not.toThrow()
73
+ await assert.doesNotReject(validate(manifest))
70
74
  })
71
75
  })
72
76
 
73
77
  describe('entity', () => {
74
- it('should be optional', () => {
78
+ it('should be optional', async () => {
75
79
  delete manifest.entity
76
- expect(() => validate(manifest)).not.toThrow()
80
+ await assert.doesNotReject(validate(manifest))
77
81
  })
78
82
 
79
- it('should be object', () => {
83
+ it('should be object', async () => {
80
84
  manifest.entity = 'foo'
81
- expect(() => validate(manifest)).toThrow(/must be object/)
85
+ await assert.rejects(validate(manifest), (error) =>
86
+ /must be object/.test(error.message)
87
+ )
82
88
  })
83
89
 
84
- it('should not have additional properties', () => {
90
+ it('should not have additional properties', async () => {
85
91
  manifest.entity.foo = 'bar'
86
- expect(() => validate(manifest)).toThrow(/must NOT have additional property/)
92
+ await assert.rejects(validate(manifest))
87
93
  })
88
94
 
89
- describe('schema', () => {
90
- it('should be required', () => {
91
- delete manifest.entity.schema
92
- expect(() => validate(manifest)).toThrow(/must have required property/)
93
- })
94
-
95
- it('should be JSON schema object', () => {
96
- manifest.entity.schema = { properties: { foo: 'bar' } }
97
- expect(() => validate(manifest)).toThrow()
95
+ describe('properties', () => {
96
+ it('should be required', async () => {
97
+ delete manifest.entity.properties
98
+ await assert.rejects(validate(manifest))
98
99
  })
99
100
 
100
- it('should be JSON schema object of type object', () => {
101
- manifest.entity.schema = { type: 'integer' }
102
- expect(() => validate(manifest)).toThrow(/must be equal to constant 'object'/)
103
-
104
- manifest.entity.schema = {}
105
- validate(manifest)
106
- expect(manifest.entity.schema.type).toBe('object')
101
+ it('should be JSON schemas', async () => {
102
+ manifest.entity.properties.foo = 'bar'
103
+ await assert.rejects(validate(manifest))
107
104
  })
108
105
 
109
- it('should have property names matching token pattern', () => {
110
- manifest.entity.schema.properties._foo = { type: 'string' }
111
- expect(() => validate(manifest)).toThrow(/pattern/)
106
+ it('should have names matching token pattern', async () => {
107
+ manifest.entity.properties._foo = { type: 'string' }
108
+ await assert.rejects(validate(manifest), (error) => /pattern/.test(error.message))
112
109
  })
113
110
 
114
- it('should allow default id', () => {
115
- manifest.entity.schema.properties.id = {
111
+ it('should allow own id', async () => {
112
+ manifest.entity.properties.id = {
116
113
  type: 'string',
117
114
  pattern: '^[a-fA-F0-9]+$'
118
115
  }
119
- expect(() => validate(manifest)).not.toThrow()
116
+ await assert.doesNotReject(validate(manifest))
117
+ })
118
+
119
+ it('should not declare TRAILERS', async () => {
120
+ manifest.entity.properties.TRAILERS = { type: 'object' }
121
+ await assert.rejects(validate(manifest), (error) =>
122
+ /System property 'TRAILERS' cannot be declared/.test(error.message)
123
+ )
124
+ })
125
+
126
+ it('should not declare DISCARD', async () => {
127
+ manifest.entity.properties.DISCARD = { type: 'boolean' }
128
+ await assert.rejects(validate(manifest), (error) =>
129
+ /System property 'DISCARD' cannot be declared/.test(error.message)
130
+ )
120
131
  })
121
132
  })
122
133
 
123
- describe('associated', () => {
124
- it('should provide default', () => {
125
- expect(() => validate(manifest)).not.toThrow()
126
- expect(manifest.entity.associated).toBe(false)
134
+ describe('required', () => {
135
+ it('should name declared properties', async () => {
136
+ manifest.entity.required = ['name']
137
+ await assert.doesNotReject(validate(manifest))
138
+
139
+ manifest.entity.required = ['nope']
140
+ await assert.rejects(validate(manifest), (error) =>
141
+ /requires property 'nope'/.test(error.message)
142
+ )
143
+ })
144
+ })
145
+
146
+ describe('blank', () => {
147
+ it('should name declared properties', async () => {
148
+ manifest.entity.blank = { name: 'whatever' }
149
+ await assert.doesNotReject(validate(manifest))
150
+
151
+ manifest.entity.blank = { nope: 1 }
152
+ await assert.rejects(validate(manifest), (error) =>
153
+ /names property 'nope'/.test(error.message)
154
+ )
155
+ })
156
+
157
+ it('should not name a system property', async () => {
158
+ manifest.entity.blank = { VERSION: 1 }
159
+ await assert.rejects(validate(manifest), (error) =>
160
+ /must NOT be valid/.test(error.message)
161
+ )
162
+ })
163
+
164
+ it('should not name TRAILERS', async () => {
165
+ manifest.entity.blank = { TRAILERS: {} }
166
+ await assert.rejects(validate(manifest), (error) =>
167
+ /must NOT be valid/.test(error.message)
168
+ )
169
+ })
170
+
171
+ it('should not name DISCARD', async () => {
172
+ manifest.entity.blank = { DISCARD: true }
173
+ await assert.rejects(validate(manifest), (error) =>
174
+ /must NOT be valid/.test(error.message)
175
+ )
127
176
  })
128
177
  })
129
178
  })
130
179
 
131
180
  describe('bindings', () => {
132
- it('should be array of unique strings', () => {
181
+ it('should be array of unique strings', async () => {
133
182
  manifest.bindings = 'oops'
134
- expect(() => validate(manifest)).toThrow(/must be array/)
183
+ await assert.rejects(validate(manifest), (error) =>
184
+ /must be array/.test(error.message)
185
+ )
135
186
 
136
187
  manifest.bindings = ['oops', 'oops']
137
- expect(() => validate(manifest)).toThrow(/duplicate items/)
188
+ await assert.rejects(validate(manifest), (error) =>
189
+ /duplicate items/.test(error.message)
190
+ )
138
191
 
139
192
  manifest.bindings = ['oops', {}]
140
- expect(() => validate(manifest)).toThrow(/must be string/)
193
+ await assert.rejects(validate(manifest), (error) =>
194
+ /must be string/.test(error.message)
195
+ )
141
196
  })
142
197
 
143
- it('should forbid explicit loop', () => {
198
+ it('should forbid explicit loop', async () => {
144
199
  manifest.bindings = ['@toa.io/bindings.loop']
145
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
200
+ await assert.rejects(validate(manifest), (error) =>
201
+ /must NOT be valid/.test(error.message)
202
+ )
146
203
  })
147
204
  })
148
205
 
149
206
  describe('operations', () => {
150
- it('should be object', () => {
207
+ it('should be object', async () => {
151
208
  manifest.operations.get = 'bar'
152
- expect(() => validate(manifest)).toThrow(/must be object/)
209
+ await assert.rejects(validate(manifest), (error) =>
210
+ /must be object/.test(error.message)
211
+ )
153
212
  })
154
213
 
155
- it('should not have additional properties', () => {
214
+ it('should not have additional properties', async () => {
156
215
  manifest.operations.get.foo = 'bar'
157
- expect(() => validate(manifest)).toThrow(/additional property/)
216
+ await assert.rejects(validate(manifest))
158
217
  })
159
218
 
160
- it('should have type (transition or observation)', () => {
219
+ it('should have type (transition or observation)', async () => {
161
220
  delete manifest.operations.get.type
162
- expect(() => validate(manifest)).toThrow()
221
+ await assert.rejects(validate(manifest))
163
222
 
164
223
  manifest.operations.get.type = 'foo'
165
- expect(() => validate(manifest)).toThrow(/one of the allowed values/)
224
+ await assert.rejects(validate(manifest), (error) =>
225
+ /one of the allowed values/.test(error.message)
226
+ )
166
227
  })
167
228
 
168
- it('should forbid explicit loop', () => {
229
+ it('should forbid explicit loop', async () => {
169
230
  manifest.operations.get.bindings = ['@toa.io/bindings.loop']
170
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
231
+ await assert.rejects(validate(manifest), (error) =>
232
+ /must NOT be valid/.test(error.message)
233
+ )
171
234
  })
172
235
 
173
- it('should forbid query: false for observations', () => {
236
+ it('should forbid query: false for observations', async () => {
174
237
  manifest.operations.get.query = false
175
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
238
+ await assert.rejects(validate(manifest), (error) =>
239
+ /must NOT be valid/.test(error.message)
240
+ )
176
241
  })
177
242
 
178
- it.each([
179
- ['computation', 'compute']
180
- ])('should set query: false for %s', async (_, operation) => {
181
- validate(manifest)
243
+ for (const [_, operation] of [['computation', 'compute']])
244
+ it(`should set query: false for ${_}`, async () => {
245
+ validate(manifest)
182
246
 
183
- expect(manifest.operations[operation].query).toBe(false)
184
- })
247
+ assert.strictEqual(manifest.operations[operation].query, false)
248
+ })
185
249
 
186
250
  describe('scope', () => {
187
- it('should have scope', () => {
251
+ it('should have scope', async () => {
188
252
  delete manifest.operations.get.scope
189
- expect(() => validate(manifest)).toThrow(/required property/)
253
+ await assert.rejects(validate(manifest), (error) =>
254
+ /required property/.test(error.message)
255
+ )
190
256
  })
191
257
 
192
- it('should allow only entity or set for observations', () => {
258
+ it('should allow only entity or set for observations', async () => {
193
259
  manifest.operations.get.scope = 'changeset'
194
- expect(() => validate(manifest)).toThrow(/allowed values/)
260
+ await assert.rejects(validate(manifest), (error) =>
261
+ /allowed values/.test(error.message)
262
+ )
195
263
  })
196
264
 
197
- it('should allow only entity for transitions', () => {
265
+ it('should allow only entity for transitions', async () => {
198
266
  manifest.operations.add.scope = 'changeset'
199
- expect(() => validate(manifest)).toThrow(/allowed values/)
267
+ await assert.rejects(validate(manifest), (error) =>
268
+ /allowed values/.test(error.message)
269
+ )
200
270
 
201
271
  manifest.operations.add.scope = 'set'
202
- expect(() => validate(manifest)).toThrow(/allowed values/)
272
+ await assert.rejects(validate(manifest), (error) =>
273
+ /allowed values/.test(error.message)
274
+ )
275
+ })
276
+
277
+ it('should refuse the names the scopes had before `entry` and `entries`', async () => {
278
+ for (const scope of ['object', 'objects']) {
279
+ manifest.operations.get.scope = scope
280
+ await assert.rejects(validate(manifest), (error) =>
281
+ /allowed values/.test(error.message)
282
+ )
283
+ }
203
284
  })
204
285
 
205
- it('should allow only changeset for assignments', () => {
286
+ it('should allow only changeset for assignments', async () => {
206
287
  manifest.operations.set.scope = 'changeset'
207
- expect(() => validate(manifest)).not.toThrow()
288
+ await assert.doesNotReject(validate(manifest))
208
289
 
209
290
  manifest.operations.set.scope = 'set'
210
- expect(() => validate(manifest)).toThrow(/allowed values/)
291
+ await assert.rejects(validate(manifest), (error) =>
292
+ /allowed values/.test(error.message)
293
+ )
294
+ })
295
+ })
296
+
297
+ describe('once', () => {
298
+ it('should be true, false or a number of seconds', async () => {
299
+ for (const once of [true, false, 600, 86400]) {
300
+ manifest.operations.add.once = once
301
+ await assert.doesNotReject(validate(manifest), `once: ${once}`)
302
+ }
303
+ })
304
+
305
+ it('should stay true rather than be coerced into a number', async () => {
306
+ manifest.operations.add.once = true
307
+ await validate(manifest)
308
+
309
+ assert.strictEqual(manifest.operations.add.once, true)
310
+ })
311
+
312
+ it('should not be a window shorter than ten minutes', async () => {
313
+ manifest.operations.add.once = 599
314
+ await assert.rejects(validate(manifest), (error) =>
315
+ /must be >= 600/.test(error.message)
316
+ )
317
+ })
318
+
319
+ it('should not be a fraction of a second', async () => {
320
+ manifest.operations.add.once = 600.5
321
+ await assert.rejects(validate(manifest))
322
+ })
323
+
324
+ it('should throw for an operation that takes none', async () => {
325
+ manifest.operations.get.once = 600
326
+ await assert.rejects(validate(manifest))
211
327
  })
212
328
  })
213
329
 
214
330
  describe('concurrency', () => {
215
- it('should be required for transitions', () => {
331
+ it('should be required for transitions', async () => {
216
332
  delete manifest.operations.add.concurrency
217
- expect(() => validate(manifest)).toThrow(/required property/)
333
+ await assert.rejects(validate(manifest), (error) =>
334
+ /required property/.test(error.message)
335
+ )
218
336
  })
219
337
 
220
- it('should throw for observations, assignments', () => {
338
+ it('should throw for observations, assignments', async () => {
221
339
  manifest.operations.get.concurrency = 'none'
222
- expect(() => validate(manifest)).toThrow()
340
+ await assert.rejects(validate(manifest))
223
341
  delete manifest.operations.get.concurrency
224
342
 
225
343
  manifest.operations.set.concurrency = 'none'
226
- expect(() => validate(manifest)).toThrow()
344
+ await assert.rejects(validate(manifest))
227
345
  })
228
346
  })
229
347
 
230
348
  describe('input, output', () => {
231
- it('should be schema', () => {
349
+ it('should be schema', async () => {
232
350
  manifest.operations.get.input = { properties: { foo: 'bar' } }
233
- expect(() => validate(manifest)).toThrow()
351
+ await assert.rejects(validate(manifest))
234
352
 
235
353
  delete manifest.operations.get.input
236
354
  manifest.operations.get.output = { properties: { foo: 'bar' } }
237
- expect(() => validate(manifest)).toThrow()
355
+ await assert.rejects(validate(manifest))
238
356
  })
239
357
  })
240
358
  })
241
359
 
242
360
  describe('receivers', () => {
243
- it('should throw if transition points to undefined operation', () => {
361
+ it('should throw if transition points to undefined operation', async () => {
244
362
  manifest.receivers['foo.bar.happened'].operation = 'notExists'
245
363
 
246
- expect(() => validate(manifest)).toThrow(/refers to undefined operation/)
364
+ await assert.rejects(validate(manifest), (error) =>
365
+ /refers to undefined operation/.test(error.message)
366
+ )
247
367
  })
248
368
 
249
- it('should throw if transition points to observation', () => {
369
+ it('should throw if transition points to observation', async () => {
250
370
  manifest.receivers['foo.bar.happened'].operation = 'get'
251
371
 
252
- expect(() => validate(manifest)).toThrow(/one of the allowed types/)
372
+ await assert.rejects(validate(manifest), (error) =>
373
+ /of the allowed types/.test(error.message)
374
+ )
253
375
  })
254
376
 
255
377
  it('should throw if source has a name `context`', async () => {
256
378
  manifest.receivers['foo.bar.happened'].source = 'context'
257
379
 
258
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
380
+ await assert.rejects(validate(manifest), (error) =>
381
+ /must NOT be valid/.test(error.message)
382
+ )
383
+ })
384
+ })
385
+
386
+ describe('stateful', () => {
387
+ it('should be allowed on an operation', async () => {
388
+ const [operation] = Object.values(manifest.operations)
389
+
390
+ operation.stateful = true
391
+
392
+ await assert.doesNotReject(validate(manifest))
393
+ })
394
+
395
+ it('should be a boolean', async () => {
396
+ const [operation] = Object.values(manifest.operations)
397
+
398
+ operation.stateful = 'yes'
399
+
400
+ await assert.rejects(validate(manifest), (error) =>
401
+ /must be boolean/.test(error.message)
402
+ )
403
+ })
404
+ })
405
+
406
+ describe('unchained', () => {
407
+ it('should be allowed on an operation', async () => {
408
+ const [operation] = Object.values(manifest.operations)
409
+
410
+ operation.unchained = true
411
+
412
+ await assert.doesNotReject(validate(manifest))
413
+ })
414
+
415
+ it('should be a boolean', async () => {
416
+ const [operation] = Object.values(manifest.operations)
417
+
418
+ operation.unchained = 'yes'
419
+
420
+ await assert.rejects(validate(manifest), (error) =>
421
+ /must be boolean/.test(error.message)
422
+ )
423
+ })
424
+ })
425
+
426
+ describe('stream', () => {
427
+ beforeEach(() => {
428
+ manifest.operations.read = {
429
+ type: 'computation',
430
+ scope: 'none',
431
+ query: false,
432
+ bridge: 'whatever',
433
+ bindings: ['@toa.io/bindings.amqp'],
434
+ stream: 'content'
435
+ }
436
+ })
437
+
438
+ it('should reject an operation whose bindings carry no stream', async () => {
439
+ await assert.rejects(validate(manifest), (error) =>
440
+ /takes a stream, which none of its bindings carries/.test(error.message)
441
+ )
442
+ })
443
+
444
+ it('should accept an operation served where it is composed', async () => {
445
+ manifest.operations.read.bindings = []
446
+
447
+ await assert.doesNotReject(validate(manifest))
448
+ })
449
+
450
+ it('should accept an operation bound to a binding that carries a stream', async () => {
451
+ manifest.operations.read.bindings = ['@toa.io/bindings.http']
452
+
453
+ await assert.doesNotReject(validate(manifest))
454
+ })
455
+
456
+ it('should reject a transition that takes a stream', async () => {
457
+ manifest.operations.add.stream = 'content'
458
+
459
+ await assert.rejects(validate(manifest), (error) =>
460
+ /must be equal to one of the allowed values/.test(error.message)
461
+ )
259
462
  })
260
463
  })
@@ -1,5 +1,3 @@
1
- 'use strict'
2
-
3
1
  const component = (id) => {
4
2
  const [namespace, name] = id.split('.')
5
3
 
@@ -16,12 +14,11 @@ const component = (id) => {
16
14
  }
17
15
  }
18
16
 
19
- const context = {
17
+ export const context = {
20
18
  runtime: '0.0.0',
21
19
  name: 'test',
22
20
  description: 'context fixture',
23
21
  version: '0.0.0',
24
- packages: 'namespaces/**/*',
25
22
  registry: 'localhost:5000',
26
23
  components: [
27
24
  component('a.b'),
@@ -43,7 +40,7 @@ const context = {
43
40
  }
44
41
 
45
42
  /** @type {Array<toa.norm.context.Composition>} */
46
- const compositions = [
43
+ export const compositions = [
47
44
  ...context.compositions,
48
45
  {
49
46
  name: 'd-a',
@@ -54,6 +51,3 @@ const compositions = [
54
51
  components: [component('d.b')]
55
52
  }
56
53
  ]
57
-
58
- exports.context = context
59
- exports.compositions = compositions
@@ -1,9 +1,11 @@
1
- 'use strict'
1
+ import { it, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { isDeepStrictEqual } from 'node:util'
2
4
 
3
- const clone = require('clone-deep')
5
+ import clone from 'clone-deep'
4
6
 
5
- const { complete } = require('../../src/.context')
6
- const fixtures = require('./complete.fixtures')
7
+ import { complete } from '../../src/.context/index.js'
8
+ import * as fixtures from './complete.fixtures.js'
7
9
 
8
10
  /** @type {toa.norm.Context} */
9
11
  let context
@@ -14,8 +16,12 @@ beforeEach(() => {
14
16
  })
15
17
 
16
18
  it('should complete compositions', () => {
17
- expect(context.compositions.length).toEqual(fixtures.compositions.length)
18
- expect(context.compositions).toEqual(expect.arrayContaining(fixtures.compositions))
19
+ assert.deepStrictEqual(context.compositions.length, fixtures.compositions.length)
20
+ assert.ok(
21
+ fixtures.compositions.every((item) =>
22
+ context.compositions.some((candidate) => isDeepStrictEqual(candidate, item))
23
+ )
24
+ )
19
25
  })
20
26
 
21
27
  it('should create if compositions are not set', () => {
@@ -26,6 +32,6 @@ it('should create if compositions are not set', () => {
26
32
  components: [component]
27
33
  }))
28
34
 
29
- expect(() => complete(context)).not.toThrow()
30
- expect(context.compositions).toStrictEqual(compositions)
35
+ assert.doesNotThrow(() => complete(context))
36
+ assert.deepStrictEqual(context.compositions, compositions)
31
37
  })