@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.
- package/CHANGELOG.md +124 -0
- package/package.json +9 -7
- package/src/.component/.expand/bridge.js +2 -6
- package/src/.component/.expand/entity.js +2 -9
- package/src/.component/.expand/events.js +2 -6
- package/src/.component/.expand/extensions.js +5 -8
- package/src/.component/.expand/index.js +7 -17
- package/src/.component/.expand/operations.js +2 -9
- package/src/.component/.expand/receivers.js +4 -7
- package/src/.component/.expand/version.js +55 -0
- package/src/.component/.normalize/entity.js +7 -0
- package/src/.component/.normalize/events.js +16 -4
- package/src/.component/.normalize/index.js +4 -9
- package/src/.component/.normalize/operations.js +10 -6
- package/src/.component/.normalize/receivers.js +4 -5
- package/src/.component/collapse.js +60 -9
- package/src/.component/defaults.js +16 -25
- package/src/.component/dependencies.js +14 -10
- package/src/.component/dereference.js +20 -18
- package/src/.component/expand.js +6 -9
- package/src/.component/extensions.js +9 -10
- package/src/.component/index.js +10 -21
- package/src/.component/merge.js +25 -13
- package/src/.component/migrations.js +57 -0
- package/src/.component/normalize.js +10 -6
- package/src/.component/schema.yaml +139 -59
- package/src/.component/validate.js +37 -17
- package/src/.context/.dependencies/connectors.js +11 -10
- package/src/.context/.dependencies/describe.js +4 -5
- package/src/.context/.dependencies/extensions.js +93 -20
- package/src/.context/.dependencies/index.js +3 -9
- package/src/.context/.dependencies/resolve.js +44 -7
- package/src/.context/complete.js +1 -5
- package/src/.context/converge.js +31 -0
- package/src/.context/dependencies.js +43 -6
- package/src/.context/dereference.js +11 -6
- package/src/.context/evict.js +38 -0
- package/src/.context/expand.js +11 -6
- package/src/.context/index.js +8 -15
- package/src/.context/normalize.js +9 -7
- package/src/.context/schema.yaml +179 -11
- package/src/.context/validate.js +8 -14
- package/src/component.js +37 -18
- package/src/context.js +33 -14
- package/src/definition.js +56 -0
- package/src/entity.js +15 -0
- package/src/index.js +6 -9
- package/src/manifest.js +154 -0
- package/src/shortcuts.js +9 -14
- package/test/component/collapse.fixtures.js +30 -39
- package/test/component/collapse.test.js +90 -10
- package/test/component/dependencies.test.js +16 -14
- package/test/component/dereference.fixtures.js +15 -24
- package/test/component/dereference.test.js +18 -8
- package/test/component/dummies/extension/index.js +1 -5
- package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
- package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
- package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
- package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
- package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
- package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
- package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
- package/test/component/expand.fixtures.js +13 -12
- package/test/component/expand.test.js +17 -13
- package/test/component/migrations.test.js +62 -0
- package/test/component/normalize.fixtures.js +3 -7
- package/test/component/normalize.test.js +86 -28
- package/test/component/validate.fixtures.js +15 -13
- package/test/component/validate.test.js +181 -107
- package/test/context/complete.fixtures.js +2 -8
- package/test/context/complete.test.js +14 -8
- package/test/context/converge.test.js +57 -0
- package/test/context/dereference.fixtures.js +3 -9
- package/test/context/dereference.test.js +18 -7
- package/test/context/evict.fixtures.js +39 -0
- package/test/context/evict.test.js +72 -0
- package/test/context/expand.fixtures.js +1 -5
- package/test/context/expand.test.js +36 -8
- package/test/context/normalize.fixtures.js +2 -7
- package/test/context/normalize.test.js +33 -9
- package/test/context/validate.fixtures.js +1 -6
- package/test/context/validate.test.js +143 -34
- package/test/definition.test.js +35 -0
- package/test/dummies/bare/index.js +1 -0
- package/test/dummies/defined/definition.js +1 -0
- package/test/dummies/defined/index.js +1 -0
- package/test/dummies/defined/package.json +1 -0
- package/test/manifest.test.js +126 -0
- package/test/shortcuts.fixtures.js +4 -9
- package/test/shortcuts.test.js +17 -15
- package/types/component.d.ts +24 -5
- package/types/context/declaration.d.ts +33 -3
- package/types/context.d.ts +19 -13
- package/types/entity.d.ts +7 -0
- package/types/index.d.ts +12 -3
- package/src/.component/.expand/properties.js +0 -14
- package/src/.context/.dependencies/load.js +0 -36
- package/test/context/dependencies.load.test.js +0 -24
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import clone from 'clone-deep'
|
|
4
5
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
35
|
+
/must match pattern/.test(error.message)
|
|
36
|
+
)
|
|
36
37
|
|
|
37
38
|
manifest.namespace = 'foo_'
|
|
38
|
-
|
|
39
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
40
|
+
/must match pattern/.test(error.message)
|
|
41
|
+
)
|
|
39
42
|
|
|
40
43
|
manifest.namespace = 'foo_bar'
|
|
41
|
-
|
|
44
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
45
|
+
/must match pattern/.test(error.message)
|
|
46
|
+
)
|
|
42
47
|
|
|
43
48
|
manifest.namespace = 'foo-'
|
|
44
|
-
|
|
49
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
50
|
+
/must match pattern/.test(error.message)
|
|
51
|
+
)
|
|
45
52
|
|
|
46
53
|
manifest.namespace = 'foo-bar'
|
|
47
|
-
|
|
54
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
55
|
+
/must match pattern/.test(error.message)
|
|
56
|
+
)
|
|
48
57
|
|
|
49
58
|
manifest.namespace = 'FooBar12'
|
|
50
|
-
|
|
59
|
+
await assert.doesNotReject(validate(manifest))
|
|
51
60
|
})
|
|
52
61
|
|
|
53
|
-
it(
|
|
62
|
+
it("should forbid 'system' namespace", async () => {
|
|
54
63
|
manifest.namespace = 'system'
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
+
await assert.rejects(validate(manifest))
|
|
87
93
|
})
|
|
88
94
|
|
|
89
|
-
describe('
|
|
90
|
-
it('should be required', () => {
|
|
91
|
-
delete manifest.entity.
|
|
92
|
-
|
|
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
|
|
96
|
-
manifest.entity.
|
|
97
|
-
|
|
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
|
|
101
|
-
manifest.entity.
|
|
102
|
-
|
|
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
|
|
110
|
-
manifest.entity.
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
manifest
|
|
116
|
-
|
|
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('
|
|
121
|
-
it('should
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
155
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
156
|
+
/must be array/.test(error.message)
|
|
157
|
+
)
|
|
132
158
|
|
|
133
159
|
manifest.bindings = ['oops', 'oops']
|
|
134
|
-
|
|
160
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
161
|
+
/duplicate items/.test(error.message)
|
|
162
|
+
)
|
|
135
163
|
|
|
136
164
|
manifest.bindings = ['oops', {}]
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
193
|
+
await assert.rejects(validate(manifest))
|
|
160
194
|
|
|
161
195
|
manifest.operations.get.type = 'foo'
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
211
|
+
/must NOT be valid/.test(error.message)
|
|
212
|
+
)
|
|
173
213
|
})
|
|
174
214
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
239
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
240
|
+
/allowed values/.test(error.message)
|
|
241
|
+
)
|
|
198
242
|
|
|
199
243
|
manifest.operations.add.scope = 'set'
|
|
200
|
-
|
|
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
|
-
|
|
251
|
+
await assert.doesNotReject(validate(manifest))
|
|
206
252
|
|
|
207
253
|
manifest.operations.set.scope = 'set'
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
270
|
+
await assert.rejects(validate(manifest))
|
|
221
271
|
delete manifest.operations.get.concurrency
|
|
222
272
|
|
|
223
273
|
manifest.operations.set.concurrency = 'none'
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
import { it, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util'
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
import clone from 'clone-deep'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
import { it, before } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import clone from 'clone-deep'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
12
|
+
before(() => {
|
|
12
13
|
context = clone(fixtures.context)
|
|
13
14
|
dereference(context)
|
|
14
15
|
})
|
|
15
16
|
|
|
16
17
|
it('should dereference', () => {
|
|
17
|
-
|
|
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
|
-
|
|
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
|
})
|