'use strict' const common = require('./common-tap.js') const Bluebird = require('bluebird') const silentLogger = { http: () => {}, silly: () => {} } const log = process.env.TAP_CHILD_ID ? silentLogger : require('npmlog') const http = require('http') const EventEmitter = require('events') // See fake-registry.md for details class FakeRegistry extends EventEmitter { constructor (opts) { if (!opts) opts = {} super(opts) this.mocks = {} this.port = opts.port || common.port this.registry = 'http://localhost:' + this.port this.server = http.createServer() if (!opts.keepNodeAlive) this.server.unref() this.server.on('request', (req, res) => { if (this.mocks[req.method] && this.mocks[req.method][req.url]) { this.mocks[req.method][req.url](req, res) } else { res.statusCode = 404 res.end(JSON.stringify({error: 'not found'})) } log.http('fake-registry', res.statusCode || 'unknown', '→', req.method, req.url) }) this._error = err => { log.silly('fake-registry', err) this.emit('error', err) } this._addErrorHandler() } reset () { this.mocks = {} return this } close () { this.reset() this._removeErrorHandler() return new Promise((resolve, reject) => { this.server.once('error', reject) this.server.once('close', () => { this.removeListener('error', reject) resolve(this) }) this.server.close() }) } _addErrorHandler () { this.server.on('error', this._error) } _removeErrorHandler () { if (!this._error) return this.server.removeListener('error', this._error) } listen (cb) { this._removeErrorHandler() return this._findPort(this.port).then(port => { this._addErrorHandler() this.port = port this.registry = 'http://localhost:' + port common.port = this.port common.registry = this.registry return this }).asCallback(cb) } _findPort (port) { return new Bluebird((resolve, reject) => { let onListening const onError = err => { this.server.removeListener('listening', onListening) if (err.code === 'EADDRINUSE') { return resolve(this._findPort(++port)) } else { return reject(err) } } onListening = () => { this.server.removeListener('error', onError) resolve(port) } this.server.once('error', onError) this.server.once('listening', onListening) this.server.listen(port) }) } mock (method, url, respondWith) { log.http('fake-registry', 'mock', method, url, respondWith) if (!this.mocks[method]) this.mocks[method] = {} if (typeof respondWith === 'function') { this.mocks[method][url] = respondWith } else if (Array.isArray(respondWith)) { const [status, body] = respondWith this.mocks[method][url] = (req, res) => { res.statusCode = status if (typeof body === 'object') { res.end(JSON.stringify(body)) } else { res.end(String(body)) } } } else { throw new Error('Invalid args, expected: mr.mock(method, url, [status, body])') } return this } // compat done () { this.reset() } filteringRequestBody () { return this } post (url, matchBody) { return this._createReply('POST', url) } get (url) { return this._createReply('GET', url) } put (url, matchBody) { return this._createReply('PUT', url) } delete (url) { return this._createReply('DELETE', url) } _createReply (method, url) { const mr = this return { twice: function () { return this }, reply: function (status, responseBody) { mr.mock(method, url, [status, responseBody]) return mr } } } } module.exports = new FakeRegistry() module.exports.FakeRegistry = FakeRegistry module.exports.compat = function (opts, cb) { if (arguments.length === 1 && typeof opts === 'function') { cb = opts opts = {} } return new FakeRegistry(Object.assign({keepNodeAlive: true}, opts || {})).listen(cb) }