@toa.io/norm 1.0.0-alpha.3 → 1.0.0-alpha.300

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 +124 -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 -17
  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 +55 -0
  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 +10 -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 -25
  18. package/src/.component/dependencies.js +14 -10
  19. package/src/.component/dereference.js +20 -18
  20. package/src/.component/expand.js +6 -9
  21. package/src/.component/extensions.js +9 -10
  22. package/src/.component/index.js +10 -21
  23. package/src/.component/merge.js +25 -13
  24. package/src/.component/migrations.js +57 -0
  25. package/src/.component/normalize.js +10 -6
  26. package/src/.component/schema.yaml +139 -59
  27. package/src/.component/validate.js +37 -17
  28. package/src/.context/.dependencies/connectors.js +11 -10
  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 +179 -11
  42. package/src/.context/validate.js +8 -14
  43. package/src/component.js +37 -18
  44. package/src/context.js +33 -14
  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 +15 -24
  54. package/test/component/dereference.test.js +18 -8
  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 -12
  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 +86 -28
  68. package/test/component/validate.fixtures.js +15 -13
  69. package/test/component/validate.test.js +181 -107
  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 -34
  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 +24 -5
  92. package/types/context/declaration.d.ts +33 -3
  93. package/types/context.d.ts +19 -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,246 +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()
101
+ it('should be JSON schemas', async () => {
102
+ manifest.entity.properties.foo = 'bar'
103
+ await assert.rejects(validate(manifest))
98
104
  })
99
105
 
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')
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))
107
109
  })
108
110
 
109
- it('should have property names matching token pattern', () => {
110
- manifest.entity.schema.properties._foo = { type: 'string' }
111
- expect(() => validate(manifest)).toThrow(/pattern/)
111
+ it('should allow own id', async () => {
112
+ manifest.entity.properties.id = {
113
+ type: 'string',
114
+ pattern: '^[a-fA-F0-9]+$'
115
+ }
116
+ await assert.doesNotReject(validate(manifest))
112
117
  })
118
+ })
119
+
120
+ describe('required', () => {
121
+ it('should name declared properties', async () => {
122
+ manifest.entity.required = ['name']
123
+ await assert.doesNotReject(validate(manifest))
113
124
 
114
- it('should allow default id', () => {
115
- manifest.entity.schema.properties.id = { type: 'string', pattern: '^[a-fA-F0-9]+$' }
116
- expect(() => validate(manifest)).not.toThrow()
125
+ manifest.entity.required = ['nope']
126
+ await assert.rejects(validate(manifest), (error) =>
127
+ /requires property 'nope'/.test(error.message)
128
+ )
117
129
  })
118
130
  })
119
131
 
120
- describe('dependent', () => {
121
- it('should provide default', () => {
122
- expect(() => validate(manifest)).not.toThrow()
123
- expect(manifest.entity.dependent).toBe(false)
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
+ )
124
148
  })
125
149
  })
126
150
  })
127
151
 
