@toa.io/norm 1.0.0-alpha.32 → 1.0.0-alpha.320
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 +298 -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 +7 -9
- 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 +69 -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 +5 -11
- 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 +162 -73
- package/src/.component/validate.js +63 -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 +183 -11
- package/src/.context/validate.js +11 -14
- package/src/component.js +33 -14
- package/src/context.js +44 -15
- package/src/definition.js +63 -0
- package/src/entity.js +15 -0
- package/src/index.js +7 -9
- package/src/manifest.js +154 -0
- package/src/map.js +24 -0
- package/src/shortcuts.js +11 -15
- 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 +17 -15
- package/test/component/validate.test.js +271 -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 +73 -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 +173 -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/test/system.test.js +31 -0
- package/types/component.d.ts +29 -4
- package/types/context/declaration.d.ts +34 -3
- package/types/context.d.ts +27 -13
- package/types/entity.d.ts +7 -0
- package/types/index.d.ts +18 -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,412 @@ 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))
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should not declare TRAILERS', async () => {
|
|
120
|
+
manifest.entity.properties.TRAILERS = { type: 'object' }
|
|
121
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
122
|
+
/System property 'TRAILERS' cannot be declared/.test(error.message)
|
|
123
|
+
)
|
|
120
124
|
})
|
|
121
125
|
})
|
|
122
126
|
|
|
123
|
-
describe('
|
|
124
|
-
it('should
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
describe('required', () => {
|
|
128
|
+
it('should name declared properties', async () => {
|
|
129
|
+
manifest.entity.required = ['name']
|
|
130
|
+
await assert.doesNotReject(validate(manifest))
|
|
131
|
+
|
|
132
|
+
manifest.entity.required = ['nope']
|
|
133
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
134
|
+
/requires property 'nope'/.test(error.message)
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('blank', () => {
|
|
140
|
+
it('should name declared properties', async () => {
|
|
141
|
+
manifest.entity.blank = { name: 'whatever' }
|
|
142
|
+
await assert.doesNotReject(validate(manifest))
|
|
143
|
+
|
|
144
|
+
manifest.entity.blank = { nope: 1 }
|
|
145
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
146
|
+
/names property 'nope'/.test(error.message)
|
|
147
|
+
)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('should not name a system property', async () => {
|
|
151
|
+
manifest.entity.blank = { VERSION: 1 }
|
|
152
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
153
|
+
/must NOT be valid/.test(error.message)
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should not name TRAILERS', async () => {
|
|
158
|
+
manifest.entity.blank = { TRAILERS: {} }
|
|
159
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
160
|
+
/must NOT be valid/.test(error.message)
|
|
161
|
+
)
|
|
127
162
|
})
|
|
128
163
|
})
|
|
129
164
|
})
|
|
130
165
|
|
|
131
166
|
describe('bindings', () => {
|
|
132
|
-
it('should be array of unique strings', () => {
|
|
167
|
+
it('should be array of unique strings', async () => {
|
|
133
168
|
manifest.bindings = 'oops'
|
|
134
|
-
|
|
169
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
170
|
+
/must be array/.test(error.message)
|
|
171
|
+
)
|
|
135
172
|
|
|
136
173
|
manifest.bindings = ['oops', 'oops']
|
|
137
|
-
|
|
174
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
175
|
+
/duplicate items/.test(error.message)
|
|
176
|
+
)
|
|
138
177
|
|
|
139
178
|
manifest.bindings = ['oops', {}]
|
|
140
|
-
|
|
179
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
180
|
+
/must be string/.test(error.message)
|
|
181
|
+
)
|
|
141
182
|
})
|
|
142
183
|
|
|
143
|
-
it('should forbid explicit loop', () => {
|
|
184
|
+
it('should forbid explicit loop', async () => {
|
|
144
185
|
manifest.bindings = ['@toa.io/bindings.loop']
|
|
145
|
-
|
|
186
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
187
|
+
/must NOT be valid/.test(error.message)
|
|
188
|
+
)
|
|
146
189
|
})
|
|
147
190
|
})
|
|
148
191
|
|
|
149
192
|
describe('operations', () => {
|
|
150
|
-
it('should be object', () => {
|
|
193
|
+
it('should be object', async () => {
|
|
151
194
|
manifest.operations.get = 'bar'
|
|
152
|
-
|
|
195
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
196
|
+
/must be object/.test(error.message)
|
|
197
|
+
)
|
|
153
198
|
})
|
|
154
199
|
|
|
155
|
-
it('should not have additional properties', () => {
|
|
200
|
+
it('should not have additional properties', async () => {
|
|
156
201
|
manifest.operations.get.foo = 'bar'
|
|
157
|
-
|
|
202
|
+
await assert.rejects(validate(manifest))
|
|
158
203
|
})
|
|
159
204
|
|
|
160
|
-
it('should have type (transition or observation)', () => {
|
|
205
|
+
it('should have type (transition or observation)', async () => {
|
|
161
206
|
delete manifest.operations.get.type
|
|
162
|
-
|
|
207
|
+
await assert.rejects(validate(manifest))
|
|
163
208
|
|
|
164
209
|
manifest.operations.get.type = 'foo'
|
|
165
|
-
|
|
210
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
211
|
+
/one of the allowed values/.test(error.message)
|
|
212
|
+
)
|
|
166
213
|
})
|
|
167
214
|
|
|
168
|
-
it('should forbid explicit loop', () => {
|
|
215
|
+
it('should forbid explicit loop', async () => {
|
|
169
216
|
manifest.operations.get.bindings = ['@toa.io/bindings.loop']
|
|
170
|
-
|
|
217
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
218
|
+
/must NOT be valid/.test(error.message)
|
|
219
|
+
)
|
|
171
220
|
})
|
|
172
221
|
|
|
173
|
-
it('should forbid query: false for observations', () => {
|
|
222
|
+
it('should forbid query: false for observations', async () => {
|
|
174
223
|
manifest.operations.get.query = false
|
|
175
|
-
|
|
224
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
225
|
+
/must NOT be valid/.test(error.message)
|
|
226
|
+
)
|
|
176
227
|
})
|
|
177
228
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
])('should set query: false for %s', async (_, operation) => {
|
|
182
|
-
validate(manifest)
|
|
229
|
+
for (const [_, operation] of [['computation', 'compute']])
|
|
230
|
+
it(`should set query: false for ${_}`, async () => {
|
|
231
|
+
validate(manifest)
|
|
183
232
|
|
|
184
|
-
|
|
185
|
-
|
|
233
|
+
assert.strictEqual(manifest.operations[operation].query, false)
|
|
234
|
+
})
|
|
186
235
|
|
|
187
236
|
describe('scope', () => {
|
|
188
|
-
it('should have scope', () => {
|
|
237
|
+
it('should have scope', async () => {
|
|
189
238
|
delete manifest.operations.get.scope
|
|
190
|
-
|
|
239
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
240
|
+
/required property/.test(error.message)
|
|
241
|
+
)
|
|
191
242
|
})
|
|
192
243
|
|
|
193
|
-
it('should allow only entity or set for observations', () => {
|
|
244
|
+
it('should allow only entity or set for observations', async () => {
|
|
194
245
|
manifest.operations.get.scope = 'changeset'
|
|
195
|
-
|
|
246
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
247
|
+
/allowed values/.test(error.message)
|
|
248
|
+
)
|
|
196
249
|
})
|
|
197
250
|
|
|
198
|
-
it('should allow only entity for transitions', () => {
|
|
251
|
+
it('should allow only entity for transitions', async () => {
|
|
199
252
|
manifest.operations.add.scope = 'changeset'
|
|
200
|
-
|
|
253
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
254
|
+
/allowed values/.test(error.message)
|
|
255
|
+
)
|
|
201
256
|
|
|
202
257
|
manifest.operations.add.scope = 'set'
|
|
203
|
-
|
|
258
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
259
|
+
/allowed values/.test(error.message)
|
|
260
|
+
)
|
|
204
261
|
})
|
|
205
262
|
|
|
206
|
-
it('should
|
|
263
|
+
it('should refuse the names the scopes had before `entry` and `entries`', async () => {
|
|
264
|
+
for (const scope of ['object', 'objects']) {
|
|
265
|
+
manifest.operations.get.scope = scope
|
|
266
|
+
await assert.rejects(validate(manifest), (error) => /allowed values/.test(error.message))
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('should allow only changeset for assignments', async () => {
|
|
207
271
|
manifest.operations.set.scope = 'changeset'
|
|
208
|
-
|
|
272
|
+
await assert.doesNotReject(validate(manifest))
|
|
209
273
|
|
|
210
274
|
manifest.operations.set.scope = 'set'
|
|
211
|
-
|
|
275
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
276
|
+
/allowed values/.test(error.message)
|
|
277
|
+
)
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
describe('once', () => {
|
|
282
|
+
it('should be true, false or a number of seconds', async () => {
|
|
283
|
+
for (const once of [true, false, 600, 86400]) {
|
|
284
|
+
manifest.operations.add.once = once
|
|
285
|
+
await assert.doesNotReject(validate(manifest), `once: ${once}`)
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('should stay true rather than be coerced into a number', async () => {
|
|
290
|
+
manifest.operations.add.once = true
|
|
291
|
+
await validate(manifest)
|
|
292
|
+
|
|
293
|
+
assert.strictEqual(manifest.operations.add.once, true)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('should not be a window shorter than ten minutes', async () => {
|
|
297
|
+
manifest.operations.add.once = 599
|
|
298
|
+
await assert.rejects(validate(manifest), (error) => /must be >= 600/.test(error.message))
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('should not be a fraction of a second', async () => {
|
|
302
|
+
manifest.operations.add.once = 600.5
|
|
303
|
+
await assert.rejects(validate(manifest))
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should throw for an operation that takes none', async () => {
|
|
307
|
+
manifest.operations.get.once = 600
|
|
308
|
+
await assert.rejects(validate(manifest))
|
|
212
309
|
})
|
|
213
310
|
})
|
|
214
311
|
|
|
215
312
|
describe('concurrency', () => {
|
|
216
|
-
it('should be required for transitions', () => {
|
|
313
|
+
it('should be required for transitions', async () => {
|
|
217
314
|
delete manifest.operations.add.concurrency
|
|
218
|
-
|
|
315
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
316
|
+
/required property/.test(error.message)
|
|
317
|
+
)
|
|
219
318
|
})
|
|
220
319
|
|
|
221
|
-
it('should throw for observations, assignments', () => {
|
|
320
|
+
it('should throw for observations, assignments', async () => {
|
|
222
321
|
manifest.operations.get.concurrency = 'none'
|
|
223
|
-
|
|
322
|
+
await assert.rejects(validate(manifest))
|
|
224
323
|
delete manifest.operations.get.concurrency
|
|
225
324
|
|
|
226
325
|
manifest.operations.set.concurrency = 'none'
|
|
227
|
-
|
|
326
|
+
await assert.rejects(validate(manifest))
|
|
228
327
|
})
|
|
229
328
|
})
|
|
230
329
|
|
|
231
330
|
describe('input, output', () => {
|
|
232
|
-
it('should be schema', () => {
|
|
331
|
+
it('should be schema', async () => {
|
|
233
332
|
manifest.operations.get.input = { properties: { foo: 'bar' } }
|
|
234
|
-
|
|
333
|
+
await assert.rejects(validate(manifest))
|
|
235
334
|
|
|
236
335
|
delete manifest.operations.get.input
|
|
237
336
|
manifest.operations.get.output = { properties: { foo: 'bar' } }
|
|
238
|
-
|
|
337
|
+
await assert.rejects(validate(manifest))
|
|
239
338
|
})
|
|
240
339
|
})
|
|
241
340
|
})
|
|
242
341
|
|
|
243
342
|
describe('receivers', () => {
|
|
244
|
-
it('should throw if transition points to undefined operation', () => {
|
|
343
|
+
it('should throw if transition points to undefined operation', async () => {
|
|
245
344
|
manifest.receivers['foo.bar.happened'].operation = 'notExists'
|
|
246
345
|
|
|
247
|
-
|
|
346
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
347
|
+
/refers to undefined operation/.test(error.message)
|
|
348
|
+
)
|
|
248
349
|
})
|
|
249
350
|
|
|
250
|
-
it('should throw if transition points to observation', () => {
|
|
351
|
+
it('should throw if transition points to observation', async () => {
|
|
251
352
|
manifest.receivers['foo.bar.happened'].operation = 'get'
|
|
252
353
|
|
|
253
|
-
|
|
354
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
355
|
+
/of the allowed types/.test(error.message)
|
|
356
|
+
)
|
|
254
357
|
})
|
|
255
358
|
|
|
256
359
|
it('should throw if source has a name `context`', async () => {
|
|
257
360
|
manifest.receivers['foo.bar.happened'].source = 'context'
|
|
258
361
|
|
|
259
|
-
|
|
362
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
363
|
+
/must NOT be valid/.test(error.message)
|
|
364
|
+
)
|
|
365
|
+
})
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
describe('stateful', () => {
|
|
369
|
+
it('should be allowed on an operation', async () => {
|
|
370
|
+
const [operation] = Object.values(manifest.operations)
|
|
371
|
+
|
|
372
|
+
operation.stateful = true
|
|
373
|
+
|
|
374
|
+
await assert.doesNotReject(validate(manifest))
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('should be a boolean', async () => {
|
|
378
|
+
const [operation] = Object.values(manifest.operations)
|
|
379
|
+
|
|
380
|
+
operation.stateful = 'yes'
|
|
381
|
+
|
|
382
|
+
await assert.rejects(validate(manifest), (error) => /must be boolean/.test(error.message))
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
describe('stream', () => {
|
|
387
|
+
beforeEach(() => {
|
|
388
|
+
manifest.operations.read = {
|
|
389
|
+
type: 'computation',
|
|
390
|
+
scope: 'none',
|
|
391
|
+
query: false,
|
|
392
|
+
bridge: 'whatever',
|
|
393
|
+
bindings: ['@toa.io/bindings.amqp'],
|
|
394
|
+
stream: 'content'
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('should reject an operation whose bindings carry no stream', async () => {
|
|
399
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
400
|
+
/takes a stream, which none of its bindings carries/.test(error.message)
|
|
401
|
+
)
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('should accept an operation served where it is composed', async () => {
|
|
405
|
+
manifest.operations.read.bindings = []
|
|
406
|
+
|
|
407
|
+
await assert.doesNotReject(validate(manifest))
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
it('should accept an operation bound to a binding that carries a stream', async () => {
|
|
411
|
+
manifest.operations.read.bindings = ['@toa.io/bindings.http']
|
|
412
|
+
|
|
413
|
+
await assert.doesNotReject(validate(manifest))
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it('should reject a transition that takes a stream', async () => {
|
|
417
|
+
manifest.operations.add.stream = 'content'
|
|
418
|
+
|
|
419
|
+
await assert.rejects(validate(manifest), (error) =>
|
|
420
|
+
/must be equal to one of the allowed values/.test(error.message)
|
|
421
|
+
)
|
|
260
422
|
})
|
|
261
423
|
})
|
|
@@ -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
|
+
})
|