This repository has been archived by the owner on Apr 3, 2019. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(server): exit if db patch level is wrong
- Loading branch information
Showing
5 changed files
with
318 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
const assert = require('insist'); | ||
const sinon = require('sinon'); | ||
const mocks = require('../lib/mocks'); | ||
|
||
const modulePath = '../../lib/db/mysql'; | ||
|
||
var instances = {}; | ||
var dependencies = mocks.require([ | ||
{ path: 'buf' }, | ||
{ path: 'mysql' }, | ||
{ path: 'mysql-patcher', ctor: function() { return instances.patcher; } }, | ||
{ path: '../../config' }, | ||
{ path: '../../encrypt' }, | ||
{ path: '../../logging', ctor: function() { return instances.logger; } }, | ||
{ path: '../../scope' }, | ||
{ path: '../../unique', ctor: function() { return instances.scope; } }, | ||
{ path: './patch' } | ||
], modulePath, __dirname); | ||
|
||
function nop() { | ||
} | ||
|
||
function callback(cb) { | ||
cb(); | ||
} | ||
|
||
process.setMaxListeners(0); | ||
|
||
describe('db/mysql:', function() { | ||
var sandbox, mysql; | ||
|
||
beforeEach(function() { | ||
sandbox = sinon.sandbox.create(); | ||
|
||
sandbox.stub(dependencies['../../config'], 'get', function() { | ||
return 'mock config.get result'; | ||
}); | ||
instances.logger = { | ||
info: nop, | ||
debug: nop, | ||
warn: nop, | ||
error: nop, | ||
verbose: nop | ||
}; | ||
Object.keys(instances.logger).forEach(function(methodName) { | ||
sandbox.spy(instances.logger, methodName); | ||
}); | ||
|
||
mocks.register(dependencies, modulePath, __dirname); | ||
|
||
mysql = require(modulePath); | ||
}); | ||
|
||
afterEach(function() { | ||
sandbox.restore(); | ||
mocks.deregister(); | ||
}); | ||
|
||
it('exports a connect function', function() { | ||
assert.equal(typeof mysql.connect, 'function'); | ||
}); | ||
|
||
describe('connect:', function() { | ||
beforeEach(function() { | ||
instances.patcher = { | ||
connect: nop, | ||
patch: nop, | ||
readDbPatchLevel: nop, | ||
end: nop, | ||
currentPatchLevel: dependencies['./patch'].level | ||
}; | ||
sandbox.stub(instances.patcher, 'connect', callback); | ||
sandbox.stub(instances.patcher, 'patch', nop); | ||
sandbox.stub(instances.patcher, 'end', callback); | ||
sandbox.stub(process, 'exit', nop); | ||
}); | ||
|
||
describe('readDbPatchLevel succeeds:', function() { | ||
beforeEach(function() { | ||
sandbox.stub(instances.patcher, 'readDbPatchLevel', function(callback) { | ||
callback(); | ||
}); | ||
}); | ||
|
||
describe('db patch level is okay:', function() { | ||
var result; | ||
|
||
beforeEach(function() { | ||
return mysql.connect({}).then(function(r) { | ||
result = r; | ||
}); | ||
}); | ||
|
||
it('called patcher.connect', function() { | ||
assert.equal(instances.patcher.connect.callCount, 1); | ||
var args = instances.patcher.connect.getCall(0).args; | ||
assert.equal(args.length, 1); | ||
assert.equal(typeof args[0], 'function'); | ||
assert.equal(args[0].length, 2); | ||
}); | ||
|
||
it('did not call patcher.patch', function() { | ||
assert.equal(instances.patcher.patch.callCount, 0); | ||
}); | ||
|
||
it('called patcher.readDbPatchLevel', function() { | ||
assert.equal(instances.patcher.readDbPatchLevel.callCount, 1); | ||
var args = instances.patcher.readDbPatchLevel.getCall(0).args; | ||
assert.equal(args.length, 1); | ||
assert.equal(typeof args[0], 'function'); | ||
assert.equal(args[0].length, 1); | ||
}); | ||
|
||
it('called patcher.end', function() { | ||
assert.equal(instances.patcher.end.callCount, 1); | ||
var args = instances.patcher.end.getCall(0).args; | ||
assert.equal(args.length, 1); | ||
assert.equal(typeof args[0], 'function'); | ||
assert.equal(args[0].length, 2); | ||
}); | ||
|
||
it('did not call logger.error', function() { | ||
assert.equal(instances.logger.error.callCount, 0); | ||
}); | ||
|
||
it('did not call process.exit', function() { | ||
assert.equal(process.exit.callCount, 0); | ||
}); | ||
|
||
it('returned an object', function () { | ||
assert.equal(typeof result, 'object'); | ||
assert.notEqual(result, null); | ||
}); | ||
}); | ||
|
||
describe('db patch level is bad:', function() { | ||
beforeEach(function() { | ||
instances.patcher.currentPatchLevel += 2; | ||
return mysql.connect({}); | ||
}); | ||
|
||
afterEach(function() { | ||
instances.patcher.currentPatchLevel -= 2; | ||
}); | ||
|
||
it('called patcher.end', function() { | ||
assert.equal(instances.patcher.end.callCount, 1); | ||
}); | ||
|
||
it('called logger.error', function() { | ||
assert.equal(instances.logger.error.callCount, 1); | ||
var args = instances.logger.error.getCall(0).args; | ||
assert.equal(args.length, 2); | ||
assert.equal(args[0], 'checkDbPatchLevel'); | ||
assert(args[1] instanceof Error); | ||
assert.equal(args[1].message, 'unexpected db patch level: ' + (dependencies['./patch'].level + 2)); | ||
}); | ||
|
||
it('called process.exit', function() { | ||
assert.equal(process.exit.callCount, 1); | ||
var args = process.exit.getCall(0).args; | ||
assert.equal(args.length, 1); | ||
assert.equal(args[0], 1); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('readDbPatchLevel fails:', function() { | ||
beforeEach(function() { | ||
sandbox.stub(instances.patcher, 'readDbPatchLevel', function(callback) { | ||
callback('foo'); | ||
}); | ||
return mysql.connect({}); | ||
}); | ||
|
||
it('called patcher.end', function() { | ||
assert.equal(instances.patcher.end.callCount, 1); | ||
}); | ||
|
||
it('called logger.error', function() { | ||
assert.equal(instances.logger.error.callCount, 1); | ||
var args = instances.logger.error.getCall(0).args; | ||
assert.equal(args[0], 'checkDbPatchLevel'); | ||
assert.equal(args[1], 'foo'); | ||
}); | ||
|
||
it('called process.exit', function() { | ||
assert.equal(process.exit.callCount, 1); | ||
var args = process.exit.getCall(0).args; | ||
assert.equal(args.length, 1); | ||
assert.equal(args[0], 1); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
||
const path = require('path'); | ||
const proxyquire = require('proxyquire'); | ||
const m = require('module'); | ||
|
||
var moduleCache; | ||
|
||
module.exports = { | ||
require: requireDependencies, | ||
register: registerDependencies, | ||
deregister: deregisterDependencies | ||
}; | ||
|
||
// `mocks.require` | ||
// | ||
// Require dependencies using the same path that is specifed in the module | ||
// under test. | ||
// | ||
// Returns an object containing dependencies keyed by their path. | ||
// | ||
// Expects three arguments; `dependencies`, `modulePath` and `basePath`. | ||
// | ||
// dependencies: Array of { path, ctor } items. | ||
// path: The dependency path, as specified in the module | ||
// under test. | ||
// ctor: Optional. If the dependency is a constructor for | ||
// an instance that you wish to mock, set this to a | ||
// function that returns your mock instance. | ||
// modulePath: The relative path to the module under test. | ||
// basePath: The base path, i.e. __dirname for the test itself. | ||
function requireDependencies(dependencies, modulePath, basePath) { | ||
var result = {}; | ||
|
||
dependencies.forEach(function (dependency) { | ||
result[dependency.path] = requireDependency(dependency, modulePath, basePath); | ||
}); | ||
|
||
return result; | ||
} | ||
|
||
function requireDependency(dependency, modulePath, basePath) { | ||
if (typeof dependency.ctor === 'function') { | ||
return dependency.ctor; | ||
} | ||
|
||
var localPath = dependency.path; | ||
|
||
if (localPath[0] === '.') { | ||
localPath = path.relative( | ||
basePath, | ||
path.resolve(basePath, modulePath, localPath) | ||
); | ||
} | ||
|
||
return require(localPath); | ||
} | ||
|
||
// `mocks.register` | ||
// | ||
// Register mock dependencies, fixing paths as we go so that it works | ||
// with the blanket coverage tool (which rewrites require paths in the | ||
// instrumented code). You should call this function inside beforeEach. | ||
// | ||
// Expects three arguments; `dependencies`, `modulePath` and `basePath`. | ||
// | ||
// dependencies: An object, where keys are dependency paths and values | ||
// are mock objects. This argument is typically the return | ||
// value from `mocks.require`, modified by sinon for your | ||
// tests. | ||
// modulePath: The relative path to the module under test. | ||
// basePath: The base path, i.e. __dirname for the test itself. | ||
function registerDependencies(dependencies, modulePath, basePath) { | ||
var instrumentedDependencies = {}; | ||
|
||
clearModuleCache(); | ||
|
||
Object.keys(dependencies).forEach(function(dependencyPath) { | ||
var instrumentedPath = getInstrumentedPath(dependencyPath, modulePath, basePath); | ||
instrumentedDependencies[instrumentedPath] = dependencies[dependencyPath]; | ||
}); | ||
|
||
proxyquire(modulePath, instrumentedDependencies); | ||
} | ||
|
||
function clearModuleCache() { | ||
moduleCache = m._cache; | ||
m._cache = {}; | ||
} | ||
|
||
function getInstrumentedPath(dependencyPath, modulePath, basePath) { | ||
if (dependencyPath[0] !== '.') { | ||
return dependencyPath; | ||
} | ||
|
||
return path.resolve(basePath, modulePath) + '/' + dependencyPath; | ||
} | ||
|
||
// `mocks.deregister` | ||
// | ||
// Deregister mock dependencies. You should call this function | ||
// inside afterEach. | ||
function deregisterDependencies() { | ||
if (moduleCache) { | ||
m._cache = moduleCache; | ||
moduleCache = null; | ||
} | ||
} | ||
|