@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.
Files changed (100) hide show
  1. package/CHANGELOG.md +298 -0
  2. package/package.json +9 -7
  3. package/src/.component/.expand/bridge.js +2 -6
  4. package/src/.component/.expand/entity.js +2 -9
  5. package/src/.component/.expand/events.js +2 -6
  6. package/src/.component/.expand/extensions.js +7 -9
  7. package/src/.component/.expand/index.js +7 -19
  8. package/src/.component/.expand/operations.js +2 -9
  9. package/src/.component/.expand/receivers.js +4 -7
  10. package/src/.component/.expand/version.js +69 -28
  11. package/src/.component/.normalize/entity.js +7 -0
  12. package/src/.component/.normalize/events.js +16 -4
  13. package/src/.component/.normalize/index.js +4 -9
  14. package/src/.component/.normalize/operations.js +13 -6
  15. package/src/.component/.normalize/receivers.js +4 -5
  16. package/src/.component/collapse.js +60 -9
  17. package/src/.component/defaults.js +5 -11
  18. package/src/.component/dependencies.js +14 -10
  19. package/src/.component/dereference.js +5 -49
  20. package/src/.component/expand.js +3 -9
  21. package/src/.component/extensions.js +9 -10
  22. package/src/.component/index.js +10 -21
  23. package/src/.component/merge.js +25 -15
  24. package/src/.component/migrations.js +57 -0
  25. package/src/.component/normalize.js +10 -6
  26. package/src/.component/schema.yaml +162 -73
  27. package/src/.component/validate.js +63 -17
  28. package/src/.context/.dependencies/connectors.js +5 -9
  29. package/src/.context/.dependencies/describe.js +4 -5
  30. package/src/.context/.dependencies/extensions.js +93 -20
  31. package/src/.context/.dependencies/index.js +3 -9
  32. package/src/.context/.dependencies/resolve.js +44 -7
  33. package/src/.context/complete.js +1 -5
  34. package/src/.context/converge.js +31 -0
  35. package/src/.context/dependencies.js +43 -6
  36. package/src/.context/dereference.js +11 -6
  37. package/src/.context/evict.js +38 -0
  38. package/src/.context/expand.js +11 -6
  39. package/src/.context/index.js +8 -15
  40. package/src/.context/normalize.js +9 -7
  41. package/src/.context/schema.yaml +183 -11
  42. package/src/.context/validate.js +11 -14
  43. package/src/component.js +33 -14
  44. package/src/context.js +44 -15
  45. package/src/definition.js +63 -0
  46. package/src/entity.js +15 -0
  47. package/src/index.js +7 -9
  48. package/src/manifest.js +154 -0
  49. package/src/map.js +24 -0
  50. package/src/shortcuts.js +11 -15
  51. package/test/component/collapse.fixtures.js +30 -39
  52. package/test/component/collapse.test.js +90 -10
  53. package/test/component/dependencies.test.js +16 -14
  54. package/test/component/dereference.fixtures.js +10 -98
  55. package/test/component/dereference.test.js +10 -14
  56. package/test/component/dummies/extension/index.js +1 -5
  57. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yaml +2 -0
  58. package/test/component/dummies/migrations/conflicting/migrations/0001-one.yml +2 -0
  59. package/test/component/dummies/migrations/malformed/migrations/0001-one.yaml +1 -0
  60. package/test/component/dummies/migrations/ordered/migrations/0001-first.yml +4 -0
  61. package/test/component/dummies/migrations/ordered/migrations/0002-second.yaml +4 -0
  62. package/test/component/dummies/migrations/ordered/migrations/0010-third.json +1 -0
  63. package/test/component/dummies/migrations/stateless/migrations/0001-one.yaml +2 -0
  64. package/test/component/expand.fixtures.js +13 -13
  65. package/test/component/expand.test.js +17 -13
  66. package/test/component/migrations.test.js +62 -0
  67. package/test/component/normalize.fixtures.js +3 -7
  68. package/test/component/normalize.test.js +94 -28
  69. package/test/component/validate.fixtures.js +17 -15
  70. package/test/component/validate.test.js +271 -109
  71. package/test/context/complete.fixtures.js +2 -8
  72. package/test/context/complete.test.js +14 -8
  73. package/test/context/converge.test.js +57 -0
  74. package/test/context/dereference.fixtures.js +3 -9
  75. package/test/context/dereference.test.js +18 -7
  76. package/test/context/evict.fixtures.js +39 -0
  77. package/test/context/evict.test.js +73 -0
  78. package/test/context/expand.fixtures.js +1 -5
  79. package/test/context/expand.test.js +36 -8
  80. package/test/context/normalize.fixtures.js +2 -7
  81. package/test/context/normalize.test.js +33 -9
  82. package/test/context/validate.fixtures.js +1 -6
  83. package/test/context/validate.test.js +173 -35
  84. package/test/definition.test.js +35 -0
  85. package/test/dummies/bare/index.js +1 -0
  86. package/test/dummies/defined/definition.js +1 -0
  87. package/test/dummies/defined/index.js +1 -0
  88. package/test/dummies/defined/package.json +1 -0
  89. package/test/manifest.test.js +126 -0
  90. package/test/shortcuts.fixtures.js +4 -9
  91. package/test/shortcuts.test.js +17 -15
  92. package/test/system.test.js +31 -0
  93. package/types/component.d.ts +29 -4
  94. package/types/context/declaration.d.ts +34 -3
  95. package/types/context.d.ts +27 -13
  96. package/types/entity.d.ts +7 -0
  97. package/types/index.d.ts +18 -3
  98. package/src/.component/.expand/properties.js +0 -14
  99. package/src/.context/.dependencies/load.js +0 -36
  100. package/test/context/dependencies.load.test.js +0 -24
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- const clone = require('clone-deep')
1
+ import clone from 'clone-deep'
4
2
 
