@toa.io/norm 0.2.1-dev.1
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/features/component.operations.feature +54 -0
- package/features/steps/.test/manifest.test.js +110 -0
- package/features/steps/.types/context.d.ts +9 -0
- package/features/steps/hooks.js +18 -0
- package/features/steps/manifest.js +66 -0
- package/features/steps/parameters.js +9 -0
- package/package.json +31 -0
- package/src/.component/.expand/bridge.js +9 -0
- package/src/.component/.expand/entity.js +14 -0
- package/src/.component/.expand/events.js +14 -0
- package/src/.component/.expand/extensions.js +10 -0
- package/src/.component/.expand/index.js +15 -0
- package/src/.component/.expand/operations.js +17 -0
- package/src/.component/.expand/receivers.js +16 -0
- package/src/.component/.normalize/events.js +13 -0
- package/src/.component/.normalize/extensions.js +35 -0
- package/src/.component/.normalize/index.js +9 -0
- package/src/.component/.normalize/operations.js +12 -0
- package/src/.component/collapse.js +44 -0
- package/src/.component/defaults.js +25 -0
- package/src/.component/dereference.js +72 -0
- package/src/.component/expand.js +14 -0
- package/src/.component/index.js +17 -0
- package/src/.component/merge.js +55 -0
- package/src/.component/normalize.js +13 -0
- package/src/.component/schema.yaml +222 -0
- package/src/.component/validate.js +40 -0
- package/src/.context/.dependencies/connectors.js +44 -0
- package/src/.context/.dependencies/describe.js +10 -0
- package/src/.context/.dependencies/extensions.js +24 -0
- package/src/.context/.dependencies/index.js +9 -0
- package/src/.context/.dependencies/resolve.js +45 -0
- package/src/.context/complete.js +33 -0
- package/src/.context/dependencies.js +16 -0
- package/src/.context/dereference.js +31 -0
- package/src/.context/expand.js +14 -0
- package/src/.context/index.js +15 -0
- package/src/.context/normalize.js +19 -0
- package/src/.context/schema.yaml +60 -0
- package/src/.context/validate.js +19 -0
- package/src/component.js +59 -0
- package/src/context.js +48 -0
- package/src/index.js +9 -0
- package/src/shortcuts.js +52 -0
- package/test/component/collapse.fixtures.js +134 -0
- package/test/component/collapse.test.js +81 -0
- package/test/component/dereference.fixtures.js +165 -0
- package/test/component/dereference.test.js +31 -0
- package/test/component/dummies/extension/index.js +9 -0
- package/test/component/dummies/extension/package.json +5 -0
- package/test/component/expand.fixtures.js +137 -0
- package/test/component/expand.test.js +17 -0
- package/test/component/normalize.fixtures.js +22 -0
- package/test/component/normalize.test.js +45 -0
- package/test/component/validate.fixtures.js +58 -0
- package/test/component/validate.test.js +250 -0
- package/test/context/complete.fixtures.js +59 -0
- package/test/context/complete.test.js +31 -0
- package/test/context/dereference.fixtures.js +60 -0
- package/test/context/dereference.test.js +24 -0
- package/test/context/expand.fixtures.js +5 -0
- package/test/context/expand.test.js +41 -0
- package/test/context/normalize.fixtures.js +32 -0
- package/test/context/normalize.test.js +34 -0
- package/test/context/validate.fixtures.js +38 -0
- package/test/context/validate.test.js +91 -0
- package/test/shortcuts.fixtures.js +17 -0
- package/test/shortcuts.test.js +92 -0
- package/types/component.d.ts +78 -0
- package/types/context.d.ts +75 -0
- package/types/index.d.ts +5 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const source = {
|
|
4
|
+
entity: {
|
|
5
|
+
schema: {
|
|
6
|
+
properties: {
|
|
7
|
+
foo: 'string'
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
storage: 'mongodb'
|
|
11
|
+
},
|
|
12
|
+
bridge: 'node',
|
|
13
|
+
operations: {
|
|
14
|
+
add: {
|
|
15
|
+
bridge: 'node',
|
|
16
|
+
bindings: ['amqp'],
|
|
17
|
+
input: {
|
|
18
|
+
properties: {
|
|
19
|
+
foo: 'integer',
|
|
20
|
+
bar: {
|
|
21
|
+
type: 'array',
|
|
22
|
+
items: {
|
|
23
|
+
properties: {
|
|
24
|
+
baz: 'string'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
output: {
|
|
31
|
+
properties: {
|
|
32
|
+
foo: 'integer',
|
|
33
|
+
bar: {
|
|
34
|
+
type: 'array',
|
|
35
|
+
items: {
|
|
36
|
+
properties: {
|
|
37
|
+
baz: 'string'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
events: {
|
|
46
|
+
happened: {
|
|
47
|
+
bridge: 'node',
|
|
48
|
+
binding: 'amqp'
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
receivers: {
|
|
52
|
+
one: 'transit',
|
|
53
|
+
two: {
|
|
54
|
+
binding: 'amqp',
|
|
55
|
+
bridge: 'node',
|
|
56
|
+
transition: 'transit'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const target = {
|
|
62
|
+
entity: {
|
|
63
|
+
schema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
foo: {
|
|
67
|
+
type: 'string'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
storage: '@toa.io/storages.mongodb'
|
|
72
|
+
},
|
|
73
|
+
bridge: '@toa.io/bridges.node',
|
|
74
|
+
operations: {
|
|
75
|
+
add: {
|
|
76
|
+
bridge: '@toa.io/bridges.node',
|
|
77
|
+
bindings: ['@toa.io/bindings.amqp'],
|
|
78
|
+
input: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
foo: {
|
|
82
|
+
type: 'integer'
|
|
83
|
+
},
|
|
84
|
+
bar: {
|
|
85
|
+
type: 'array',
|
|
86
|
+
items: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
baz: {
|
|
90
|
+
type: 'string'
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
output: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
foo: {
|
|
101
|
+
type: 'integer'
|
|
102
|
+
},
|
|
103
|
+
bar: {
|
|
104
|
+
type: 'array',
|
|
105
|
+
items: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
baz: {
|
|
109
|
+
type: 'string'
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
events: {
|
|
119
|
+
happened: {
|
|
120
|
+
bridge: '@toa.io/bridges.node',
|
|
121
|
+
binding: '@toa.io/bindings.amqp'
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
receivers: {
|
|
125
|
+
one: {
|
|
126
|
+
transition: 'transit'
|
|
127
|
+
},
|
|
128
|
+
two: {
|
|
129
|
+
bridge: '@toa.io/bridges.node',
|
|
130
|
+
binding: '@toa.io/bindings.amqp',
|
|
131
|
+
transition: 'transit'
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
exports.source = source
|
|
137
|
+
exports.target = target
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const clone = require('clone-deep')
|
|
4
|
+
|
|
5
|
+
const { expand } = require('../../src/.component')
|
|
6
|
+
const fixtures = require('./expand.fixtures')
|
|
7
|
+
|
|
8
|
+
let source
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
source = clone(fixtures.source)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('should expand', () => {
|
|
15
|
+
expand(source)
|
|
16
|
+
expect(source).toMatchObject(fixtures.target)
|
|
17
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { generate } = require('randomstring')
|
|
4
|
+
|
|
5
|
+
const operations = {
|
|
6
|
+
path: __dirname,
|
|
7
|
+
bindings: ['foo', 'bar'],
|
|
8
|
+
'bindings@local': ['foo'],
|
|
9
|
+
operations: {
|
|
10
|
+
add: {}
|
|
11
|
+
},
|
|
12
|
+
extensions: {
|
|
13
|
+
'@toa.io/extensions.exposition': {
|
|
14
|
+
['/' + generate()]: ['add']
|
|
15
|
+
},
|
|
16
|
+
'./dummies/extension': {
|
|
17
|
+
ok: true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
exports.operations = operations
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { dirname } = require('node:path')
|
|
4
|
+
const clone = require('clone-deep')
|
|
5
|
+
|
|
6
|
+
const { normalize } = require('../../src/.component')
|
|
7
|
+
const fixtures = require('./normalize.fixtures')
|
|
8
|
+
|
|
9
|
+
let manifest
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
manifest = clone(fixtures.operations)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe('environment', () => {
|
|
16
|
+
it('should convolve with environment argument', () => {
|
|
17
|
+
normalize(manifest, 'local')
|
|
18
|
+
expect(manifest.operations.add.bindings).toStrictEqual(['foo'])
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('operations', () => {
|
|
23
|
+
it('should set default bindings', () => {
|
|
24
|
+
normalize(manifest)
|
|
25
|
+
|
|
26
|
+
expect(manifest.operations.add.bindings).toStrictEqual(manifest.bindings)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('extensions', () => {
|
|
31
|
+
it('should resolve absolute references', () => {
|
|
32
|
+
const origins = manifest.extensions['@toa.io/extensions.origins']
|
|
33
|
+
const path = dirname(require.resolve('@toa.io/extensions.origins/package.json'))
|
|
34
|
+
|
|
35
|
+
normalize(manifest)
|
|
36
|
+
|
|
37
|
+
expect(manifest.extensions[path]).toStrictEqual(origins)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should throw if manifest is undefined', () => {
|
|
41
|
+
manifest.extensions['./dummies/extension'].ok = false
|
|
42
|
+
|
|
43
|
+
expect(() => normalize(manifest)).toThrow(/didn't return manifest/)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ok = {
|
|
4
|
+
namespace: 'foo',
|
|
5
|
+
name: 'bar',
|
|
6
|
+
entity: {
|
|
7
|
+
storage: 'whatever',
|
|
8
|
+
schema: {
|
|
9
|
+
properties: {
|
|
10
|
+
name: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
minLength: 1,
|
|
13
|
+
maxLength: 128
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
operations: {
|
|
19
|
+
get: {
|
|
20
|
+
type: 'observation',
|
|
21
|
+
scope: 'objects',
|
|
22
|
+
bridge: 'whatever',
|
|
23
|
+
bindings: ['@toa.io/bindings.http']
|
|
24
|
+
},
|
|
25
|
+
add: {
|
|
26
|
+
type: 'transition',
|
|
27
|
+
concurrency: 'none',
|
|
28
|
+
scope: 'object',
|
|
29
|
+
bridge: 'whatever',
|
|
30
|
+
bindings: ['@toa.io/bindings.http', '@toa.io/bindings.amqp']
|
|
31
|
+
},
|
|
32
|
+
set: {
|
|
33
|
+
type: 'assignment',
|
|
34
|
+
scope: 'changeset',
|
|
35
|
+
bridge: 'whatever',
|
|
36
|
+
bindings: ['@toa.io/bindings.http', '@toa.io/bindings.amqp']
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
events: {
|
|
40
|
+
created: {
|
|
41
|
+
bridge: 'whatever',
|
|
42
|
+
path: '/somewhere',
|
|
43
|
+
conditioned: true,
|
|
44
|
+
subjective: false,
|
|
45
|
+
binding: '@toa.io/bindings.amqp'
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
receivers: {
|
|
49
|
+
'foo.bar.happened': {
|
|
50
|
+
transition: 'add',
|
|
51
|
+
bridge: 'whatever',
|
|
52
|
+
binding: 'amqp',
|
|
53
|
+
path: '/somewhere'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
exports.ok = ok
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// noinspection JSUnresolvedVariable
|
|
2
|
+
|
|
3
|
+
'use strict'
|
|
4
|
+
|
|
5
|
+
const clone = require('clone-deep')
|
|
6
|
+
|
|
7
|
+
const { validate } = require('../../src/.component')
|
|
8
|
+
const fixtures = require('./validate.fixtures')
|
|
9
|
+
|
|
10
|
+
let manifest
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
manifest = clone(fixtures.ok)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should be ok', () => {
|
|
17
|
+
expect(() => validate(manifest)).not.toThrow()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should provide error', () => {
|
|
21
|
+
manifest.foo = 'bar'
|
|
22
|
+
|
|
23
|
+
expect(() => validate(manifest)).toThrow(/must NOT have additional property/)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should not have additional properties', () => {
|
|
27
|
+
manifest.foo = 'bar'
|
|
28
|
+
|
|
29
|
+
expect(() => validate(manifest)).toThrow(/must NOT have additional property/)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('namespace', () => {
|
|
33
|
+
it('should match token pattern', () => {
|
|
34
|
+
manifest.namespace = '1'
|
|
35
|
+
expect(() => validate(manifest)).toThrow(/must match pattern/)
|
|
36
|
+
|
|
37
|
+
manifest.namespace = 'foo_'
|
|
38
|
+
expect(() => validate(manifest)).toThrow(/must match pattern/)
|
|
39
|
+
|
|
40
|
+
manifest.namespace = 'foo-'
|
|
41
|
+
expect(() => validate(manifest)).toThrow(/must match pattern/)
|
|
42
|
+
|
|
43
|
+
manifest.namespace = 'foo-BAR'
|
|
44
|
+
expect(() => validate(manifest)).not.toThrow()
|
|
45
|
+
|
|
46
|
+
manifest.namespace = 'foo_bar'
|
|
47
|
+
expect(() => validate(manifest)).not.toThrow()
|
|
48
|
+
|
|
49
|
+
manifest.namespace = 'FooBar12'
|
|
50
|
+
expect(() => validate(manifest)).not.toThrow()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should forbid \'system\' namespace', () => {
|
|
54
|
+
manifest.namespace = 'system'
|
|
55
|
+
expect(() => validate(manifest)).toThrow(/must NOT be valid/)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should forbid \'default\' namespace', () => {
|
|
59
|
+
manifest.namespace = 'default'
|
|
60
|
+
expect(() => validate(manifest)).toThrow(/must NOT be valid/)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('name', () => {
|
|
65
|
+
it('should be optional', () => {
|
|
66
|
+
delete manifest.name
|
|
67
|
+
expect(() => validate(manifest)).not.toThrow()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('entity', () => {
|
|
72
|
+
it('should be optional', () => {
|
|
73
|
+
delete manifest.entity
|
|
74
|
+
expect(() => validate(manifest)).not.toThrow()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should be object', () => {
|
|
78
|
+
manifest.entity = 'foo'
|
|
79
|
+
expect(() => validate(manifest)).toThrow(/must be object/)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should not have additional properties', () => {
|
|
83
|
+
manifest.entity.foo = 'bar'
|
|
84
|
+
expect(() => validate(manifest)).toThrow(/must NOT have additional property/)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('schema', () => {
|
|
88
|
+
it('should be required', () => {
|
|
89
|
+
delete manifest.entity.schema
|
|
90
|
+
expect(() => validate(manifest)).toThrow(/must have required property/)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should be JSON schema object', () => {
|
|
94
|
+
manifest.entity.schema = { properties: { foo: 'bar' } }
|
|
95
|
+
expect(() => validate(manifest)).toThrow()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should be JSON schema object of type object', () => {
|
|
99
|
+
manifest.entity.schema = { type: 'integer' }
|
|
100
|
+
expect(() => validate(manifest)).toThrow(/must be equal to constant 'object'/)
|
|
101
|
+
|
|
102
|
+
manifest.entity.schema = {}
|
|
103
|
+
validate(manifest)
|
|
104
|
+
expect(manifest.entity.schema.type).toBe('object')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should forbid additional properties', () => {
|
|
108
|
+
manifest.entity.schema = { additionalProperties: true }
|
|
109
|
+
expect(() => validate(manifest)).toThrow(/must be equal to constant 'false'/)
|
|
110
|
+
|
|
111
|
+
manifest.entity.schema = {}
|
|
112
|
+
validate(manifest)
|
|
113
|
+
expect(manifest.entity.schema.additionalProperties).toBe(false)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('should have property names matching token pattern', () => {
|
|
117
|
+
manifest.entity.schema.properties._foo = { type: 'string' }
|
|
118
|
+
expect(() => validate(manifest)).toThrow(/pattern/)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should allow default id', () => {
|
|
122
|
+
manifest.entity.schema.properties.id = { type: 'string', pattern: '^[a-fA-F0-9]+$' }
|
|
123
|
+
expect(() => validate(manifest)).not.toThrow()
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('initialized', () => {
|
|
128
|
+
it('should provide default', () => {
|
|
129
|
+
expect(() => validate(manifest)).not.toThrow()
|
|
130
|
+
expect(manifest.entity.initialized).toBe(false)
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
describe('bindings', () => {
|
|
136
|
+
it('should be array of unique strings', () => {
|
|
137
|
+
manifest.bindings = 'oops'
|
|
138
|
+
expect(() => validate(manifest)).toThrow(/must be array/)
|
|
139
|
+
|
|
140
|
+
manifest.bindings = ['oops', 'oops']
|
|
141
|
+
expect(() => validate(manifest)).toThrow(/duplicate items/)
|
|
142
|
+
|
|
143
|
+
manifest.bindings = ['oops', {}]
|
|
144
|
+
expect(() => validate(manifest)).toThrow(/must be string/)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should forbid explicit loop', () => {
|
|
148
|
+
manifest.bindings = ['@toa.io/bindings.loop']
|
|
149
|
+
expect(() => validate(manifest)).toThrow(/must NOT be valid/)
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('operations', () => {
|
|
154
|
+
it('should be object', () => {
|
|
155
|
+
manifest.operations.get = 'bar'
|
|
156
|
+
expect(() => validate(manifest)).toThrow(/must be object/)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('should not have additional properties', () => {
|
|
160
|
+
manifest.operations.get.foo = 'bar'
|
|
161
|
+
expect(() => validate(manifest)).toThrow(/additional property/)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('should have type (transition or observation)', () => {
|
|
165
|
+
delete manifest.operations.get.type
|
|
166
|
+
expect(() => validate(manifest)).toThrow()
|
|
167
|
+
|
|
168
|
+
manifest.operations.get.type = 'foo'
|
|
169
|
+
expect(() => validate(manifest)).toThrow(/one of the allowed values/)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should forbid explicit loop', () => {
|
|
173
|
+
manifest.operations.get.bindings = ['@toa.io/bindings.loop']
|
|
174
|
+
expect(() => validate(manifest)).toThrow(/must NOT be valid/)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('should forbid query: false for observations', () => {
|
|
178
|
+
manifest.operations.get.query = false
|
|
179
|
+
expect(() => validate(manifest)).toThrow(/must NOT be valid/)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
describe('scope', () => {
|
|
183
|
+
it('should have scope', () => {
|
|
184
|
+
delete manifest.operations.get.scope
|
|
185
|
+
expect(() => validate(manifest)).toThrow(/required property/)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('should allow only entity or set for observations', () => {
|
|
189
|
+
manifest.operations.get.scope = 'changeset'
|
|
190
|
+
expect(() => validate(manifest)).toThrow(/allowed values/)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should allow only entity for transitions', () => {
|
|
194
|
+
manifest.operations.add.scope = 'changeset'
|
|
195
|
+
expect(() => validate(manifest)).toThrow(/allowed values/)
|
|
196
|
+
|
|
197
|
+
manifest.operations.add.scope = 'set'
|
|
198
|
+
expect(() => validate(manifest)).toThrow(/allowed values/)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('should allow only changeset for assignments', () => {
|
|
202
|
+
manifest.operations.set.scope = 'changeset'
|
|
203
|
+
expect(() => validate(manifest)).not.toThrow()
|
|
204
|
+
|
|
205
|
+
manifest.operations.set.scope = 'set'
|
|
206
|
+
expect(() => validate(manifest)).toThrow(/allowed values/)
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
describe('concurrency', () => {
|
|
211
|
+
it('should be required for transitions', () => {
|
|
212
|
+
delete manifest.operations.add.concurrency
|
|
213
|
+
expect(() => validate(manifest)).toThrow(/required property/)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('should throw for observations, assignments', () => {
|
|
217
|
+
manifest.operations.get.concurrency = 'none'
|
|
218
|
+
expect(() => validate(manifest)).toThrow()
|
|
219
|
+
delete manifest.operations.get.concurrency
|
|
220
|
+
|
|
221
|
+
manifest.operations.set.concurrency = 'none'
|
|
222
|
+
expect(() => validate(manifest)).toThrow()
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('input, output', () => {
|
|
227
|
+
it('should be schema', () => {
|
|
228
|
+
manifest.operations.get.input = { properties: { foo: 'bar' } }
|
|
229
|
+
expect(() => validate(manifest)).toThrow()
|
|
230
|
+
|
|
231
|
+
delete manifest.operations.get.input
|
|
232
|
+
manifest.operations.get.output = { properties: { foo: 'bar' } }
|
|
233
|
+
expect(() => validate(manifest)).toThrow()
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
describe('receivers', () => {
|
|
239
|
+
it('should throw if transition points to undefined transition', () => {
|
|
240
|
+
manifest.receivers['foo.bar.happened'].transition = 'not-exists'
|
|
241
|
+
|
|
242
|
+
expect(() => validate(manifest)).toThrow(/refers to undefined transition/)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('should throw if transition points to non transition', () => {
|
|
246
|
+
manifest.receivers['foo.bar.happened'].transition = 'get'
|
|
247
|
+
|
|
248
|
+
expect(() => validate(manifest)).toThrow(/refers to non-transition/)
|
|
249
|
+
})
|
|
250
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const component = (id) => {
|
|
4
|
+
const [namespace, name] = id.split('.')
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
namespace,
|
|
8
|
+
name,
|
|
9
|
+
version: '0.0.0',
|
|
10
|
+
locator: {
|
|
11
|
+
namespace,
|
|
12
|
+
name,
|
|
13
|
+
id,
|
|
14
|
+
label: `${namespace}-${name}`
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const context = {
|
|
20
|
+
runtime: '0.0.0',
|
|
21
|
+
name: 'test',
|
|
22
|
+
description: 'context fixture',
|
|
23
|
+
version: '0.0.0',
|
|
24
|
+
packages: 'namespaces/**/*',
|
|
25
|
+
registry: 'localhost:5000',
|
|
26
|
+
components: [
|
|
27
|
+
component('a.b'),
|
|
28
|
+
component('b.a'),
|
|
29
|
+
component('d.a'),
|
|
30
|
+
component('d.b'),
|
|
31
|
+
component('d.c')
|
|
32
|
+
],
|
|
33
|
+
compositions: [
|
|
34
|
+
{
|
|
35
|
+
name: 'foo',
|
|
36
|
+
components: [component('a.b'), component('b.a')]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'bar',
|
|
40
|
+
components: [component('d.c'), component('a.b')]
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @type {Array<toa.norm.context.Composition>} */
|
|
46
|
+
const compositions = [
|
|
47
|
+
...context.compositions,
|
|
48
|
+
{
|
|
49
|
+
name: 'd-a',
|
|
50
|
+
components: [component('d.a')]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'd-b',
|
|
54
|
+
components: [component('d.b')]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
exports.context = context
|
|
59
|
+
exports.compositions = compositions
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const clone = require('clone-deep')
|
|
4
|
+
|
|
5
|
+
const { complete } = require('../../src/.context')
|
|
6
|
+
const fixtures = require('./complete.fixtures')
|
|
7
|
+
|
|
8
|
+
/** @type {toa.norm.Context} */
|
|
9
|
+
let context
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
context = clone(fixtures.context)
|
|
13
|
+
complete(context)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should complete compositions', () => {
|
|
17
|
+
expect(context.compositions.length).toEqual(fixtures.compositions.length)
|
|
18
|
+
expect(context.compositions).toEqual(expect.arrayContaining(fixtures.compositions))
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should create if compositions are not set', () => {
|
|
22
|
+
context.compositions = undefined
|
|
23
|
+
|
|
24
|
+
const compositions = context.components.map((component) => ({
|
|
25
|
+
name: component.locator.label,
|
|
26
|
+
components: [component]
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
expect(() => complete(context)).not.toThrow()
|
|
30
|
+
expect(context.compositions).toStrictEqual(compositions)
|
|
31
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const clone = require('clone-deep')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param id {string}
|
|
7
|
+
* @return {toa.norm.component.Declaration}
|
|
8
|
+
*/
|
|
9
|
+
const component = (id) => {
|
|
10
|
+
const [namespace, name] = id.split('.')
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
namespace,
|
|
14
|
+
name,
|
|
15
|
+
version: '0.0.0',
|
|
16
|
+
locator: {
|
|
17
|
+
namespace,
|
|
18
|
+
name,
|
|
19
|
+
id,
|
|
20
|
+
label: `${namespace}-${name}`
|
|
21
|
+
},
|
|
22
|
+
entity: null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const context = {
|
|
27
|
+
name: 'test',
|
|
28
|
+
description: 'context fixture',
|
|
29
|
+
version: '0.0.0',
|
|
30
|
+
runtime: '0.0.0',
|
|
31
|
+
packages: 'namespaces/**/*',
|
|
32
|
+
registry: 'localhost:5000',
|
|
33
|
+
components: [component('a.b'), component('b.a'), component('d.c')],
|
|
34
|
+
compositions: [
|
|
35
|
+
{
|
|
36
|
+
name: 'foo',
|
|
37
|
+
components: ['a.b', 'b.a']
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'bar',
|
|
41
|
+
components: ['d.c', 'a.b']
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const expected = clone(context)
|
|
47
|
+
|
|
48
|
+
expected.compositions = [
|
|
49
|
+
{
|
|
50
|
+
name: 'foo',
|
|
51
|
+
components: [component('a.b'), component('b.a')]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'bar',
|
|
55
|
+
components: [component('d.c'), component('a.b')]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
exports.context = context
|
|
60
|
+
exports.expected = expected
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const clone = require('clone-deep')
|
|
4
|
+
|
|
5
|
+
const { dereference } = require('../../src/.context')
|
|
6
|
+
const fixtures = require('./dereference.fixtures')
|
|
7
|
+
|
|
8
|
+
/** @type {toa.norm.Context} */
|
|
9
|
+
let context
|
|
10
|
+
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
context = clone(fixtures.context)
|
|
13
|
+
dereference(context)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should dereference', () => {
|
|
17
|
+
expect(context).toEqual(expect.objectContaining(fixtures.expected))
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should not throw on empty compositions', () => {
|
|
21
|
+
context.compositions = undefined
|
|
22
|
+
|
|
23
|
+
expect(() => dereference(context)).not.toThrow()
|
|
24
|
+
})
|