@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.
- package/CHANGELOG.md +136 -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 -19
- package/src/.component/.expand/operations.js +2 -9
- package/src/.component/.expand/receivers.js +4 -7
- package/src/.component/.expand/version.js +38 -28
- 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 +13 -6
- package/src/.component/.normalize/receivers.js +4 -5
- package/src/.component/collapse.js +60 -9
- package/src/.component/defaults.js +16 -10
- package/src/.component/dependencies.js +14 -10
- package/src/.component/dereference.js +5 -49
- package/src/.component/expand.js +3 -9
- package/src/.component/extensions.js +9 -10
- package/src/.component/index.js +10 -21
- package/src/.component/merge.js +25 -15
- package/src/.component/migrations.js +57 -0
- package/src/.component/normalize.js +10 -6
- package/src/.component/schema.yaml +137 -73
- package/src/.component/validate.js +37 -17
- package/src/.context/.dependencies/connectors.js +5 -9
- 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 +178 -11
- package/src/.context/validate.js +8 -14
- package/src/component.js +36 -17
- package/src/context.js +44 -15
- 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 +10 -98
- package/test/component/dereference.test.js +10 -14
- 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 -13
- 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 +94 -28
- package/test/component/validate.fixtures.js +15 -13
- package/test/component/validate.test.js +180 -109
- 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 -35
- 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 +23 -4
- package/types/context/declaration.d.ts +33 -3
- package/types/context.d.ts +26 -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,249 +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
|
-
|
|
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
|
|
110
|
-
manifest.entity.
|
|
111
|
-
|
|
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
|
|
115
|
-
manifest.entity.
|
|
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
|
-
|
|
116
|
+
await assert.doesNotReject(validate(manifest))
|
|
120
117
|
})
|
|
121
118
|
})
|
|
122
119
|
|
|
123
|
-
describe('
|
|
124
|
-
it('should
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
155
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
156
|
+
/must be array/.test(error.message)
|
|
157
|
+
)
|
|
135
158
|
|
|
136
159
|
manifest.bindings = ['oops', 'oops']
|
|
137
|
-
|
|
160
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
161
|
+
/duplicate items/.test(error.message)
|
|
162
|
+
)
|
|
138
163
|
|
|
139
164
|
manifest.bindings = ['oops', {}]
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
193
|
+
await assert.rejects(validate(manifest))
|
|
163
194
|
|
|
164
195
|
manifest.operations.get.type = 'foo'
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
211
|
+
/must NOT be valid/.test(error.message)
|
|
212
|
+
)
|
|
176
213
|
})
|
|
177
214
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
239
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
240
|
+
/allowed values/.test(error.message)
|
|
241
|
+
)
|
|
201
242
|
|
|
202
243
|
manifest.operations.add.scope = 'set'
|
|
203
|
-
|
|
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
|
-
|
|
251
|
+
await assert.doesNotReject(validate(manifest))
|
|
209
252
|
|
|
210
253
|
manifest.operations.set.scope = 'set'
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
|
|
270
|
+
await assert.rejects(validate(manifest))
|
|
224
271
|
delete manifest.operations.get.concurrency
|
|
225
272
|
|
|
226
273
|
manifest.operations.set.concurrency = 'none'
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
})
|