5
3
  /**
6
4
  * @param id {string}
@@ -22,12 +20,11 @@ const component = (id) => {
22
20
  }
23
21
  }
24
22
 
25
- const context = {
23
+ export const context = {
26
24
  name: 'test',
27
25
  description: 'context fixture',
28
26
  version: '0.0.0',
29
27
  runtime: '0.0.0',
30
- packages: 'namespaces/**/*',
31
28
  registry: 'localhost:5000',
32
29
  components: [component('a.b'), component('b.a'), component('d.c')],
33
30
  compositions: [
@@ -42,7 +39,7 @@ const context = {
42
39
  ]
43
40
  }
44
41
 
45
- const expected = clone(context)
42
+ export const expected = clone(context)
46
43
 
47
44
  expected.compositions = [
48
45
  {
@@ -54,6 +51,3 @@ expected.compositions = [
54
51
  components: [component('d.c'), component('a.b')]
55
52
  }
56
53
  ]
57
-
58
- exports.context = context
59
- exports.expected = expected
@@ -1,24 +1,35 @@
1
- 'use strict'
1
+ import { it, before } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const clone = require('clone-deep')
4
+ import clone from 'clone-deep'
4
5
 
5
- const { dereference } = require('../../src/.context')
6
- const fixtures = require('./dereference.fixtures')
6
+ import { dereference } from '../../src/.context/index.js'
7
+ import * as fixtures from './dereference.fixtures.js'
7
8
 
8
9
  /** @type {toa.norm.Context} */
9
10
  let context
10
11
 
11
- beforeAll(() => {
12
+ before(() => {
12
13
  context = clone(fixtures.context)
13
14
  dereference(context)
14
15
  })
15
16
 
16
17
  it('should dereference', () => {
17
- expect(context).toEqual(expect.objectContaining(fixtures.expected))
18
+ assert.partialDeepStrictEqual(context, fixtures.expected)
18
19
  })
19
20
 
20
21
  it('should not throw on empty compositions', () => {
21
22
  context.compositions = undefined
22
23
 
23
- expect(() => dereference(context)).not.toThrow()
24
+ assert.doesNotThrow(() => dereference(context))
25
+ })
26
+
27
+ it('should throw on an unknown component', () => {
28
+ const broken = clone(fixtures.context)
29
+
30
+ broken.compositions[1].components.push('nope.here')
31
+
32
+ assert.throws(() => dereference(broken), {
33
+ message: "Composition 'bar' lists an unknown component 'nope.here'."
34
+ })
24
35
  })
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @param id {string}
3
+ * @return {toa.norm.component.Declaration}
4
+ */
5
+ const component = (id) => {
6
+ const [namespace, name] = id.split('.')
7
+
8
+ return {
9
+ namespace,
10
+ name,
11
+ version: '0.0.0',
12
+ locator: {
13
+ namespace,
14
+ name,
15
+ id,
16
+ label: `${namespace}-${name}`
17
+ }
18
+ }
19
+ }
20
+
21
+ export const context = {
22
+ name: 'test',
23
+ description: 'context fixture',
24
+ version: '0.0.0',
25
+ runtime: '0.0.0',
26
+ registry: 'localhost:5000',
27
+ components: [component('a.b'), component('b.a'), component('d.c')],
28
+ compositions: [
29
+ {
30
+ name: 'foo',
31
+ components: ['a.b', 'b.a'],
32
+ services: ['@toa.io/extensions.exposition', '@toa.io/extensions.cadence']
33
+ },
34
+ {
35
+ name: 'bar',
36
+ components: ['d.c']
37
+ }
38
+ ]
39
+ }
@@ -0,0 +1,73 @@
1
+ import { it, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import clone from 'clone-deep'
5
+
6
+ import { evict } from '../../src/.context/index.js'
7
+ import * as fixtures from './evict.fixtures.js'
8
+
9
+ /** @type {toa.norm.Context} */
10
+ let context
11
+
12
+ beforeEach(() => {
13
+ context = clone(fixtures.context)
14
+ })
15
+
16
+ it('should keep a context that evicts nothing', () => {
17
+ evict(context)
18
+
19
+ assert.deepStrictEqual(context, fixtures.context)
20
+ })
21
+
22
+ it('should mark an evicted component and keep it', () => {
23
+ context.evicted = { components: ['a.b'] }
24
+
25
+ evict(context)
26
+
27
+ assert.deepStrictEqual(
28
+ context.components.map((component) => [
29
+ component.locator.id,
30
+ component.evicted === true
31
+ ]),
32
+ [
33
+ ['a.b', true],
34
+ ['b.a', false],
35
+ ['d.c', false]
36
+ ]
37
+ )
38
+ })
39
+
40
+ it('should keep an evicted component in a composition that lists it', () => {
41
+ context.evicted = { components: ['a.b', 'd.c'] }
42
+
43
+ evict(context)
44
+
45
+ assert.deepStrictEqual(context.compositions[0].components, ['a.b', 'b.a'])
46
+ assert.deepStrictEqual(context.compositions[1].components, ['d.c'])
47
+ })
48
+
49
+ it('should refuse an unknown component', () => {
50
+ context.evicted = { components: ['d.a'] }
51
+
52
+ assert.throws(() => evict(context), {
53
+ message: "'evicted' names an unknown component 'd.a'."
54
+ })
55
+ })
56
+
57
+ it('should drop an evicted service from a composition that runs it', () => {
58
+ context.evicted = { services: ['@toa.io/extensions.exposition'] }
59
+
60
+ evict(context)
61
+
62
+ assert.deepStrictEqual(context.compositions[0].services, ['@toa.io/extensions.cadence'])
63
+ })
64
+
65
+ it('should leave no services where every one is evicted', () => {
66
+ context.evicted = {
67
+ services: ['@toa.io/extensions.exposition', '@toa.io/extensions.cadence']
68
+ }
69
+
70
+ evict(context)
71
+
72
+ assert.strictEqual('services' in context.compositions[0], false)
73
+ })
@@ -1,5 +1 @@
1
- 'use strict'
2
-
3
- const { context } = require('./validate.fixtures')
4
-
5
- exports.context = context
1
+ export { context } from './validate.fixtures.js'
@@ -1,10 +1,11 @@
1
- 'use strict'
1
+ import { describe, it, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const clone = require('clone-deep')
4
- const { generate } = require('randomstring')
4
+ import clone from 'clone-deep'
5
+ import { generate } from 'randomstring'
5
6
 
6
- const { expand } = require('../../src/.context')
7
- const fixtures = require('./expand.fixtures')
7
+ import { expand } from '../../src/.context/index.js'
8
+ import * as fixtures from './expand.fixtures.js'
8
9
 
9
10
  /** @type {toa.norm.context.Declaration | object} */
10
11
  let context
@@ -17,7 +18,7 @@ describe('annotations', () => {
17
18
  it('should not throw without annotations', () => {
18
19
  delete context.annotations
19
20
 
20
- expect(() => expand(context)).not.toThrow()
21
+ assert.doesNotThrow(() => expand(context))
21
22
  })
22
23
 
23
24
  it('should expand known annotations', () => {
@@ -28,7 +29,7 @@ describe('annotations', () => {
28
29
 
29
30
  expand(context)
30
31
 
31
- expect(context.annotations).toStrictEqual(fixtures.context.annotations)
32
+ assert.deepStrictEqual(context.annotations, fixtures.context.annotations)
32
33
  })
33
34
 
34
35
  it('should recognize annotations', () => {
@@ -36,6 +37,33 @@ describe('annotations', () => {
36
37
 
37
38
  expand(context)
38
39
 
39
- expect(context.annotations['@toa.io/storages.mongodb']).toBeDefined()
40
+ assert.notStrictEqual(context.annotations['@toa.io/storages.mongodb'], undefined)
41
+ })
42
+ })
43
+
44
+ describe('composition services', () => {
45
+ it('should resolve shortcuts', () => {
46
+ context.compositions[0].services = ['exposition', 'configuration']
47
+
48
+ expand(context)
49
+
50
+ assert.deepStrictEqual(context.compositions[0].services, [
51
+ '@toa.io/extensions.exposition',
52
+ '@toa.io/extensions.configuration'
53
+ ])
54
+ })
55
+
56
+ it('should leave a package reference alone', () => {
57
+ context.compositions[0].services = ['@acme/extension']
58
+
59
+ expand(context)
60
+
61
+ assert.deepStrictEqual(context.compositions[0].services, ['@acme/extension'])
62
+ })
63
+
64
+ it('should not throw without compositions', () => {
65
+ delete context.compositions
66
+
67
+ assert.doesNotThrow(() => expand(context))
40
68
  })
41
69
  })
@@ -1,15 +1,12 @@
1
- 'use strict'
1
+ import { generate } from 'randomstring'
2
2
 
3
- const { generate } = require('randomstring')
4
-
5
- const context = {
3
+ export const context = {
6
4
  runtime: {
7
5
  version: '0.0.0'
8
6
  },
9
7
  name: 'test',
10
8
  description: 'context fixture',
11
9
  version: '0.0.0',
12
- packages: 'namespaces/**/*',
13
10
  registry: 'localhost:5000',
14
11
  compositions: [
15
12
  {
@@ -28,5 +25,3 @@ const context = {
28
25
  }
29
26
  }
30
27
  }
31
-
32
- exports.context = context
@@ -1,10 +1,12 @@
1
- 'use strict'
1
+ import * as definitions from '@toa.io/definitions'
2
+ import { it, beforeEach } from 'node:test'
3
+ import assert from 'node:assert/strict'
2
4
 
3
- const clone = require('clone-deep')
4
- const { generate } = require('randomstring')
5
+ import clone from 'clone-deep'
6
+ import { generate } from 'randomstring'
5
7
 
6
- const fixtures = require('./normalize.fixtures')
7
- const { normalize } = require('../../src/.context')
8
+ import * as fixtures from './normalize.fixtures.js'
9
+ import { normalize } from '../../src/.context/index.js'
8
10
 
9
11
  let context
10
12
 
@@ -17,8 +19,8 @@ it('should resolve local version', () => {
17
19
 
18
20
  normalize(context)
19
21
 
20
- expect(context.runtime).not.toEqual('.')
21
- expect(context.runtime.version).toEqual(require('@toa.io/runtime').version)
22
+ assert.notDeepStrictEqual(context.runtime, '.')
23
+ assert.deepStrictEqual(context.runtime.version, definitions.version)
22
24
  })
23
25
 
24
26
  it('should expand registry', () => {
@@ -28,7 +30,29 @@ it('should expand registry', () => {
28
30
 
29
31
  normalize(context)
30
32
 
31
- expect(context.registry).toEqual({
32
- base
33
+ assert.deepStrictEqual(context.registry, {
34
+ base,
35
+ platforms: ['linux/amd64', 'linux/arm/v7', 'linux/arm64']
33
36
  })
34
37
  })
38
+
39
+ it('should set default platforms', () => {
40
+ context.registry = { base: generate() }
41
+
42
+ normalize(context)
43
+
44
+ assert.deepStrictEqual(context.registry.platforms, [
45
+ 'linux/amd64',
46
+ 'linux/arm/v7',
47
+ 'linux/arm64'
48
+ ])
49
+ })
50
+
51
+ it('should keep platforms declared as none', () => {
52
+ // `platforms: ~` in the context: the runner's own platform, not the default list
53
+ context.registry = { base: generate(), platforms: null }
54
+
55
+ normalize(context)
56
+
57
+ assert.strictEqual(context.registry.platforms, null)
58
+ })
@@ -1,13 +1,10 @@
1
- 'use strict'
2
-
3
- const context = {
1
+ export const context = {
4
2
  runtime: {
5
3
  version: '0.0.0'
6
4
  },
7
5
  name: 'test',
8
6
  description: 'context fixture',
9
7
  version: '0.0.0',
10
- packages: 'namespaces/**/*',
11
8
  build: {
12
9
  command: 'echo test'
13
10
  },
@@ -37,5 +34,3 @@ const context = {
37
34
  }
38
35
  }
39
36
  }
40
-
41
- exports.context = context
@@ -1,14 +1,15 @@
1
- 'use strict'
1
+ import { describe, it, before, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
2
3
 
3
- const clone = require('clone-deep')
4
+ import clone from 'clone-deep'
4
5
 
5
- const { validate } = require('../../src/.context')
6
- const fixtures = require('./validate.fixtures')
6
+ import { validate } from '../../src/.context/index.js'
7
+ import * as fixtures from './validate.fixtures.js'
7
8
 
8
9
  let context
9
10
 
10
- beforeAll(() => {
11
- expect(() => validate(fixtures.context)).not.toThrow()
11
+ before(() => {
12
+ assert.doesNotThrow(() => validate(fixtures.context))
12
13
  })
13
14
 
14
15
  beforeEach(() => {
@@ -18,23 +19,32 @@ beforeEach(() => {
18
19
  describe('runtime', () => {
19
20
  it('should require', () => {
20
21
  delete context.runtime
21
- expect(() => validate(context)).toThrow(/required/)
22
+ assert.throws(
23
+ () => validate(context),
24
+ (error) => /required/.test(error.message)
25
+ )
22
26
  })
23
27
 
24
28
  it('should require registry to match uri format', () => {
25
29
  context.runtime.registry = 'not-a-uri'
26
- expect(() => validate(context)).toThrow(/must match format/)
30
+ assert.throws(
31
+ () => validate(context),
32
+ (error) => /must match format/.test(error.message)
33
+ )
27
34
 
28
35
  context.runtime.registry = 'http://localhost'
29
- expect(() => validate(context)).not.toThrow()
36
+ assert.doesNotThrow(() => validate(context))
30
37
  })
31
38
 
32
39
  it('should require proxy to match uri format', () => {
33
40
  context.runtime.proxy = 'not-a-uri'
34
- expect(() => validate(context)).toThrow(/must match format/)
41
+ assert.throws(
42
+ () => validate(context),
43
+ (error) => /must match format/.test(error.message)
44
+ )
35
45
 
36
46
  context.runtime.proxy = 'http://localhost'
37
- expect(() => validate(context)).not.toThrow()
47
+ assert.doesNotThrow(() => validate(context))
38
48
  })
39
49
  })
40
50
 
@@ -42,43 +52,171 @@ describe('registry', () => {
42
52
  it('should require', () => {
43
53
  delete context.registry
44
54
 
45
- expect(() => validate(context)).toThrow(/required property 'registry'/)
55
+ assert.throws(
56
+ () => validate(context),
57
+ (error) => /required property 'registry'/.test(error.message)
58
+ )
46
59
  })
60
+ })
47
61
 
48
- it('should require base', () => {
49
- delete context.registry.base
62
+ it('should require name as label', () => {
63
+ delete context.name
64
+ assert.throws(
65
+ () => validate(context),
66
+ (error) => /required/.test(error.message)
67
+ )
50
68
 
51
- expect(() => validate(context)).toThrow(/required property 'base'/)
52
- })
69
+ context.name = 'foo bar'
70
+ assert.throws(
71
+ () => validate(context),
72
+ (error) => /pattern/.test(error.message)
73
+ )
53
74
 
54
- it('should set default platforms', () => {
55
- delete context.registry.platforms
75
+ context.name = 'foo-bar'
76
+ assert.doesNotThrow(() => validate(context))
77
+ })
56
78
 
57
- validate(context)
79
+ it('should require registry url', () => {
80
+ delete context.registry
81
+ assert.throws(
82
+ () => validate(context),
83
+ (error) => /required/.test(error.message)
84
+ )
85
+ })
58
86
 
59
- expect(context.registry.platforms).toBeInstanceOf(Array)
60
- expect(context.registry.platforms).toStrictEqual(['linux/amd64', 'linux/arm/v7', 'linux/arm64'])
87
+ describe('inbox', () => {
88
+ it('should allow a retention of ten minutes or more', () => {
89
+ context.inbox = { retention: 600 }
90
+ assert.doesNotThrow(() => validate(context))
91
+ })
92
+
93
+ it('should refuse a retention shorter than ten minutes', () => {
94
+ context.inbox = { retention: 599 }
95
+ assert.throws(
96
+ () => validate(context),
97
+ (error) => /must be >= 600/.test(error.message)
98
+ )
61
99
  })
62
100
  })
63
101
 
64
- it('should require name as label', () => {
65
- delete context.name
66
- expect(() => validate(context)).toThrow(/required/)
102
+ it('should allow mono replicas and resources', () => {
103
+ context.mono = {
104
+ replicas: 2,
105
+ resources: {
106
+ cpu: ['200m', '2'],
107
+ memory: ['256Mi', '2Gi']
108
+ }
109
+ }
67
110
 
68
- context.name = 'foo bar'
69
- expect(() => validate(context)).toThrow(/pattern/)
111
+ assert.doesNotThrow(() => validate(context))
112
+ })
70
113
 
71
- context.name = 'foo-bar'
72
- expect(() => validate(context)).not.toThrow(/pattern/)
114
+ describe('compositions', () => {
115
+ it('should allow services', () => {
116
+ context.compositions[0].services = ['@toa.io/extensions.exposition']
117
+
118
+ assert.doesNotThrow(() => validate(context))
119
+ })
120
+
121
+ it('should require services to be a non-empty array of strings', () => {
122
+ context.compositions[0].services = []
123
+ assert.throws(
124
+ () => validate(context),
125
+ (error) => /fewer than 1 items/.test(error.message)
126
+ )
127
+
128
+ context.compositions[0].services = 'exposition'
129
+ assert.throws(
130
+ () => validate(context),
131
+ (error) => /must be array/.test(error.message)
132
+ )
133
+ })
134
+
135
+ it('should reject an unknown property', () => {
136
+ context.compositions[0].compoments = ['a.b']
137
+
138
+ assert.throws(
139
+ () => validate(context),
140
+ (error) => /must NOT have additional properties/.test(error.message)
141
+ )
142
+ })
143
+
144
+ it('should allow replicas of one', () => {
145
+ context.compositions[0].replicas = 1
146
+
147
+ assert.doesNotThrow(() => validate(context))
148
+ })
149
+
150
+ it('should refuse replicas below one', () => {
151
+ context.compositions[0].replicas = 0
152
+
153
+ assert.throws(
154
+ () => validate(context),
155
+ (error) => /must be >= 1/.test(error.message)
156
+ )
157
+ })
73
158
  })
74
159
 
75
- it('should set default packages location', () => {
76
- delete context.packages
77
- expect(() => validate(context)).not.toThrow()
78
- expect(context.packages).toBe('components/*')
160
+ describe('evicted', () => {
161
+ it('should allow components and services', () => {
162
+ context.evicted = {
163
+ components: ['a.b'],
164
+ services: ['@toa.io/extensions.exposition']
165
+ }
166
+
167
+ assert.doesNotThrow(() => validate(context))
168
+ })
169
+
170
+ it('should require a component to be named as one', () => {
171
+ context.evicted = { components: ['a'] }
172
+
173
+ assert.throws(
174
+ () => validate(context),
175
+ (error) => /must match pattern/.test(error.message)
176
+ )
177
+ })
178
+
179
+ it('should reject an empty declaration', () => {
180
+ context.evicted = {}
181
+
182
+ assert.throws(
183
+ () => validate(context),
184
+ (error) => /fewer than 1 properties/.test(error.message)
185
+ )
186
+ })
187
+
188
+ it('should reject an unknown property', () => {
189
+ context.evicted = { compoments: ['a.b'] }
190
+
191
+ assert.throws(
192
+ () => validate(context),
193
+ (error) => /must NOT have additional properties/.test(error.message)
194
+ )
195
+ })
79
196
  })
80
197
 
81
- it('should require registry url', () => {
82
- delete context.registry
83
- expect(() => validate(context)).toThrow(/required/)
198
+ describe('addressed', () => {
199
+ it('should take the timeout an addressed call waits by default', () => {
200
+ context.addressed = { timeout: 5000 }
201
+
202
+ assert.doesNotThrow(() => validate(context))
203
+ })
204
+
205
+ it('should take a timeout of a millisecond at least', () => {
206
+ context.addressed = { timeout: 0 }
207
+
208
+ assert.throws(
209
+ () => validate(context),
210
+ (error) => /must be >= 1/.test(error.message)
211
+ )
212
+ })
213
+
214
+ it('should take nothing else', () => {
215
+ context.addressed = { interval: 5000 }
216
+
217
+ assert.throws(
218
+ () => validate(context),
219
+ (error) => /must NOT have additional properties/.test(error.message)
220
+ )
221
+ })
84
222
  })
@@ -0,0 +1,35 @@
1
+ import { it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { join } from 'node:path'
4
+
5
+ import { definition } from '../src/definition.js'
6
+
7
+ it('reads a definition.js where a package ships one', async () => {
8
+ const { name, module } = await definition(join(import.meta.dirname, 'dummies/defined'))
9
+
10
+ assert.equal(name, 'dummies.defined')
11
+ assert.equal(module.declares, true)
12
+ assert.equal(module.runs, undefined)
13
+ })
14
+
15
+ it('reads the entry where a package ships no definition.js', async () => {
16
+ const path = join(import.meta.dirname, '../')
17
+ const { name, module } = await definition(path)
18
+
19
+ assert.equal(name, '@toa.io/norm')
20
+ assert.ok(module.context instanceof Function)
21
+ })
22
+
23
+ it('names a directory without package.json by its reference', async () => {
24
+ const path = join(import.meta.dirname, 'dummies/bare')
25
+ const { name, module } = await definition(path)
26
+
27
+ assert.equal(name, path)
28
+ assert.equal(module.declares, true)
29
+ })
30
+
31
+ it('loads a definition once', async () => {
32
+ const path = join(import.meta.dirname, 'dummies/bare')
33
+
34
+ assert.equal(await definition(path), await definition(path))
35
+ })
@@ -0,0 +1 @@
1
+ export const declares = true
@@ -0,0 +1 @@
1
+ export const declares = true
@@ -0,0 +1 @@
1
+ export const runs = true
@@ -0,0 +1 @@
1
+ { "name": "dummies.defined", "type": "module", "main": "index.js" }