@toa.io/norm 1.0.0-alpha.30 → 1.0.0-alpha.301

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 (98) hide show
  1. package/CHANGELOG.md +136 -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 +5 -8
  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 +38 -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 +16 -10
  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 +137 -73
  27. package/src/.component/validate.js +37 -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 +93 -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 +43 -6
  36. package/src/.context/dereference.js +11 -6
  37. package/src/.context/evict.js +38 -0
  38. package/src/.context/expand.js +11 -6
  39. package/src/.context/index.js +8 -15
  40. package/src/.context/normalize.js +9 -7
  41. package/src/.context/schema.yaml +178 -11
  42. package/src/.context/validate.js +8 -14
  43. package/src/component.js +36 -17
  44. package/src/context.js +44 -15
  45. package/src/definition.js +56 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +6 -9
  48. package/src/manifest.js +154 -0
  49. package/src/shortcuts.js +9 -14
  50. package/test/component/collapse.fixtures.js +30 -39
  51. package/test/component/collapse.test.js +90 -10
  52. package/test/component/dependencies.test.js +16 -14
  53. package/test/component/dereference.fixtures.js +10 -98
  54. package/test/component/dereference.test.js +10 -14
  55. package/test/component/dummies/extension/index.js +1 -5
  56. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  57. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  58. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  59. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  60. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  61. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  62. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  63. package/test/component/expand.fixtures.js +13 -13
  64. package/test/component/expand.test.js +17 -13
  65. package/test/component/migrations.test.js +62 -0
  66. package/test/component/normalize.fixtures.js +3 -7
  67. package/test/component/normalize.test.js +94 -28
  68. package/test/component/validate.fixtures.js +15 -13
  69. package/test/component/validate.test.js +180 -109
  70. package/test/context/complete.fixtures.js +2 -8
  71. package/test/context/complete.test.js +14 -8
  72. package/test/context/converge.test.js +57 -0
  73. package/test/context/dereference.fixtures.js +3 -9
  74. package/test/context/dereference.test.js +18 -7
  75. package/test/context/evict.fixtures.js +39 -0
  76. package/test/context/evict.test.js +72 -0
  77. package/test/context/expand.fixtures.js +1 -5
  78. package/test/context/expand.test.js +36 -8
  79. package/test/context/normalize.fixtures.js +2 -7
  80. package/test/context/normalize.test.js +33 -9
  81. package/test/context/validate.fixtures.js +1 -6
  82. package/test/context/validate.test.js +143 -35
  83. package/test/definition.test.js +35 -0
  84. package/test/dummies/bare/index.js +1 -0
  85. package/test/dummies/defined/definition.js +1 -0
  86. package/test/dummies/defined/index.js +1 -0
  87. package/test/dummies/defined/package.json +1 -0
  88. package/test/manifest.test.js +126 -0
  89. package/test/shortcuts.fixtures.js +4 -9
  90. package/test/shortcuts.test.js +17 -15
  91. package/types/component.d.ts +23 -4
  92. package/types/context/declaration.d.ts +33 -3
  93. package/types/context.d.ts +26 -13
  94. package/types/entity.d.ts +7 -0
  95. package/types/index.d.ts +12 -3
  96. package/src/.component/.expand/properties.js +0 -14
  97. package/src/.context/.dependencies/load.js +0 -36
  98. 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,249 +12,321 @@ 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/)
95
+ describe('properties', () => {
96
+ it('should be required', async () => {
97
+ delete manifest.entity.properties
98
+ await assert.rejects(validate(manifest))
93
99
  })
94
100
 
95
- it('should be JSON schema object', () => {
96
- manifest.entity.schema = { properties: { foo: 'bar' } }
97
- expect(() => validate(manifest)).toThrow()
98
- })
99
-
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))
120
117
  })
121
118
  })
122
119
 
