@toa.io/extensions.origins 0.7.1 → 0.8.0-dev.0

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 (46) hide show
  1. package/package.json +9 -8
  2. package/readme.md +57 -0
  3. package/source/.deployment/index.js +5 -0
  4. package/source/.deployment/uris.js +35 -0
  5. package/source/.test/constants.js +3 -0
  6. package/source/.test/deployment.fixtures.js +20 -0
  7. package/source/.test/factory.fixtures.js +13 -0
  8. package/source/constants.js +3 -0
  9. package/source/deployment.js +28 -0
  10. package/source/deployment.test.js +159 -0
  11. package/source/env.js +50 -0
  12. package/source/factory.js +34 -0
  13. package/source/factory.test.js +122 -0
  14. package/{src → source}/index.js +2 -0
  15. package/source/manifest.js +23 -0
  16. package/source/manifest.test.js +51 -0
  17. package/source/protocols/amqp/.test/aspect.fixtures.js +15 -0
  18. package/source/protocols/amqp/.test/mock.comq.js +13 -0
  19. package/source/protocols/amqp/aspect.js +75 -0
  20. package/source/protocols/amqp/aspect.test.js +119 -0
  21. package/source/protocols/amqp/deployment.js +57 -0
  22. package/source/protocols/amqp/index.js +9 -0
  23. package/source/protocols/amqp/protocols.js +3 -0
  24. package/{test → source/protocols/http/.test}/aspect.fixtures.js +6 -6
  25. package/{src → source/protocols/http}/aspect.js +13 -20
  26. package/{test → source/protocols/http}/aspect.test.js +11 -11
  27. package/source/protocols/http/index.js +7 -0
  28. package/source/protocols/http/protocols.js +3 -0
  29. package/source/protocols/index.js +6 -0
  30. package/source/schemas/annotations.cos.yaml +1 -0
  31. package/source/schemas/index.js +8 -0
  32. package/source/schemas/manifest.cos.yaml +1 -0
  33. package/types/amqp.ts +9 -0
  34. package/types/deployment.d.ts +7 -0
  35. package/types/{aspect.ts → http.ts} +1 -2
  36. package/src/.manifest/index.js +0 -7
  37. package/src/.manifest/normalize.js +0 -17
  38. package/src/.manifest/schema.yaml +0 -13
  39. package/src/.manifest/validate.js +0 -13
  40. package/src/factory.js +0 -14
  41. package/src/manifest.js +0 -13
  42. package/test/factory.fixtures.js +0 -5
  43. package/test/factory.test.js +0 -22
  44. package/test/manifest.fixtures.js +0 -11
  45. package/test/manifest.test.js +0 -58
  46. package/types/declaration.d.ts +0 -11
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.origins",
3
- "version": "0.7.1",
4
- "description": "Toa operations context HTTP client",
3
+ "version": "0.8.0-dev.0",
4
+ "description": "Toa Origins",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
7
- "main": "src/index.js",
7
+ "main": "source/index.js",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/toa-io/toa.git"
@@ -19,14 +19,15 @@
19
19
  "test": "echo \"Error: run tests from root\" && exit 1"
20
20
  },
21
21
  "dependencies": {
22
- "@toa.io/core": "0.7.1",
23
- "@toa.io/generic": "0.7.0",
24
- "@toa.io/schema": "0.7.1",
25
- "@toa.io/yaml": "0.7.1",
22
+ "@toa.io/core": "0.8.0-dev.0",
23
+ "@toa.io/generic": "0.8.0-dev.0",
24
+ "@toa.io/schemas": "0.8.0-dev.0",
25
+ "@toa.io/yaml": "0.7.2-dev.1",
26
+ "comq": "0.6.0",
26
27
  "node-fetch": "2.6.7"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@types/node-fetch": "2.6.2"
30
31
  },
31
- "gitHead": "9989ea316ebf5698b1eee933e83a86e6a18b406b"
32
+ "gitHead": "5f1bfd167bb7919492d249f78bbcc6bd9fc645a4"
32
33
  }
