@bitfinex/bfx-svc-test-helper 1.1.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.
- package/README.md +146 -0
- package/client.js +89 -0
- package/example-fauxgrenache.js +44 -0
- package/example-server-advanced.js +44 -0
- package/example-server-simple.js +28 -0
- package/example.js +41 -0
- package/fauxgrenache.js +148 -0
- package/grapes.js +118 -0
- package/index.js +8 -0
- package/package.json +27 -0
- package/server.js +169 -0
- package/utils.js +21 -0
- package/worker.js +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# bfx-svc-test-helper
|
|
2
|
+
|
|
3
|
+
Test helper to help with common day to day testing tasks.
|
|
4
|
+
|
|
5
|
+
## Do you see yourself copy/pasting or doing the same setup code again?
|
|
6
|
+
|
|
7
|
+
Maybe it's time for a pull request or an issue! `bfx-svc-test-helper` is open
|
|
8
|
+
for improvements and suggestions that make our day to day life as a team easier!
|
|
9
|
+
|
|
10
|
+
## Usage in tests
|
|
11
|
+
|
|
12
|
+
The API supports Promise and callback based APIs. For a common example,
|
|
13
|
+
see [example.js](example.js).
|
|
14
|
+
|
|
15
|
+
You can also stub/mock Grenache services. See
|
|
16
|
+
[example-fauxgrenache.js](example-fauxgrenache.js) for an example.
|
|
17
|
+
|
|
18
|
+
## API
|
|
19
|
+
|
|
20
|
+
### `createGrapes([opts]) => Grapes`
|
|
21
|
+
|
|
22
|
+
Creates an instance to manage Grapes for testing.
|
|
23
|
+
|
|
24
|
+
### Arguments
|
|
25
|
+
|
|
26
|
+
- `opts` is an object of additional options, there is able to pass:
|
|
27
|
+
- `ports` is an array of object, for example:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
[{
|
|
31
|
+
dht_port: 20002,
|
|
32
|
+
dht_bootstrap: ['127.0.0.1:20001'],
|
|
33
|
+
api_port: 40001
|
|
34
|
+
}, {
|
|
35
|
+
dht_port: 20001,
|
|
36
|
+
dht_bootstrap: ['127.0.0.1:20002'],
|
|
37
|
+
api_port: 30001
|
|
38
|
+
}]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `grapes.start([cb]) => Promise`
|
|
42
|
+
|
|
43
|
+
Creates two "default Grapes" and calls the callback when the found each other.
|
|
44
|
+
|
|
45
|
+
### `grapes.onAnnounce([servicename], [cb]) => Promise`
|
|
46
|
+
|
|
47
|
+
Calls the callback as soon as the service is announced on the network.
|
|
48
|
+
Leave blank to match any announce.
|
|
49
|
+
|
|
50
|
+
### `grapes.stop([cb]) => Promise`
|
|
51
|
+
|
|
52
|
+
Stops the Grapes.
|
|
53
|
+
|
|
54
|
+
### `createWorker(worker, [grapes]) => Worker`
|
|
55
|
+
|
|
56
|
+
Creates an svc-js worker instance.
|
|
57
|
+
|
|
58
|
+
### `worker.start([cb]) => Promise`
|
|
59
|
+
|
|
60
|
+
Starts the worker. If `grapes` were passed via `createWorker`, resolves as
|
|
61
|
+
soon as the service name is announced on the network.
|
|
62
|
+
|
|
63
|
+
### `worker.stop([cb]) => Promise`
|
|
64
|
+
|
|
65
|
+
Stops the worker.
|
|
66
|
+
|
|
67
|
+
### `createClient(worker) => Client`
|
|
68
|
+
|
|
69
|
+
Sets up a typical PeerClient needed for most use cases.
|
|
70
|
+
`worker` can be the name of the worker e.g. `rest:util:net`, or can be a
|
|
71
|
+
`<Worker>` instance from `createWorker`
|
|
72
|
+
|
|
73
|
+
### `client.request(args, [opts], [cb]) => Promise`
|
|
74
|
+
|
|
75
|
+
Makes a request to the configured worker.
|
|
76
|
+
|
|
77
|
+
### `client.stop([cb]) => Promise`
|
|
78
|
+
|
|
79
|
+
Stops the client.
|
|
80
|
+
|
|
81
|
+
### `createFxGrenache(mocks, grapes|url, [opts]) => FauxGrenacheServer`
|
|
82
|
+
|
|
83
|
+
Spins up Grenache service mocks.
|
|
84
|
+
|
|
85
|
+
`opts.port` - the port the server will listen to, defaults to `1557`
|
|
86
|
+
`opts.link` - custom / own link, can be used for customised setups
|
|
87
|
+
|
|
88
|
+
Second parameter is either a `Grapes` instance or a http url pointing
|
|
89
|
+
to the Grenache Grape HTTP Server.
|
|
90
|
+
|
|
91
|
+
### `fxgrenache.start([cb]) => Promise`
|
|
92
|
+
|
|
93
|
+
Makes a request to the configured mockserver.
|
|
94
|
+
|
|
95
|
+
### `fxgrenache.stop([cb]) => Promise`
|
|
96
|
+
|
|
97
|
+
Stops the server.
|
|
98
|
+
|
|
99
|
+
### `createServer(mocks, opts) => Server`
|
|
100
|
+
|
|
101
|
+
Sets up a HTTP stub/mock server.
|
|
102
|
+
|
|
103
|
+
`opts.port` - the port the server will listen to.
|
|
104
|
+
`opts.debug` - print the data sent from the client via post
|
|
105
|
+
|
|
106
|
+
`opts.debug` is useful when you are creating new mock definitions, especially
|
|
107
|
+
if you use an external client library to send the request.
|
|
108
|
+
|
|
109
|
+
`mocks` can be a mock definition or an array of mock definitions.
|
|
110
|
+
|
|
111
|
+
The server can basically match, test and return anything on request/response,
|
|
112
|
+
sent payloads etc. For convenience, there are shorthands that should suffice
|
|
113
|
+
in most cases: [example-server-simple.js](example-server-simple.js).
|
|
114
|
+
|
|
115
|
+
For advanced route matching and testing of incoming requests, see [example-server-advanced.js](example-server-advanced.js)
|
|
116
|
+
|
|
117
|
+
### `server.start([cb]) => Promise`
|
|
118
|
+
|
|
119
|
+
Makes a request to the configured mockserver.
|
|
120
|
+
|
|
121
|
+
### `server.stop([cb]) => Promise`
|
|
122
|
+
|
|
123
|
+
Stops the server.
|
|
124
|
+
|
|
125
|
+
### `utils.getApi(worker) => Api`
|
|
126
|
+
|
|
127
|
+
Returns the loc.api instance of the worker
|
|
128
|
+
|
|
129
|
+
### `utils.getGrapeApiPort(grapesWorker) => port`
|
|
130
|
+
|
|
131
|
+
Gets the HTTP port of one of the Grape workers
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
const createGrapes = require('bfx-svc-test-helper/grapes')
|
|
137
|
+
const utils = require('bfx-svc-test-helper/utils')
|
|
138
|
+
|
|
139
|
+
const grapes = createGrapes()
|
|
140
|
+
|
|
141
|
+
;(async () => {
|
|
142
|
+
await grapes.start()
|
|
143
|
+
const port = utils.getGrapeApiPort(grapes)
|
|
144
|
+
console.log(port) // 30001
|
|
145
|
+
})()
|
|
146
|
+
```
|
package/client.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { PeerRPCClient } = require('grenache-nodejs-http')
|
|
4
|
+
const Link = require('grenache-nodejs-link')
|
|
5
|
+
|
|
6
|
+
class Client {
|
|
7
|
+
constructor (worker, opts = {}) {
|
|
8
|
+
this.peer = null
|
|
9
|
+
this.link = null
|
|
10
|
+
|
|
11
|
+
this.worker = worker
|
|
12
|
+
this.workerName = this.getWorkerName(worker)
|
|
13
|
+
|
|
14
|
+
const oDefault = {
|
|
15
|
+
grape: 'http://127.0.0.1:30001'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.conf = { ...oDefault, ...opts }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getWorkerName (worker) {
|
|
22
|
+
if (typeof worker === 'string') {
|
|
23
|
+
return worker
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return worker.name
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
connectGrape (cb = () => {}) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
if (this.link && this.peer) {
|
|
32
|
+
cb(null)
|
|
33
|
+
resolve()
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.link = new Link({
|
|
38
|
+
grape: this.conf.grape
|
|
39
|
+
})
|
|
40
|
+
this.link.start()
|
|
41
|
+
|
|
42
|
+
this.peer = new PeerRPCClient(this.link, this.conf)
|
|
43
|
+
this.peer.init()
|
|
44
|
+
|
|
45
|
+
resolve()
|
|
46
|
+
cb(null)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
request (query, opts, cb) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
if (typeof opts === 'function') {
|
|
53
|
+
this.request(query, { timeout: 10000 }, opts)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.connectGrape(() => {
|
|
58
|
+
this.peer.request(this.workerName, query, opts, (err, res) => {
|
|
59
|
+
if (err) {
|
|
60
|
+
if (cb) return cb(err)
|
|
61
|
+
reject(err)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (cb) return cb(null, res)
|
|
66
|
+
resolve(res)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
stop (cb) {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
if (this.peer && this.link) {
|
|
75
|
+
this.peer.stop()
|
|
76
|
+
this.link.stop()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (cb) return cb(null)
|
|
80
|
+
resolve()
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function createClient (worker, opts) {
|
|
86
|
+
return new Client(worker, opts)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = createClient
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const createGrapes = require('./grapes')
|
|
4
|
+
const createClient = require('./client')
|
|
5
|
+
const createFxGrenache = require('./fauxgrenache')
|
|
6
|
+
|
|
7
|
+
const grapes = createGrapes()
|
|
8
|
+
|
|
9
|
+
const stubs = {
|
|
10
|
+
'rest:util:net': {
|
|
11
|
+
getIpInfo (space, ip, cb) {
|
|
12
|
+
const res = [ip, { country: 'US', region: 'CA' }]
|
|
13
|
+
return cb(null, res)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
;(async () => {
|
|
19
|
+
await grapes.start()
|
|
20
|
+
// lets create a worker stub
|
|
21
|
+
|
|
22
|
+
// we can pass in Grape instances or an url
|
|
23
|
+
const url = 'http://localhost:30001'
|
|
24
|
+
const grape = grapes || url
|
|
25
|
+
|
|
26
|
+
const fxg = createFxGrenache(stubs, grape)
|
|
27
|
+
await fxg.start()
|
|
28
|
+
console.log('stub service started, announcing on grape')
|
|
29
|
+
|
|
30
|
+
const client = createClient('rest:util:net')
|
|
31
|
+
const res = await client.request({
|
|
32
|
+
action: 'getIpInfo',
|
|
33
|
+
args: ['8.8.8.8']
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
console.log('got response:', res)
|
|
37
|
+
|
|
38
|
+
await client.stop()
|
|
39
|
+
console.log('client stopped')
|
|
40
|
+
await fxg.stop()
|
|
41
|
+
console.log('grenache stub stopped')
|
|
42
|
+
await grapes.stop()
|
|
43
|
+
console.log('grapes stopped')
|
|
44
|
+
})()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const createServer = require('./server')
|
|
4
|
+
const fetch = require('node-fetch')
|
|
5
|
+
const assert = require('assert')
|
|
6
|
+
|
|
7
|
+
const server = createServer({
|
|
8
|
+
reply: (reply) => {
|
|
9
|
+
reply({ fruit: 'lemon' }, 404) // default: 200
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
// all are optional
|
|
13
|
+
match: (payload, match) => {
|
|
14
|
+
match({
|
|
15
|
+
route: '/foo',
|
|
16
|
+
method: 'POST',
|
|
17
|
+
payload: (payload, cb) => {
|
|
18
|
+
return cb(null, payload.fruit === 'mango')
|
|
19
|
+
},
|
|
20
|
+
custom: (req, res, cb) => {
|
|
21
|
+
return cb(null, true)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// makes assertions on the _sent request_
|
|
27
|
+
test: (req, res, payload, cb) => {
|
|
28
|
+
assert.ok(payload.fruit, 'missing property')
|
|
29
|
+
cb(null)
|
|
30
|
+
}
|
|
31
|
+
}, { port: 1337 });
|
|
32
|
+
|
|
33
|
+
(async () => {
|
|
34
|
+
await server.start()
|
|
35
|
+
|
|
36
|
+
const res = await fetch('http://localhost:1337/foo', {
|
|
37
|
+
body: JSON.stringify({ fruit: 'mango' }),
|
|
38
|
+
method: 'POST'
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
console.log('response', await res.json(), res.status)
|
|
42
|
+
|
|
43
|
+
await server.stop()
|
|
44
|
+
})()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const createServer = require('./server')
|
|
4
|
+
const fetch = require('node-fetch')
|
|
5
|
+
|
|
6
|
+
const server = createServer([
|
|
7
|
+
{
|
|
8
|
+
reply: { animal: 'platypus' },
|
|
9
|
+
route: '/australia'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
reply: { animal: 'Fair Isle Wren' },
|
|
13
|
+
route: '/england'
|
|
14
|
+
}
|
|
15
|
+
], { port: 1337, debug: true });
|
|
16
|
+
|
|
17
|
+
(async () => {
|
|
18
|
+
await server.start()
|
|
19
|
+
|
|
20
|
+
const res = await fetch('http://localhost:1337/australia', {
|
|
21
|
+
body: JSON.stringify({ foo: 'bar' }),
|
|
22
|
+
method: 'POST'
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
console.log('response', await res.json(), res.status)
|
|
26
|
+
|
|
27
|
+
await server.stop()
|
|
28
|
+
})()
|
package/example.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const createGrapes = require('./grapes')
|
|
6
|
+
const createWorker = require('./worker')
|
|
7
|
+
const createClient = require('./client')
|
|
8
|
+
|
|
9
|
+
const grapes = createGrapes()
|
|
10
|
+
|
|
11
|
+
;(async () => {
|
|
12
|
+
await grapes.start()
|
|
13
|
+
|
|
14
|
+
const worker = createWorker({
|
|
15
|
+
env: 'development',
|
|
16
|
+
wtype: 'wrk-util-net-api',
|
|
17
|
+
apiPort: 8721,
|
|
18
|
+
serviceRoot: path.join(__dirname, '..', 'bfx-util-net-js')
|
|
19
|
+
}, grapes)
|
|
20
|
+
|
|
21
|
+
await worker.start()
|
|
22
|
+
console.log('worker started, announcing on grape')
|
|
23
|
+
|
|
24
|
+
// you can also pass 'rest:util:net'
|
|
25
|
+
const client = createClient(worker)
|
|
26
|
+
|
|
27
|
+
const res = await client.request({
|
|
28
|
+
action: 'getIpGeo',
|
|
29
|
+
args: ['8.8.8.8']
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
console.log('got response:')
|
|
33
|
+
console.log(res)
|
|
34
|
+
|
|
35
|
+
await client.stop()
|
|
36
|
+
console.log('client stopped')
|
|
37
|
+
|
|
38
|
+
await worker.stop()
|
|
39
|
+
console.log('worker stopped')
|
|
40
|
+
await grapes.stop()
|
|
41
|
+
})()
|
package/fauxgrenache.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const http = require('http')
|
|
4
|
+
const Link = require('grenache-nodejs-link')
|
|
5
|
+
const { getGrapeApiUrl } = require('./utils')
|
|
6
|
+
|
|
7
|
+
const getRawBody = require('raw-body')
|
|
8
|
+
const async = require('async')
|
|
9
|
+
|
|
10
|
+
class FauxGrenache {
|
|
11
|
+
constructor (stubs, grapes, opts = {}) {
|
|
12
|
+
this.stubs = stubs
|
|
13
|
+
this.grapes = grapes
|
|
14
|
+
|
|
15
|
+
this.port = opts.port || 1557
|
|
16
|
+
this.link = opts.link || null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getLink () {
|
|
20
|
+
if (this.link) {
|
|
21
|
+
return this.link
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const url = getGrapeApiUrl(this.grapes)
|
|
25
|
+
this.link = new Link({
|
|
26
|
+
grape: url
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return this.link
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
start (cb = () => {}) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
this.link = this.getLink()
|
|
35
|
+
|
|
36
|
+
async.waterfall([
|
|
37
|
+
(next) => {
|
|
38
|
+
this.link.start()
|
|
39
|
+
next()
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
(next) => {
|
|
43
|
+
this.listen(this.port, next)
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
(next) => {
|
|
47
|
+
this.announce(this.stubs, next)
|
|
48
|
+
}
|
|
49
|
+
], (err) => {
|
|
50
|
+
if (err) {
|
|
51
|
+
reject(err)
|
|
52
|
+
cb(err)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
resolve()
|
|
57
|
+
cb(null)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
stop (cb = () => {}) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
async.waterfall([
|
|
65
|
+
(next) => {
|
|
66
|
+
this.stopAnnounces(this.stubs)
|
|
67
|
+
next()
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
(next) => {
|
|
71
|
+
this.link.stop()
|
|
72
|
+
next()
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
(next) => {
|
|
76
|
+
this.server.close(next)
|
|
77
|
+
}
|
|
78
|
+
], (err) => {
|
|
79
|
+
if (err) {
|
|
80
|
+
reject(err)
|
|
81
|
+
cb(err)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
resolve()
|
|
86
|
+
cb(null)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
listen (port, cb) {
|
|
92
|
+
const stubs = this.stubs
|
|
93
|
+
|
|
94
|
+
// its just tests, so lets use some async/await
|
|
95
|
+
this.server = http.createServer(async (req, res) => {
|
|
96
|
+
const buf = await getRawBody(req)
|
|
97
|
+
const parsed = JSON.parse(buf.toString())
|
|
98
|
+
|
|
99
|
+
const [id, service, data] = parsed
|
|
100
|
+
if (!stubs[service]) {
|
|
101
|
+
throw new Error('ERR_TEST_MISSING_STUB')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!stubs[service][data.action]) {
|
|
105
|
+
throw new Error('ERR_TEST_MISSING_STUB_ACTION')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function reply (err, data) {
|
|
109
|
+
if (err) {
|
|
110
|
+
res.end(
|
|
111
|
+
`["${id}","ERR_API_BASE: ${err.message}",null]`
|
|
112
|
+
)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const d = JSON.stringify([id, null, data])
|
|
117
|
+
res.end(d)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
data.args.push(reply)
|
|
121
|
+
const args = [{}, ...data.args]
|
|
122
|
+
|
|
123
|
+
stubs[service][data.action].apply(stubs[service], args)
|
|
124
|
+
}).listen(port, cb)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
stopAnnounces (stubs) {
|
|
128
|
+
Object.keys(stubs).map((el) => {
|
|
129
|
+
this.link.stopAnnouncing(el, this.port)
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
announce (stubs, cb) {
|
|
134
|
+
const tasks = Object.keys(stubs).map((el) => {
|
|
135
|
+
return (cb) => {
|
|
136
|
+
this.link.startAnnouncing(el, this.port, cb)
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
async.parallel(tasks, cb)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function fauxGrenache (stubs, grapes, opts) {
|
|
145
|
+
return new FauxGrenache(stubs, grapes, opts)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = fauxGrenache
|
package/grapes.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Grape } = require('grenache-grape')
|
|
4
|
+
const waterfall = require('async/waterfall')
|
|
5
|
+
const EventEmitter = require('events')
|
|
6
|
+
|
|
7
|
+
class Grapes extends EventEmitter {
|
|
8
|
+
constructor (opts = {}) {
|
|
9
|
+
super()
|
|
10
|
+
|
|
11
|
+
this.opts = opts
|
|
12
|
+
this.grapes = this.addDefaultGrapes()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
start (cb = () => {}) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const tasks = this.grapes.map((grape, i) => {
|
|
18
|
+
return (cb) => {
|
|
19
|
+
if (i === 0) grape.once('ready', cb)
|
|
20
|
+
else grape.once('node', cb)
|
|
21
|
+
|
|
22
|
+
grape.start()
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
waterfall(tasks, (err) => {
|
|
27
|
+
if (err) {
|
|
28
|
+
this.emit('error')
|
|
29
|
+
reject(err)
|
|
30
|
+
return cb(err)
|
|
31
|
+
}
|
|
32
|
+
this.emit('ready')
|
|
33
|
+
resolve()
|
|
34
|
+
cb(null)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onAnnounce (serviceName, cb = () => {}) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
if (typeof serviceName === 'function') {
|
|
42
|
+
this.onAnnounce(null, serviceName)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!this.grapes[0]) {
|
|
47
|
+
this.on('ready', () => {
|
|
48
|
+
this.onAnnounce(serviceName, cb)
|
|
49
|
+
})
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!serviceName) {
|
|
54
|
+
cb(null)
|
|
55
|
+
resolve()
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this._checkAnnounce(serviceName, () => {
|
|
60
|
+
resolve()
|
|
61
|
+
cb(null)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_checkAnnounce (serviceName, cb) {
|
|
67
|
+
this.grapes[0].once('announce', (name) => {
|
|
68
|
+
if (serviceName === name) {
|
|
69
|
+
return cb(null)
|
|
70
|
+
}
|
|
71
|
+
this._checkAnnounce(serviceName, cb)
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
addDefaultGrapes () {
|
|
76
|
+
const { ports } = this.opts
|
|
77
|
+
const _ports = Array.isArray(ports)
|
|
78
|
+
? ports
|
|
79
|
+
: [{
|
|
80
|
+
dht_port: 20002,
|
|
81
|
+
dht_bootstrap: ['127.0.0.1:20001'],
|
|
82
|
+
api_port: 40001
|
|
83
|
+
}, {
|
|
84
|
+
dht_port: 20001,
|
|
85
|
+
dht_bootstrap: ['127.0.0.1:20002'],
|
|
86
|
+
api_port: 30001
|
|
87
|
+
}]
|
|
88
|
+
|
|
89
|
+
return _ports
|
|
90
|
+
.map((ports) => new Grape(ports))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
stop (cb = () => {}) {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const tasks = this.grapes.map((grape) => {
|
|
96
|
+
return (cb) => {
|
|
97
|
+
grape.stop(cb)
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
waterfall(tasks, (err) => {
|
|
102
|
+
if (err) {
|
|
103
|
+
reject(err)
|
|
104
|
+
return cb(err)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
resolve()
|
|
108
|
+
cb(null)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createGrapes (opts) {
|
|
115
|
+
return new Grapes(opts)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = createGrapes
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bitfinex/bfx-svc-test-helper",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "common test functions",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "standard"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/bitfinexcom/bfx-svc-test-helper.git"
|
|
12
|
+
},
|
|
13
|
+
"author": "Robert <robert@bitfinex.com>",
|
|
14
|
+
"license": "Apache-2.0",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"node-fetch": "^2.6.9",
|
|
17
|
+
"standard": "^17.0.0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@bitfinex/bfx-svc-boot-js": "git+https://github.com/bitfinexcom/bfx-svc-boot-js.git",
|
|
21
|
+
"async": "3.2.6",
|
|
22
|
+
"grenache-grape": "1.0.0",
|
|
23
|
+
"grenache-nodejs-http": "1.0.1",
|
|
24
|
+
"grenache-nodejs-link": "1.0.2",
|
|
25
|
+
"raw-body": "2.5.2"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const http = require('http')
|
|
4
|
+
const async = require('async')
|
|
5
|
+
|
|
6
|
+
class Mocks {
|
|
7
|
+
constructor (routes, opts) {
|
|
8
|
+
this.routes = this.parseRouting(routes)
|
|
9
|
+
this.debug = opts.debug
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
parseRouting (routes) {
|
|
13
|
+
if (!Array.isArray(routes)) {
|
|
14
|
+
routes = [routes]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
routes = routes.map((route) => {
|
|
18
|
+
if (route.route && route.match) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'shorthand .route must be used without .match'
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (route.route) {
|
|
25
|
+
route.match = (data, match) => {
|
|
26
|
+
match({ route: route.route })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (typeof route.reply !== 'function') {
|
|
31
|
+
const msg = route.reply
|
|
32
|
+
route.reply = (reply) => {
|
|
33
|
+
reply(msg, 200)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return route
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return routes
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getMock (req, res, data, cb) {
|
|
44
|
+
const match = (route, cb) => {
|
|
45
|
+
route.match(data, (filters) => {
|
|
46
|
+
if (filters.route && filters.route !== req.url) {
|
|
47
|
+
return cb(null, false)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (filters.method && filters.method !== req.method) {
|
|
51
|
+
return cb(null, false)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!filters.custom) {
|
|
55
|
+
filters.custom = (req, res, cb) => {
|
|
56
|
+
return cb(null, true)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
filters.custom(req, res, (err, res) => {
|
|
61
|
+
if (err) return cb(err)
|
|
62
|
+
if (res === false) return cb(null, false)
|
|
63
|
+
|
|
64
|
+
if (!filters.payload) return cb(null, res)
|
|
65
|
+
|
|
66
|
+
filters.payload(data, cb)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async.filter(this.routes, match, cb)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
stubReply (req, res, mock) {
|
|
75
|
+
mock.reply((data, code) => {
|
|
76
|
+
if (code) {
|
|
77
|
+
res.statusCode = code
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof data === 'object') {
|
|
81
|
+
data = JSON.stringify(data)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
res.end(data)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
handle (req, res) {
|
|
89
|
+
const data = []
|
|
90
|
+
req.on('data', (buf) => {
|
|
91
|
+
data.push(buf)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
req.on('end', () => {
|
|
95
|
+
const str = Buffer.concat(data).toString()
|
|
96
|
+
|
|
97
|
+
this.debug && console.log('[DEBUG] bfx-svc-test-helper:', str)
|
|
98
|
+
|
|
99
|
+
let parsed
|
|
100
|
+
try {
|
|
101
|
+
parsed = JSON.parse(str)
|
|
102
|
+
} catch (e) {
|
|
103
|
+
parsed = str
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.getMock(req, res, parsed, (err, matches) => {
|
|
107
|
+
if (err) {
|
|
108
|
+
console.error('bfx-svc-test-helper: route matching error')
|
|
109
|
+
throw err
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (matches.length === 0) {
|
|
113
|
+
throw new Error(`bfx-svc-test-helper: no route matched (${req.url})`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (matches.length > 1) {
|
|
117
|
+
throw new Error(`bfx-svc-test-helper: multiple routes matched (${req.url})`)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const mock = matches[0]
|
|
121
|
+
if (!mock.test) mock.test = (req, res, payload, cb) => { cb(null) }
|
|
122
|
+
|
|
123
|
+
mock.test(req, res, parsed, (err) => {
|
|
124
|
+
if (err) {
|
|
125
|
+
console.error('bfx-svc-test-helper: client request test failed')
|
|
126
|
+
throw err
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.stubReply(req, res, mock)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class Server {
|
|
137
|
+
constructor (mocks, opts) {
|
|
138
|
+
this.opts = opts
|
|
139
|
+
this.mocks = mocks
|
|
140
|
+
|
|
141
|
+
this.server = null
|
|
142
|
+
this.port = this.opts.port
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
start (cb = () => {}) {
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
this.server = http.createServer(this.mocks.handle.bind(this.mocks)).listen(this.port, () => {
|
|
148
|
+
resolve()
|
|
149
|
+
cb(null)
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
stop (cb = () => {}) {
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
this.server.close(() => {
|
|
157
|
+
resolve()
|
|
158
|
+
cb(null)
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function createServer (mocks, opts) {
|
|
165
|
+
const m = typeof mocks === 'function' ? mocks() : new Mocks(mocks, opts)
|
|
166
|
+
return new Server(m, opts)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = createServer
|
package/utils.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
exports.getApi = getApi
|
|
4
|
+
function getApi (instance) {
|
|
5
|
+
return instance.worker.api_bfx.api
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
exports.getGrapeApiPort = getGrapeApiPort
|
|
9
|
+
function getGrapeApiPort (grapeHelperInstance) {
|
|
10
|
+
return grapeHelperInstance.grapes[0].conf.api_port
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
exports.getGrapeApiUrl = getGrapeApiUrl
|
|
14
|
+
function getGrapeApiUrl (grapes) {
|
|
15
|
+
if (typeof grapes === 'string') {
|
|
16
|
+
return grapes
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const p = getGrapeApiPort(grapes)
|
|
20
|
+
return 'http://127.0.0.1:' + p
|
|
21
|
+
}
|
package/worker.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const worker = require('@bitfinex/bfx-svc-boot-js/lib/worker')
|
|
4
|
+
|
|
5
|
+
class Worker {
|
|
6
|
+
constructor (conf, grapes) {
|
|
7
|
+
this.worker = null
|
|
8
|
+
this.grenacheConf = null
|
|
9
|
+
|
|
10
|
+
this.conf = conf
|
|
11
|
+
this.grapes = grapes
|
|
12
|
+
this.name = null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
start (cb = () => {}) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
this.worker = worker(this.conf)
|
|
18
|
+
|
|
19
|
+
if (!this.grapes) {
|
|
20
|
+
resolve()
|
|
21
|
+
cb(null)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!this.worker.getGrcConf) {
|
|
26
|
+
resolve()
|
|
27
|
+
return cb(null)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.grenacheConf = this.worker.getGrcConf()
|
|
31
|
+
|
|
32
|
+
if (!this.grenacheConf.services) {
|
|
33
|
+
resolve()
|
|
34
|
+
return cb(null)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const [service] = this.grenacheConf.services
|
|
38
|
+
this.name = service
|
|
39
|
+
this.grapes.onAnnounce(service, () => {
|
|
40
|
+
resolve()
|
|
41
|
+
cb(null)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
stop (cb = () => {}) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
this.worker.stop((err) => {
|
|
49
|
+
if (err) {
|
|
50
|
+
reject(err)
|
|
51
|
+
cb(err)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
cb(null)
|
|
56
|
+
resolve()
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createWorker (opts, grapes) {
|
|
63
|
+
return new Worker(opts, grapes)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = createWorker
|