123
- describe('associated', () => {
124
- it('should provide default', () => {
125
- expect(() => validate(manifest)).not.toThrow()
126
- expect(manifest.entity.associated).toBe(false)
120
+ describe('required', () => {
121
+ it('should name declared properties', async () => {
122
+ manifest.entity.required = ['name']
123
+ await assert.doesNotReject(validate(manifest))
124
+
125
+ manifest.entity.required = ['nope']
126
+ await assert.rejects(validate(manifest), (error) =>
127
+ /requires property 'nope'/.test(error.message)
128
+ )
129
+ })
130
+ })
131
+
132
+ describe('blank', () => {
133
+ it('should name declared properties', async () => {
134
+ manifest.entity.blank = { name: 'whatever' }
135
+ await assert.doesNotReject(validate(manifest))
136
+
137
+ manifest.entity.blank = { nope: 1 }
138
+ await assert.rejects(validate(manifest), (error) =>
139
+ /names property 'nope'/.test(error.message)
140
+ )
141
+ })
142
+
143
+ it('should not name a system property', async () => {
144
+ manifest.entity.blank = { VERSION: 1 }
145
+ await assert.rejects(validate(manifest), (error) =>
146
+ /must NOT be valid/.test(error.message)
147
+ )
127
148
  })
128
149
  })
129
150
  })
130
151
 
131
152
  describe('bindings', () => {
132
- it('should be array of unique strings', () => {
153
+ it('should be array of unique strings', async () => {
133
154
  manifest.bindings = 'oops'
134
- expect(() => validate(manifest)).toThrow(/must be array/)
155
+ await assert.rejects(validate(manifest), (error) =>
156
+ /must be array/.test(error.message)
157
+ )
135
158
 
136
159
  manifest.bindings = ['oops', 'oops']
137
- expect(() => validate(manifest)).toThrow(/duplicate items/)
160
+ await assert.rejects(validate(manifest), (error) =>
161
+ /duplicate items/.test(error.message)
162
+ )
138
163
 
139
164
  manifest.bindings = ['oops', {}]
140
- expect(() => validate(manifest)).toThrow(/must be string/)
165
+ await assert.rejects(validate(manifest), (error) =>
166
+ /must be string/.test(error.message)
167
+ )
141
168
  })
142
169
 
143
- it('should forbid explicit loop', () => {
170
+ it('should forbid explicit loop', async () => {
144
171
  manifest.bindings = ['@toa.io/bindings.loop']
145
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
172
+ await assert.rejects(validate(manifest), (error) =>
173
+ /must NOT be valid/.test(error.message)
174
+ )
146
175
  })
147
176
  })
148
177
 
149
178
  describe('operations', () => {
150
- it('should be object', () => {
179
+ it('should be object', async () => {
151
180
  manifest.operations.get = 'bar'
152
- expect(() => validate(manifest)).toThrow(/must be object/)
181
+ await assert.rejects(validate(manifest), (error) =>
182
+ /must be object/.test(error.message)
183
+ )
153
184
  })
154
185
 
155
- it('should not have additional properties', () => {
186
+ it('should not have additional properties', async () => {
156
187
  manifest.operations.get.foo = 'bar'
157
- expect(() => validate(manifest)).toThrow(/additional property/)
188
+ await assert.rejects(validate(manifest))
158
189
  })
159
190
 
160
- it('should have type (transition or observation)', () => {
191
+ it('should have type (transition or observation)', async () => {
161
192
  delete manifest.operations.get.type
162
- expect(() => validate(manifest)).toThrow()
193
+ await assert.rejects(validate(manifest))
163
194
 
164
195
  manifest.operations.get.type = 'foo'
165
- expect(() => validate(manifest)).toThrow(/one of the allowed values/)
196
+ await assert.rejects(validate(manifest), (error) =>
197
+ /one of the allowed values/.test(error.message)
198
+ )
166
199
  })
167
200
 
168
- it('should forbid explicit loop', () => {
201
+ it('should forbid explicit loop', async () => {
169
202
  manifest.operations.get.bindings = ['@toa.io/bindings.loop']
170
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
203
+ await assert.rejects(validate(manifest), (error) =>
204
+ /must NOT be valid/.test(error.message)
205
+ )
171
206
  })
172
207
 
173
- it('should forbid query: false for observations', () => {
208
+ it('should forbid query: false for observations', async () => {
174
209
  manifest.operations.get.query = false
175
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
210
+ await assert.rejects(validate(manifest), (error) =>
211
+ /must NOT be valid/.test(error.message)
212
+ )
176
213
  })
177
214
 
178
- it.each([
179
- ['computation', 'compute'],
180
- ['effect', 'affect']
181
- ])('should set query: false for %s', async (_, operation) => {
182
- validate(manifest)
215
+ for (const [_, operation] of [['computation', 'compute']])
216
+ it(`should set query: false for ${_}`, async () => {
217
+ validate(manifest)
183
218
 
184
- expect(manifest.operations[operation].query).toBe(false)
185
- })
219
+ assert.strictEqual(manifest.operations[operation].query, false)
220
+ })
186
221
 
187
222
  describe('scope', () => {
188
- it('should have scope', () => {
223
+ it('should have scope', async () => {
189
224
  delete manifest.operations.get.scope
190
- expect(() => validate(manifest)).toThrow(/required property/)
225
+ await assert.rejects(validate(manifest), (error) =>
226
+ /required property/.test(error.message)
227
+ )
191
228
  })
192
229
 
193
- it('should allow only entity or set for observations', () => {
230
+ it('should allow only entity or set for observations', async () => {
194
231
  manifest.operations.get.scope = 'changeset'
195
- expect(() => validate(manifest)).toThrow(/allowed values/)
232
+ await assert.rejects(validate(manifest), (error) =>
233
+ /allowed values/.test(error.message)
234
+ )
196
235
  })
197
236
 
198
- it('should allow only entity for transitions', () => {
237
+ it('should allow only entity for transitions', async () => {
199
238
  manifest.operations.add.scope = 'changeset'
200
- expect(() => validate(manifest)).toThrow(/allowed values/)
239
+ await assert.rejects(validate(manifest), (error) =>
240
+ /allowed values/.test(error.message)
241
+ )
201
242
 
202
243
  manifest.operations.add.scope = 'set'
203
- expect(() => validate(manifest)).toThrow(/allowed values/)
244
+ await assert.rejects(validate(manifest), (error) =>
245
+ /allowed values/.test(error.message)
246
+ )
204
247
  })
205
248
 
206
- it('should allow only changeset for assignments', () => {
249
+ it('should allow only changeset for assignments', async () => {
207
250
  manifest.operations.set.scope = 'changeset'
208
- expect(() => validate(manifest)).not.toThrow()
251
+ await assert.doesNotReject(validate(manifest))
209
252
 
210
253
  manifest.operations.set.scope = 'set'
211
- expect(() => validate(manifest)).toThrow(/allowed values/)
254
+ await assert.rejects(validate(manifest), (error) =>
255
+ /allowed values/.test(error.message)
256
+ )
212
257
  })
213
258
  })
214
259
 
215
260
  describe('concurrency', () => {
216
- it('should be required for transitions', () => {
261
+ it('should be required for transitions', async () => {
217
262
  delete manifest.operations.add.concurrency
218
- expect(() => validate(manifest)).toThrow(/required property/)
263
+ await assert.rejects(validate(manifest), (error) =>
264
+ /required property/.test(error.message)
265
+ )
219
266
  })
220
267
 
221
- it('should throw for observations, assignments', () => {
268
+ it('should throw for observations, assignments', async () => {
222
269
  manifest.operations.get.concurrency = 'none'
223
- expect(() => validate(manifest)).toThrow()
270
+ await assert.rejects(validate(manifest))
224
271
  delete manifest.operations.get.concurrency
225
272
 
226
273
  manifest.operations.set.concurrency = 'none'
227
- expect(() => validate(manifest)).toThrow()
274
+ await assert.rejects(validate(manifest))
228
275
  })
229
276
  })
230
277
 
231
278
  describe('input, output', () => {
232
- it('should be schema', () => {
279
+ it('should be schema', async () => {
233
280
  manifest.operations.get.input = { properties: { foo: 'bar' } }
234
- expect(() => validate(manifest)).toThrow()
281
+ await assert.rejects(validate(manifest))
235
282
 
236
283
  delete manifest.operations.get.input
237
284
  manifest.operations.get.output = { properties: { foo: 'bar' } }
238
- expect(() => validate(manifest)).toThrow()
285
+ await assert.rejects(validate(manifest))
239
286
  })
240
287
  })
241
288
  })
242
289
 
243
290
  describe('receivers', () => {
244
- it('should throw if transition points to undefined operation', () => {
291
+ it('should throw if transition points to undefined operation', async () => {
245
292
  manifest.receivers['foo.bar.happened'].operation = 'notExists'
246
293
 
247
- expect(() => validate(manifest)).toThrow(/refers to undefined operation/)
294
+ await assert.rejects(validate(manifest), (error) =>
295
+ /refers to undefined operation/.test(error.message)
296
+ )
248
297
  })
249
298
 
250
- it('should throw if transition points to observation', () => {
299
+ it('should throw if transition points to observation', async () => {
251
300
  manifest.receivers['foo.bar.happened'].operation = 'get'
252
301
 
253
- expect(() => validate(manifest)).toThrow(/one of the allowed types/)
302
+ await assert.rejects(validate(manifest), (error) =>
303
+ /of the allowed types/.test(error.message)
304
+ )
254
305
  })
255
306
 
256
307
  it('should throw if source has a name `context`', async () => {
257
308
  manifest.receivers['foo.bar.happened'].source = 'context'
258
309
 
259
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
310
+ await assert.rejects(validate(manifest), (error) =>
311
+ /must NOT be valid/.test(error.message)
312
+ )
313
+ })
314
+ })
315
+
316
+ describe('stateful', () => {
317
+ it('should be allowed on an operation', async () => {
318
+ const [operation] = Object.values(manifest.operations)
319
+
320
+ operation.stateful = true
321
+
322
+ await assert.doesNotReject(validate(manifest))
323
+ })
324
+
325
+ it('should be a boolean', async () => {
326
+ const [operation] = Object.values(manifest.operations)
327
+
328
+ operation.stateful = 'yes'
329
+
330
+ await assert.rejects(validate(manifest), (error) => /must be boolean/.test(error.message))
260
331
  })
261
332
  })
@@ -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
  })
@@ -0,0 +1,57 @@
1
+ import { it, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import { converge } from '../../src/.context/index.js'
5
+
6
+ const REFERENCE = '@toa.io/extensions.convergence'
7
+
8
+ /** @type {object} */
9
+ let context
10
+
11
+ /** @type {object[]} */
12
+ let components
13
+
14
+ const component = (id, properties = {}) => ({ id, entity: {}, ...properties })
15
+
16
+ beforeEach(() => {
17
+ context = { annotations: { [REFERENCE]: { priority: 0 } } }
18
+
19
+ components = [
20
+ component('store.orders'),
21
+ // what an extension ships reaches a deployment the same way and converges the same way:
22
+ // an application whose identity does not converge has no users in its second region
23
+ component('identity.basic'),
24
+ component('exposition.octets', { entity: undefined })
25
+ ]
26
+ })
27
+
28
+ it('should give the extension to every component that stores anything', () => {
29
+ converge(context, components)
30
+
31
+ assert.deepEqual(components[0].extensions, { [REFERENCE]: null })
32
+ assert.deepEqual(components[1].extensions, { [REFERENCE]: null })
33
+ })
34
+
35
+ it('should leave a component that stores nothing alone', () => {
36
+ converge(context, components)
37
+
38
+ assert.equal(components[2].extensions, undefined)
39
+ })
40
+
41
+ it('should do nothing where the context is not a region', () => {
42
+ delete context.annotations
43
+
44
+ converge(context, components)
45
+
46
+ for (const one of components) assert.equal(one.extensions, undefined)
47
+ })
48
+
49
+ it('should leave what a manifest declared as it was', () => {
50
+ const declaration = { some: 'declaration' }
51
+
52
+ components[0].extensions = { [REFERENCE]: declaration }
53
+
54
+ converge(context, components)
55
+
56
+ assert.equal(components[0].extensions[REFERENCE], declaration)
57
+ })
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- const clone = require('clone-deep')
1
+ import clone from 'clone-deep'
4
2
 
5
3
  /**
6
4
  * @param id {string}
@@ -22,12 +20,11 @@ const component = (id) => {
22
20
  }
23
21
  }
24
22
 
25
- const context = {
23
+ export const context = {
26
24
  name: 'test',
27
25
  description: 'context fixture',
28
26
  version: '0.0.0',
29
27
  runtime: '0.0.0',
30
- packages: 'namespaces/**/*',
31
28
  registry: 'localhost:5000',
32
29
  components: [component('a.b'), component('b.a'), component('d.c')],
33
30
  compositions: [
@@ -42,7 +39,7 @@ const context = {
42
39
  ]
43
40
  }
44
41
 
45
- const expected = clone(context)
42
+ export const expected = clone(context)
46
43
 
47
44
  expected.compositions = [
48
45
  {
@@ -54,6 +51,3 @@ expected.compositions = [
54
51
  components: [component('d.c'), component('a.b')]
55
52
  }
56
53
  ]
57
-
58
- exports.context = context
59
- exports.expected = expected
@@ -1,24 +1,35 @@
1
- 'use strict'
1
+ import { it, before } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const clone = require('clone-deep')
4
+ import clone from 'clone-deep'
4
5
 
5
- const { dereference } = require('../../src/.context')
6
- const fixtures = require('./dereference.fixtures')
6
+ import { dereference } from '../../src/.context/index.js'
7
+ import * as fixtures from './dereference.fixtures.js'
7
8
 
8
9
  /** @type {toa.norm.Context} */
9
10
  let context
10
11
 
11
- beforeAll(() => {
12
+ before(() => {
12
13
  context = clone(fixtures.context)
13
14
  dereference(context)
14
15
  })
15
16
 
16
17
  it('should dereference', () => {
17
- expect(context).toEqual(expect.objectContaining(fixtures.expected))
18
+ assert.partialDeepStrictEqual(context, fixtures.expected)
18
19
  })
19
20
 
20
21
  it('should not throw on empty compositions', () => {
21
22
  context.compositions = undefined
22
23
 
23
- expect(() => dereference(context)).not.toThrow()
24
+ assert.doesNotThrow(() => dereference(context))
25
+ })
26
+
27
+ it('should throw on an unknown component', () => {
28
+ const broken = clone(fixtures.context)
29
+
30
+ broken.compositions[1].components.push('nope.here')
31
+
32
+ assert.throws(() => dereference(broken), {
33
+ message: "Composition 'bar' lists an unknown component 'nope.here'."
34
+ })
24
35
  })