128
152
  describe('bindings', () => {
129
- it('should be array of unique strings', () => {
153
+ it('should be array of unique strings', async () => {
130
154
  manifest.bindings = 'oops'
131
- expect(() => validate(manifest)).toThrow(/must be array/)
155
+ await assert.rejects(validate(manifest), (error) =>
156
+ /must be array/.test(error.message)
157
+ )
132
158
 
133
159
  manifest.bindings = ['oops', 'oops']
134
- expect(() => validate(manifest)).toThrow(/duplicate items/)
160
+ await assert.rejects(validate(manifest), (error) =>
161
+ /duplicate items/.test(error.message)
162
+ )
135
163
 
136
164
  manifest.bindings = ['oops', {}]
137
- expect(() => validate(manifest)).toThrow(/must be string/)
165
+ await assert.rejects(validate(manifest), (error) =>
166
+ /must be string/.test(error.message)
167
+ )
138
168
  })
139
169
 
140
- it('should forbid explicit loop', () => {
170
+ it('should forbid explicit loop', async () => {
141
171
  manifest.bindings = ['@toa.io/bindings.loop']
142
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
172
+ await assert.rejects(validate(manifest), (error) =>
173
+ /must NOT be valid/.test(error.message)
174
+ )
143
175
  })
144
176
  })
145
177
 
146
178
  describe('operations', () => {
147
- it('should be object', () => {
179
+ it('should be object', async () => {
148
180
  manifest.operations.get = 'bar'
149
- expect(() => validate(manifest)).toThrow(/must be object/)
181
+ await assert.rejects(validate(manifest), (error) =>
182
+ /must be object/.test(error.message)
183
+ )
150
184
  })
151
185
 
152
- it('should not have additional properties', () => {
186
+ it('should not have additional properties', async () => {
153
187
  manifest.operations.get.foo = 'bar'
154
- expect(() => validate(manifest)).toThrow(/additional property/)
188
+ await assert.rejects(validate(manifest))
155
189
  })
156
190
 
157
- it('should have type (transition or observation)', () => {
191
+ it('should have type (transition or observation)', async () => {
158
192
  delete manifest.operations.get.type
159
- expect(() => validate(manifest)).toThrow()
193
+ await assert.rejects(validate(manifest))
160
194
 
161
195
  manifest.operations.get.type = 'foo'
162
- 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
+ )
163
199
  })
164
200
 
165
- it('should forbid explicit loop', () => {
201
+ it('should forbid explicit loop', async () => {
166
202
  manifest.operations.get.bindings = ['@toa.io/bindings.loop']
167
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
203
+ await assert.rejects(validate(manifest), (error) =>
204
+ /must NOT be valid/.test(error.message)
205
+ )
168
206
  })
169
207
 
170
- it('should forbid query: false for observations', () => {
208
+ it('should forbid query: false for observations', async () => {
171
209
  manifest.operations.get.query = false
172
- expect(() => validate(manifest)).toThrow(/must NOT be valid/)
210
+ await assert.rejects(validate(manifest), (error) =>
211
+ /must NOT be valid/.test(error.message)
212
+ )
173
213
  })
174
214
 
175
- it.each([
176
- ['computation', 'compute'],
177
- ['effect', 'affect']
178
- ])('should set query: false for %s', async (_, operation) => {
179
- validate(manifest)
215
+ for (const [_, operation] of [['computation', 'compute']])
216
+ it(`should set query: false for ${_}`, async () => {
217
+ validate(manifest)
180
218
 
181
- expect(manifest.operations[operation].query).toBe(false)
182
- })
219
+ assert.strictEqual(manifest.operations[operation].query, false)
220
+ })
183
221
 
184
222
  describe('scope', () => {
185
- it('should have scope', () => {
223
+ it('should have scope', async () => {
186
224
  delete manifest.operations.get.scope
187
- expect(() => validate(manifest)).toThrow(/required property/)
225
+ await assert.rejects(validate(manifest), (error) =>
226
+ /required property/.test(error.message)
227
+ )
188
228
  })
189
229
 
190
- it('should allow only entity or set for observations', () => {
230
+ it('should allow only entity or set for observations', async () => {
191
231
  manifest.operations.get.scope = 'changeset'
192
- expect(() => validate(manifest)).toThrow(/allowed values/)
232
+ await assert.rejects(validate(manifest), (error) =>
233
+ /allowed values/.test(error.message)
234
+ )
193
235
  })
194
236
 
195
- it('should allow only entity for transitions', () => {
237
+ it('should allow only entity for transitions', async () => {
196
238
  manifest.operations.add.scope = 'changeset'
197
- expect(() => validate(manifest)).toThrow(/allowed values/)
239
+ await assert.rejects(validate(manifest), (error) =>
240
+ /allowed values/.test(error.message)
241
+ )
198
242
 
199
243
  manifest.operations.add.scope = 'set'
200
- expect(() => validate(manifest)).toThrow(/allowed values/)
244
+ await assert.rejects(validate(manifest), (error) =>
245
+ /allowed values/.test(error.message)
246
+ )
201
247
  })
202
248
 
203
- it('should allow only changeset for assignments', () => {
249
+ it('should allow only changeset for assignments', async () => {
204
250
  manifest.operations.set.scope = 'changeset'
205
- expect(() => validate(manifest)).not.toThrow()
251
+ await assert.doesNotReject(validate(manifest))
206
252
 
207
253
  manifest.operations.set.scope = 'set'
208
- expect(() => validate(manifest)).toThrow(/allowed values/)
254
+ await assert.rejects(validate(manifest), (error) =>
255
+ /allowed values/.test(error.message)
256
+ )
209
257
  })
210
258
  })
211
259
 
212
260
  describe('concurrency', () => {
213
- it('should be required for transitions', () => {
261
+ it('should be required for transitions', async () => {
214
262
  delete manifest.operations.add.concurrency
215
- expect(() => validate(manifest)).toThrow(/required property/)
263
+ await assert.rejects(validate(manifest), (error) =>
264
+ /required property/.test(error.message)
265
+ )
216
266
  })
217
267
 
218
- it('should throw for observations, assignments', () => {
268
+ it('should throw for observations, assignments', async () => {
219
269
  manifest.operations.get.concurrency = 'none'
220
- expect(() => validate(manifest)).toThrow()
270
+ await assert.rejects(validate(manifest))
221
271
  delete manifest.operations.get.concurrency
222
272
 
223
273
  manifest.operations.set.concurrency = 'none'
224
- expect(() => validate(manifest)).toThrow()
274
+ await assert.rejects(validate(manifest))
225
275
  })
226
276
  })
227
277
 
228
278
  describe('input, output', () => {
229
- it('should be schema', () => {
279
+ it('should be schema', async () => {
230
280
  manifest.operations.get.input = { properties: { foo: 'bar' } }
231
- expect(() => validate(manifest)).toThrow()
281
+ await assert.rejects(validate(manifest))
232
282
 
233
283
  delete manifest.operations.get.input
234
284
  manifest.operations.get.output = { properties: { foo: 'bar' } }
235
- expect(() => validate(manifest)).toThrow()
285
+ await assert.rejects(validate(manifest))
236
286
  })
237
287
  })
238
288
  })
239
289
 
240
290
  describe('receivers', () => {
241
- it('should throw if transition points to undefined operation', () => {
291
+ it('should throw if transition points to undefined operation', async () => {
242
292
  manifest.receivers['foo.bar.happened'].operation = 'notExists'
243
293
 
244
- expect(() => validate(manifest)).toThrow(/refers to undefined operation/)
294
+ await assert.rejects(validate(manifest), (error) =>
295
+ /refers to undefined operation/.test(error.message)
296
+ )
245
297
  })
246
298
 
247
- it('should throw if transition points to observation', () => {
299
+ it('should throw if transition points to observation', async () => {
248
300
  manifest.receivers['foo.bar.happened'].operation = 'get'
249
301
 
250
- 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
+ )
251
305
  })
252
306
 
253
307
  it('should throw if source has a name `context`', async () => {
254
308
  manifest.receivers['foo.bar.happened'].source = 'context'
255
309
 
256
- 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))
257
331
  })
258
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
  })