• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2const common = require('./common-tap.js')
3const Bluebird = require('bluebird')
4const silentLogger = {
5  http: () => {},
6  silly: () => {}
7}
8
9const log = process.env.TAP_CHILD_ID
10  ? silentLogger
11  : require('npmlog')
12
13const http = require('http')
14const EventEmitter = require('events')
15// See fake-registry.md for details
16
17class FakeRegistry extends EventEmitter {
18  constructor (opts) {
19    if (!opts) opts = {}
20    super(opts)
21    this.mocks = {}
22    this.port = opts.port || common.port
23    this.registry = 'http://localhost:' + this.port
24    this.server = http.createServer()
25    if (!opts.keepNodeAlive) this.server.unref()
26    this.server.on('request', (req, res) => {
27      if (this.mocks[req.method] && this.mocks[req.method][req.url]) {
28        this.mocks[req.method][req.url](req, res)
29      } else {
30        res.statusCode = 404
31        res.end(JSON.stringify({error: 'not found'}))
32      }
33      log.http('fake-registry', res.statusCode || 'unknown', '→', req.method, req.url)
34    })
35    this._error = err => {
36      log.silly('fake-registry', err)
37      this.emit('error', err)
38    }
39    this._addErrorHandler()
40  }
41  reset () {
42    this.mocks = {}
43    return this
44  }
45  close () {
46    this.reset()
47    this._removeErrorHandler()
48    return new Promise((resolve, reject) => {
49      this.server.once('error', reject)
50      this.server.once('close', () => {
51        this.removeListener('error', reject)
52        resolve(this)
53      })
54      this.server.close()
55    })
56  }
57  _addErrorHandler () {
58    this.server.on('error', this._error)
59  }
60  _removeErrorHandler () {
61    if (!this._error) return
62    this.server.removeListener('error', this._error)
63  }
64  listen (cb) {
65    this._removeErrorHandler()
66    return this._findPort(this.port).then(port => {
67      this._addErrorHandler()
68      this.port = port
69      this.registry = 'http://localhost:' + port
70      common.port = this.port
71      common.registry = this.registry
72      return this
73    }).asCallback(cb)
74  }
75  _findPort (port) {
76    return new Bluebird((resolve, reject) => {
77      let onListening
78      const onError = err => {
79        this.server.removeListener('listening', onListening)
80        if (err.code === 'EADDRINUSE') {
81          return resolve(this._findPort(++port))
82        } else {
83          return reject(err)
84        }
85      }
86      onListening = () => {
87        this.server.removeListener('error', onError)
88        resolve(port)
89      }
90      this.server.once('error', onError)
91      this.server.once('listening', onListening)
92      this.server.listen(port)
93    })
94  }
95
96  mock (method, url, respondWith) {
97    log.http('fake-registry', 'mock', method, url, respondWith)
98    if (!this.mocks[method]) this.mocks[method] = {}
99    if (typeof respondWith === 'function') {
100      this.mocks[method][url] = respondWith
101    } else if (Array.isArray(respondWith)) {
102      const [status, body] = respondWith
103      this.mocks[method][url] = (req, res) => {
104        res.statusCode = status
105        if (typeof body === 'object') {
106          res.end(JSON.stringify(body))
107        } else {
108          res.end(String(body))
109        }
110      }
111    } else {
112      throw new Error('Invalid args, expected: mr.mock(method, url, [status, body])')
113    }
114    return this
115  }
116
117  // compat
118  done () {
119    this.reset()
120  }
121  filteringRequestBody () {
122    return this
123  }
124  post (url, matchBody) {
125    return this._createReply('POST', url)
126  }
127  get (url) {
128    return this._createReply('GET', url)
129  }
130  put (url, matchBody) {
131    return this._createReply('PUT', url)
132  }
133  delete (url) {
134    return this._createReply('DELETE', url)
135  }
136  _createReply (method, url) {
137    const mr = this
138    return {
139      twice: function () { return this },
140      reply: function (status, responseBody) {
141        mr.mock(method, url, [status, responseBody])
142        return mr
143      }
144    }
145  }
146}
147
148module.exports = new FakeRegistry()
149module.exports.FakeRegistry = FakeRegistry
150module.exports.compat = function (opts, cb) {
151  if (arguments.length === 1 && typeof opts === 'function') {
152    cb = opts
153    opts = {}
154  }
155  return new FakeRegistry(Object.assign({keepNodeAlive: true}, opts || {})).listen(cb)
156}
157