package/readme.md ADDED
@@ -0,0 +1,57 @@
1
+ # Toa Origins
2
+
3
+ Origins extension enables external communications over supported protocols (`HTTP` and `AMQP`).
4
+
5
+ ## TL;DR
6
+
7
+ ```yaml
8
+ # manifest.toa.yaml
9
+ name: dummy
10
+ namespace: dummies
11
+
12
+ origins:
13
+ website: http://www.domain.com/docs/
14
+ messages: amqps://amqp.amazon.com
15
+ ```
16
+
17
+ ```javascript
18
+ // Node.js bridge
19
+ async function transition (input, object, context) {
20
+ await context.http.example.get() // GET http://www.domain.com/docs/example
21
+ await context.amqp.emit('something_happened', { really: true })
22
+ }
23
+ ```
24
+
25
+ ```yaml
26
+ # context.toa.yaml
27
+ origins:
28
+ dummies.dummy:
29
+ messages: amqps://amqp.azure.com
30
+ messages@staging: amqp://amqp.stage
31
+ ```
32
+
33
+ ## Declaration
34
+
35
+ Origins extension declaration is a [Pointer](/libraries/pointer). Declarations can be overridden by
36
+ the context annotations.
37
+
38
+ ## HTTP
39
+
40
+ Uses [node-fetch](https://github.com/node-fetch/node-fetch) and returns its result.
41
+
42
+ ## AMQP
43
+
44
+ Uses [ComQ](https://github.com/toa-io/comq), thus, provides interface of `comq.IO` restricted
45
+ to `emit` and `request` methods.
46
+
47
+ AMQP origins require credential secrets to be deployed. Secret's name must
48
+ follow `toa-origins-{namespace}-{component}-{origin}` and it must have keys `username`
49
+ and `password`.
50
+
51
+ ### Example
52
+
53
+ ```shell
54
+ # deploy credentials to the current kubectl context
55
+ $ toa conceal toa-origins-dummies-dummiy-messages username developer
56
+ $ toa conceal toa-origins-dummies-dummiy-messages password secret
57
+ ```
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { uris } = require('./uris')
4
+
5
+ exports.uris = uris
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ const { PREFIX } = require('../constants')
4
+
5
+ /**
6
+ * @param {toa.norm.context.dependencies.Instance[]} instances
7
+ * @param {toa.origins.Annotations} annotations
8
+ * @returns {toa.deployment.dependency.Variables}
9
+ */
10
+ function uris (instances, annotations) {
11
+ const variables = {}
12
+
13
+ for (const [id, annotation] of Object.entries(annotations)) {
14
+ const component = instances.find((instance) => instance.locator.id === id)
15
+
16
+ if (component === undefined) throw new Error(`Origins annotations error: component '${id}' is not found`)
17
+
18
+ for (const origin of Object.keys(annotation)) {
19
+ if (!(origin in component.manifest)) {
20
+ throw new Error(`Origins annotations error: component '${id}' doesn't have '${origin}' origin`)
21
+ }
22
+ }
23
+
24
+ const name = PREFIX + component.locator.uppercase
25
+ const json = JSON.stringify(annotation)
26
+ const value = btoa(json)
27
+ const variable = { name, value }
28
+
29
+ variables[component.locator.label] = [variable]
30
+ }
31
+
32
+ return variables
33
+ }
34
+
35
+ exports.uris = uris
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ exports.PROTOCOLS = ['http:', 'https:', 'amqp:', 'amqps:']
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { Locator } = require('@toa.io/core')
5
+ const { random, sample } = require('@toa.io/generic')
6
+
7
+ const { PROTOCOLS } = require('./constants')
8
+
9
+ const component = () => ({
10
+ locator: new Locator(generate(), generate()),
11
+ manifest: { [generate()]: sample(PROTOCOLS) + '//' + generate() }
12
+ })
13
+
14
+ const components = () => {
15
+ const length = random(20) + 10
16
+
17
+ return Array.from({ length }, component)
18
+ }
19
+
20
+ exports.components = components
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ /** @type {Record<string, string>} */
6
+ const manifest = {
7
+ [generate()]: 'https://toa.io',
8
+ [generate()]: 'https://api.domain.com',
9
+ [generate()]: 'amqp://localhost',
10
+ [generate()]: 'amqps://localhost'
11
+ }
12
+
13
+ exports.manifest = manifest
@@ -0,0 +1,3 @@
1
+ 'use strict'
2
+
3
+ exports.PREFIX = 'TOA_ORIGINS_'
@@ -0,0 +1,28 @@
1
+ 'use strict'
2
+
3
+ const { merge } = require('@toa.io/generic')
4
+ const schemas = require('./schemas')
5
+ const protocols = require('./protocols')
6
+ const create = require('./.deployment')
7
+
8
+ /**
9
+ * @param {toa.norm.context.dependencies.Instance[]} instances
10
+ * @param {toa.origins.Annotations} annotations
11
+ * @returns {toa.deployment.dependency.Declaration}
12
+ */
13
+ function deployment (instances, annotations = {}) {
14
+ schemas.annotations.validate(annotations)
15
+
16
+ const uris = create.uris(instances, annotations)
17
+ const variables = { ...uris }
18
+
19
+ protocols.reduce((variables, provider) => {
20
+ const specifics = provider.deployment?.(instances)
21
+
22
+ return merge(variables, specifics)
23
+ }, variables)
24
+
25
+ return { variables }
26
+ }
27
+
28
+ exports.deployment = deployment
@@ -0,0 +1,159 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { sample, letters: { up } } = require('@toa.io/generic')
5
+
6
+ const fixtures = require('./.test/deployment.fixtures')
7
+ const { deployment } = require('../')
8
+
9
+ it('should be', async () => {
10
+ expect(deployment).toBeInstanceOf(Function)
11
+ })
12
+
13
+ /** @type {toa.norm.context.dependencies.Instance[]} */
14
+ let components
15
+
16
+ /** @type {string} */
17
+ let origin
18
+
19
+ /** @type {toa.norm.context.dependencies.Instance} */
20
+ let component
21
+
22
+ beforeEach(() => {
23
+ components = fixtures.components()
24
+
25
+ component = sample(components)
26
+
27
+ const origins = Object.keys(component.manifest)
28
+
29
+ origin = sample(origins)
30
+ })
31
+
32
+ describe('validation', () => {
33
+ it('should throw on annotation component mismatch', async () => {
34
+ const id = generate()
35
+
36
+ const annotations = {
37
+ [id]: {
38
+ [origin]: 'dev://' + generate()
39
+ }
40
+ }
41
+
42
+ expect(() => deployment(components, annotations))
43
+ .toThrow(`Origins annotations error: component '${id}' is not found`)
44
+ })
45
+
46
+ it('should throw on annotation origin mismatch', async () => {
47
+ const id = component.locator.id
48
+ const origin = generate()
49
+
50
+ const annotations = {
51
+ [id]: {
52
+ [origin]: 'dev://' + generate()
53
+ }
54
+ }
55
+
56
+ expect(() => deployment(components, annotations))
57
+ .toThrow(`Origins annotations error: component '${id}' doesn't have '${origin}' origin`)
58
+ })
59
+
60
+ it('should throw if annotation is not valid', async () => {
61
+ const annotations = /** @type {toa.origins.Annotations} */ { [component.locator.id]: generate() }
62
+
63
+ expect(() => deployment(components, annotations)).toThrow('must be object')
64
+ })
65
+
66
+ it('should throw if annotation is URI is not valid', async () => {
67
+ const annotations = {
68
+ [component.locator.id]: {
69
+ [origin]: 'hello!'
70
+ }
71
+ }
72
+
73
+ expect(() => deployment(components, annotations)).toThrow('must match format')
74
+ })
75
+ })
76
+
77
+ it('should create variables', () => {
78
+ const value = 'dev://' + generate()
79
+
80
+ /** @type {toa.origins.Annotations} */
81
+ const annotations = {
82
+ [component.locator.id]: {
83
+ [origin]: value
84
+ }
85
+ }
86
+
87
+ const output = deployment(components, annotations)
88
+
89
+ expect(output.variables).not.toBeUndefined()
90
+
91
+ const variables = output.variables[component.locator.label]
92
+ const varName = 'TOA_ORIGINS_' + component.locator.uppercase
93
+ const variable = findVariable(variables, varName)
94
+
95
+ expect(variable).toBeDefined()
96
+
97
+ const json = JSON.stringify(annotations[component.locator.id])
98
+ const base64 = btoa(json)
99
+
100
+ expect(variable.value).toStrictEqual(base64)
101
+ })
102
+
103
+ describe('amqp', () => {
104
+ beforeEach(() => {
105
+ const amqpComponents = components.filter(
106
+ (component) => {
107
+ const origin = Object.keys(component.manifest)
108
+ const url = new URL(component.manifest[origin])
109
+
110
+ return url.protocol === 'amqp:' || url.protocol === 'amqps:'
111
+ }
112
+ )
113
+
114
+ component = sample(amqpComponents)
115
+
116
+ const origins = Object.keys(component.manifest)
117
+
118
+ origin = sample(origins)
119
+ })
120
+
121
+ it('should create credential secrets', () => {
122
+ /** @type {toa.origins.Annotations} */
123
+ const annotations = {
124
+ [component.locator.id]: {
125
+ [origin]: 'amqps://whatever'
126
+ }
127
+ }
128
+
129
+ const output = deployment(components, annotations)
130
+ const variables = output.variables[component.locator.label]
131
+
132
+ expect(variables).toBeDefined()
133
+
134
+ const envPrefix = `TOA_ORIGINS_${component.locator.uppercase}_`
135
+ const secPrefix = `toa-origins-${component.locator.label}-`
136
+
137
+ for (const property of ['username', 'password']) {
138
+ const variableName = envPrefix + up(property)
139
+ const secretName = secPrefix + property
140
+ const variable = findVariable(variables, variableName)
141
+
142
+ expect(variable).toBeDefined()
143
+
144
+ expect(variable.secret).toStrictEqual({
145
+ name: secretName,
146
+ key: property
147
+ })
148
+ }
149
+ })
150
+ })
151
+
152
+ /**
153
+ * @param {toa.deployment.dependency.Variable[]} variables
154
+ * @param {string} name
155
+ * @returns {toa.deployment.dependency.Variable}
156
+ */
157
+ function findVariable (variables, name) {
158
+ return variables.find((variable) => variable.name === name)
159
+ }
package/source/env.js ADDED
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+
3
+ const { PREFIX } = require('./constants')
4
+ const { overwrite, remap, letters: { up } } = require('@toa.io/generic')
5
+
6
+ /**
7
+ * @param {toa.core.Locator} locator
8
+ * @param {toa.origins.Manifest} manifest
9
+ */
10
+ function apply (locator, manifest) {
11
+ const variable = PREFIX + locator.uppercase
12
+ const envValue = readEnv(variable)
13
+
14
+ overwrite(manifest, envValue)
15
+ addCredentials(manifest, variable)
16
+ }
17
+
18
+ function readEnv (variable) {
19
+ if (!(variable in process.env)) return
20
+
21
+ const base64 = process.env[variable]
22
+ const json = atob(base64)
23
+
24
+ return JSON.parse(json)
25
+ }
26
+
27
+ /**
28
+ * @param {toa.origins.Manifest} manifest
29
+ * @param {string} variable
30
+ */
31
+ function addCredentials (manifest, variable) {
32
+ const prefix = variable + '_'
33
+
34
+ remap(manifest, (reference, origin) => {
35
+ const originPrefix = prefix + up(origin)
36
+ const username = process.env[originPrefix + '_USERNAME']
37
+ const password = process.env[originPrefix + '_PASSWORD']
38
+
39
+ if (username === undefined && password === undefined) return
40
+
41
+ const url = new URL(reference)
42
+
43
+ url.username = username
44
+ url.password = password
45
+
46
+ manifest[origin] = url.href
47
+ })
48
+ }
49
+
50
+ exports.apply = apply
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ const protocols = require('./protocols')
4
+ const env = require('./env')
5
+
6
+ /**
7
+ * @implements {toa.core.extensions.Factory}
8
+ */
9
+ class Factory {
10
+ aspect (locator, manifest) {
11
+ env.apply(locator, /** @type {toa.origins.Manifest} */ manifest)
12
+
13
+ return protocols.map((protocol) => this.#createAspect(protocol, manifest))
14
+ }
15
+
16
+ /**
17
+ * @param {object} protocol
18
+ * @param {toa.origins.Manifest} manifest
19
+ * @return {toa.core.extensions.Aspect}
20
+ */
21
+ #createAspect (protocol, manifest) {
22
+ const protocolManifest = {}
23
+
24
+ for (const [origin, reference] of Object.entries(manifest)) {
25
+ const url = new URL(reference)
26
+
27
+ if (protocol.protocols.includes(url.protocol)) protocolManifest[origin] = reference
28
+ }
29
+
30
+ return protocol.create(protocolManifest)
31
+ }
32
+ }
33
+
34
+ exports.Factory = Factory
@@ -0,0 +1,122 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { Locator } = require('@toa.io/core')
5
+ const { sample, overwrite, letters: { up } } = require('@toa.io/generic')
6
+
7
+ jest.mock('./protocols/http/aspect')
8
+ jest.mock('./protocols/amqp/aspect')
9
+
10
+ const http = require('./protocols/http/aspect')
11
+ const amqp = require('./protocols/amqp/aspect')
12
+
13
+ const fixtures = require('./.test/factory.fixtures')
14
+ const { Factory } = require('../')
15
+
16
+ /** @type {toa.core.extensions.Factory} */
17
+ let factory
18
+
19
+ beforeEach(() => {
20
+ jest.clearAllMocks()
21
+
22
+ factory = new Factory()
23
+ })
24
+
25
+ it('should create aspects', () => {
26
+ factory.aspect(new Locator(generate(), generate()), fixtures.manifest)
27
+
28
+ const httpManifest = filterManifest(fixtures.manifest, 'http')
29
+ const amqpManifest = filterManifest(fixtures.manifest, 'amqp')
30
+
31
+ expect(http.create).toHaveBeenCalledWith(httpManifest)
32
+ expect(amqp.create).toHaveBeenCalledWith(amqpManifest)
33
+ })
34
+
35
+ describe('env', () => {
36
+ it('should overwrites URLs from environment', async () => {
37
+ const httpManifest = filterManifest(fixtures.manifest, 'http')
38
+ const key = sample(Object.keys(httpManifest))
39
+ const override = { [key]: 'http://' + generate() }
40
+ const json = JSON.stringify(override)
41
+ const base64 = btoa(json)
42
+ const locator = new Locator(generate(), generate())
43
+
44
+ process.env['TOA_ORIGINS_' + locator.uppercase] = base64
45
+
46
+ factory.aspect(locator, fixtures.manifest)
47
+
48
+ const expected = overwrite(httpManifest, override)
49
+
50
+ expect(http.create).toHaveBeenCalledWith(expected)
51
+ })
52
+
53
+ describe('amqp', () => {
54
+ /** @type {toa.origins.Manifest} */
55
+ let amqpManifest
56
+
57
+ beforeEach(() => {
58
+ amqpManifest = filterManifest(fixtures.manifest, 'amqp')
59
+ })
60
+
61
+ it('should add credentials from environment', async () => {
62
+ const key = sample(Object.keys(amqpManifest))
63
+ const locator = new Locator(generate(), generate())
64
+ const envPrefix = 'TOA_ORIGINS_' + locator.uppercase + '_' + up(key) + '_'
65
+ const username = generate()
66
+ const password = generate()
67
+
68
+ process.env[envPrefix + 'USERNAME'] = username
69
+ process.env[envPrefix + 'PASSWORD'] = password
70
+
71
+ factory.aspect(locator, amqpManifest)
72
+
73
+ const manifest = amqp.create.mock.calls[0][0]
74
+ const origin = manifest[key]
75
+ const url = new URL(origin)
76
+
77
+ expect(url.username).toStrictEqual(username)
78
+ expect(url.password).toStrictEqual(password)
79
+ })
80
+
81
+ it('should add credentials to URLs from environment', async () => {
82
+ const key = sample(Object.keys(amqpManifest))
83
+ const hostname = generate().toLowerCase()
84
+ const override = { [key]: 'amqp://' + hostname }
85
+ const json = JSON.stringify(override)
86
+ const base64 = btoa(json)
87
+ const locator = new Locator(generate(), generate())
88
+ const envPrefix = 'TOA_ORIGINS_' + locator.uppercase + '_' + up(key) + '_'
89
+ const username = generate()
90
+ const password = generate()
91
+
92
+ process.env['TOA_ORIGINS_' + locator.uppercase] = base64
93
+ process.env[envPrefix + 'USERNAME'] = username
94
+ process.env[envPrefix + 'PASSWORD'] = password
95
+
96
+ factory.aspect(locator, fixtures.manifest)
97
+
98
+ const manifest = amqp.create.mock.calls[0][0]
99
+ const origin = manifest[key]
100
+ const url = new URL(origin)
101
+
102
+ expect(url.hostname).toStrictEqual(hostname)
103
+ expect(url.username).toStrictEqual(username)
104
+ expect(url.password).toStrictEqual(password)
105
+ })
106
+ })
107
+ })
108
+
109
+ /**
110
+ * @param {toa.origins.Manifest} manifest
111
+ * @param {string} protocol
112
+ * @return {toa.origins.Manifest}
113
+ */
114
+ function filterManifest (manifest, protocol) {
115
+ const result = {}
116
+
117
+ for (const [origin, reference] of Object.entries(manifest)) {
118
+ if (reference.slice(0, protocol.length) === protocol) result[origin] = reference
119
+ }
120
+
121
+ return result
122
+ }
@@ -1,7 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const { manifest } = require('./manifest')
4
+ const { deployment } = require('./deployment')
4
5
  const { Factory } = require('./factory')
5
6
 
6
7
  exports.manifest = manifest
8
+ exports.deployment = deployment
7
9
  exports.Factory = Factory
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ const schemas = require('./schemas')
4
+ const protocols = require('./protocols')
5
+
6
+ /**
7
+ * @param {toa.origins.Manifest} manifest
8
+ * @returns {toa.origins.Manifest}
9
+ */
10
+ function manifest (manifest) {
11
+ schemas.manifest.validate(manifest)
12
+
13
+ for (const uri of Object.values(manifest)) {
14
+ const protocol = new URL(uri).protocol
15
+ const supported = protocols.find((provider) => provider.protocols.includes(protocol))
16
+
17
+ if (supported === undefined) throw new Error(`'${protocol}' protocol is not supported`)
18
+ }
19
+
20
+ return manifest
21
+ }
22
+
23
+ exports.manifest = manifest
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { PROTOCOLS } = require('./.test/constants')
5
+
6
+ const { manifest } = require('../')
7
+
8
+ it('should be', async () => {
9
+ expect(manifest).toBeInstanceOf(Function)
10
+ })
11
+
12
+ it('should return manifest', async () => {
13
+ const input = { [generate()]: 'http://' + generate() }
14
+ const output = manifest(input)
15
+
16
+ expect(output).toStrictEqual(input)
17
+ })
18
+
19
+ it('should fail if not Record<string, string>', async () => {
20
+ const input = /** @type {toa.origins.Manifest} */ {
21
+ foo: {
22
+ bar: 'dev://null'
23
+ }
24
+ }
25
+
26
+ expect(() => manifest(input)).toThrow()
27
+ })
28
+
29
+ it('should pass if valid', async () => {
30
+ const input = { foo: 'amqp://' + generate() }
31
+
32
+ expect(() => manifest(input)).not.toThrow()
33
+ })
34
+
35
+ it('should fail if not uri', async () => {
36
+ const input = { [generate()]: generate() }
37
+
38
+ expect(() => manifest(input)).toThrow('must match format')
39
+ })
40
+
41
+ it('should throw if protocol is not supported', async () => {
42
+ const input = { foo: 'wat://' + generate() }
43
+
44
+ expect(() => manifest(input)).toThrow('is not supported')
45
+ })
46
+
47
+ it.each(PROTOCOLS)('should support %s protocol', async (protocol) => {
48
+ const input = { foo: protocol + '//' + generate() }
49
+
50
+ expect(() => manifest(input)).not.toThrow()
51
+ })
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { random } = require('@toa.io/generic')
5
+
6
+ const manifest = {}
7
+ const originCount = random(5) + 2
8
+
9
+ for (let j = 0; j < originCount; j++) {
10
+ const origin = generate()
11
+
12
+ manifest[origin] = 'amqp://' + generate()
13
+ }
14
+
15
+ exports.manifest = manifest