Skip to content

Instantly share code, notes, and snippets.

@nickknissen
Created October 2, 2013 08:59
Show Gist options
  • Save nickknissen/6790962 to your computer and use it in GitHub Desktop.
Save nickknissen/6790962 to your computer and use it in GitHub Desktop.
/*
* Middleware to mock GoCardless API requests
*/
'use strict';
var url = require('url');
var _ = require('lodash');
var db = require('./database.js');
var POST = 'POST';
var PUT = 'PUT';
var PARAM_EXTRACT_BLACKLIST = ['/api/applied_plans'];
// Some POST data looks like: { endpoint_name: {actual: 'params'} }
// This function is used to extract the contents of `endpoint_name` if necessary
function extractParams(dataType, params) {
if (_.contains(PARAM_EXTRACT_BLACKLIST, dataType)) {
return params;
} else {
return params[Object.keys(params)[0]];
}
}
// Set response headers if any are passed
function setHeaders(res, headers) {
if (_.isObject(headers)) {
Object.keys(headers).forEach(function(key) {
res.setHeader(key, JSON.stringify(headers[key]));
});
}
}
module.exports = function(req, res, next) {
var pathName = url.parse(req.url).pathname;
if (!pathName.match(/\/api/)) return next();
var method = req.method;
var response = db.find(pathName);
var params;
// If it's a POST, store the attributes to be returned on the next GET.
if (method === POST) {
params = extractParams(pathName, req.body);
response = db.create(pathName, params);
}
// If it's a PUT, update the attributes to be returned on the next GET.
if (method === PUT) {
params = extractParams(pathName, req.body);
response = db.update(pathName, params);
}
if (response == null) {
res.writeHead(404, 'API mock not found');
return res.end();
}
setHeaders(res, response.headers);
res.end(JSON.stringify(response.body));
};
'use strict';
var _ = require('lodash');
// Initial data - contains data required by every spec, e.g. user.
var data = {
'/api/features': {
headers: {},
body: {}
},
'/api/user': {
headers: {},
body: {
id: '123ABC',
authorized: true,
developer_enabled: true,
email: '[email protected]',
first_name: 'Barry',
last_name: 'White'
}
},
'/api/settings/merchant': {
headers: {},
body: {
use_custom_reference: true
}
}
};
function deepClone(input) {
return JSON.parse(JSON.stringify(input));
}
// Store a clone of the initial data to allow resetting.
var originalData = deepClone(data);
module.exports = {
find: function(url) {
return data[url];
},
// Store POSTed data and add a created_at attribute.
create: function(url, requestData) {
requestData = requestData || {};
requestData.created_at = new Date();
var dataSet = data[url];
// Create a db object if it doesn't exist
if (dataSet == null) {
dataSet = {
headers: {},
body: []
};
data[url] = dataSet;
}
dataSet.body.push(requestData);
return data;
},
// Find and update PUTed records.
update: function(url, requestdata) {
var dataSet = data[url] && data[url].body;
// If there's no dataset, try to find one based on the /route/:id pattern
if (dataSet == null) {
var splitUrl = url.split('/');
var id = splitUrl[splitUrl.length - 1];
var urlBase = splitUrl.slice(0, -1).join('/');
dataSet = data[urlBase].body;
}
// Search the dataSet array for the matching record.
if (_.isArray(dataSet)) {
dataSet = _.find(dataSet, function(record) {
return record.id === id;
});
}
// Update the found record and return the new reppresentation.
return _.extend(dataSet, requestdata);
},
// TODO: probably need to handle DELETE /object/:id
destroy: function(url) {
var dataSet = data[url].body;
data[url] = (_.isArray(dataSet) ? [] : {});
},
// Add a new mock, overwriting the old one if it existed
mock: function(url, body, headers) {
data[url] = {
body: body,
headers: headers || {}
};
},
// Set the data store back to pristine state and reclone originalData
reset: function() {
data = originalData;
originalData = deepClone(originalData);
}
};
'use strict';
var _ = require('lodash');
var db = require('../../config/integration/database.js');
var glob = require('glob');
var factories = {};
function getFactory(type) {
var factory = factories[type];
if (factory == null) {
throw new Error('Factory ' + type + ' is not defined');
}
return factory;
}
function define(type, url, attrs) {
if (factories[type]) {
throw new Error('Factory ' + type + ' is already defined');
}
var factory = factories[type] = {
url: url,
attrs: attrs
};
return factory;
}
function build(type) {
var factory = getFactory(type);
return _.chain(factory.attrs).map(function(val, key) {
// Return a [key, value] tuple which we can call `object` on later
return [key, (_.isFunction(val) ? val() : val)];
}).object().value();
}
function create(type) {
var data = build(type);
var url = getFactory(type).url;
db.create(url, data);
return data;
}
module.exports = {
define: define,
create: create,
build: build
};
// Bootstrap - reuire all factories which each call to factory.define
glob.sync(__dirname + '/**-factory.js').forEach(require);
// An example configuration file.
exports.config = {
// ----- How to setup Selenium
// There are three ways to specify how to use Selenium. Specify one of the
// following:
// 1. seleniumServerJar - to start Selenium Standalone locally.
// 2. seleniumAddress - to connect to a Selenium server which is already
// running.
// 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs.
// The location of the selenium standalone server .jar file.
seleniumServerJar: './selenium/selenium-server-standalone-2.33.0.jar',
// The port to start the selenium server on, or null if the server should
// find its own unused port.
seleniumPort: 4321,
// Chromedriver location is used to help the selenium standalone server
// find chromedriver. This will be passed to the selenium jar as
// the system property webdriver.chrome.driver. If null, selenium will
// attempt to find chromedriver using PATH.
chromeDriver: './selenium/chromedriver',
// Additional command line options to pass to selenium. For example,
// if you need to change the browser timeout, use
// seleniumArgs: [-browserTimeout=60],
seleniumArgs: [],
// If sauceUser and sauceKey are specified, seleniumServerJar will be ignored.
// sauceUser: null,
// sauceKey: null,
// The address of a running selenium server.
// seleniumAddress: 'http://localhost:4444/wd/hub',
// Spec patterns are relative to the current working directly when
// protractor is called.
specs: ['./client/spec/config/protractor.init.js'],
// ----- Capabilities to be passed to the webdriver instance.
// For a full list of available capabilities, see
// https://code.google.com/p/selenium/wiki/DesiredCapabilities
capabilities: {
'browserName': 'chrome'
},
// A base URL for your application under test. Calls to protractor.get()
// with relative paths will be prepended with this.
baseUrl: 'http://localhost:4003',
// ----- Options to be passed to minijasminenode.
jasmineNodeOpts: {
// onComplete will be called before the driver quits.
onComplete: null,
isVerbose: false,
showColors: true,
includeStackTrace: true
}
};
/*
* Protractor Bootstrap - configure protractor globals and require all spec
* files.
*/
'use strict';
var url = require('url');
var path = require('path');
var glob = require('glob');
var connect = require('connect');
var config = require('./protractor.conf.js').config;
var middleware = require('./protractor.server.js');
// Run test server in same process as specs
var port = url.parse(config.baseUrl).port;
var server = connect();
middleware = middleware(connect);
middleware.forEach(server.use.bind(server));
server.listen(port);
// Export protractor globals to use in spec files
global.protractor = require('protractor');
global.ptor = global.protractor.getInstance();
require('protractor/jasminewd');
var specPath = path.join(process.cwd(), 'client', 'spec', 'integration');
// Require and run helpers
var helperPath = path.join(specPath, 'helpers');
glob.sync(helperPath + '/**/*.js').forEach(require);
// Require (and run) all integration specs
glob.sync(specPath + '/**/*spec.js').forEach(require);
'use strict';
/*
* This file defines a set of connect middleware to build an integration test
* server.
* 1. Serve static files
* 2. Concatenate and serve the application files
* 3. Serve the index.html to all Angular-looking requests
*/
var fs = require('fs');
var path = require('path');
var mockApi = require('./integration/api.js');
var publicPath = path.join(process.cwd(), 'public');
module.exports = function(connect /*, options*/) {
return [
connect.json(),
mockApi,
connect.static(publicPath),
// Serve index for Angular routes
function(req, res, next) {
// Fall through to 404 if there's a path extension.
// Our Angular routes don't have this so it must be a request for a
// resource that's not there.
if (path.extname(req.url) !== '') { return next(); }
// Else render the index.html and let our router handle it.
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
var indexPath = path.join(publicPath, 'app-index.html');
fs.createReadStream(indexPath).pipe(res);
}
];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment