Skip to content

Commit

Permalink
Merge pull request #1 from avik-das/master
Browse files Browse the repository at this point in the history
Add x-sepia-test-name header support
  • Loading branch information
deepankgupta committed Dec 24, 2013
2 parents 27574e7 + 93203bd commit 55dc617
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 5 deletions.
19 changes: 19 additions & 0 deletions README.md
Expand Up @@ -319,6 +319,25 @@ Now, all requests whose URLs match `/my-global-resource/` will be placed in
the root of the configured `fixtureDir`, regardless of what the current test
name is.

### Cassettes Without Modifying Global State

The above approach to VCR cassettes modifies global state in the server managed
by sepia. This prevents running multiple tests--with different test names--in
parallel, because the nature of the global state is such that only one test
name can be set at one time. If you're willing to pass along information from
an incoming request down to a downstream request, sepia provides a stateless
alternative: the `x-sepia-test-name` header.

The `x-sepia-test-name` header, when passed to a downstream request, will
override the globally-configured test name. The header itself is not passed to
any downstream service, nor is the header name used in the calculation of the
fixture name.

The downside is that the server under test must pass along information from the
test integration runner to each of its downstream requests, because otherwise,
sepia has no means of determining the associated test name for a particular
dowstream request.

## Limitations

### Repeated Identical HTTP Requests
Expand Down
44 changes: 43 additions & 1 deletion examples/testName.js
Expand Up @@ -49,9 +49,21 @@ var sepia = require('..');
// 1. Returns a random number.

var httpServer = http.createServer(function(req, res) {
var headers = {
'Content-type': 'text/plain'
};

// One piece of functionality being tested is the use of the
// x-sepia-test-name header, which is not meant to be passed along to
// downstream services. For that reason, we pass that header back to the
// client so that it can test the absence of the header.
if (req.headers['x-sepia-test-name']) {
headers['x-sepia-test-name'] = req.headers['x-sepia-test-name'];
}

// simulate server latency
setTimeout(function() {
res.writeHead(200, { 'Content-type': 'text/plain' });
res.writeHead(200, headers);
res.end(Math.random().toString());
}, 500);
}).listen(1337, '0.0.0.0');
Expand Down Expand Up @@ -120,6 +132,33 @@ function localRequest(next) {
});
}

function requestWithHeader(testName, cacheHitExpected, next) {
var start = Date.now();

request({
url: 'http://localhost:1337/local',
headers: {
'x-sepia-test-name': testName
}
}, function(err, data, body) {
var time = Date.now() - start;

console.log('LOCAL REQUEST');
console.log(' status:', data.statusCode);
console.log(' body :', body);
console.log(' time :', time);

common.verify(function() {
common.shouldUseCache(cacheHitExpected, time);
data.headers.should.not.have.property('x-sepia-test-name');
});

console.log();

next();
});
}

// -- RUN EVERYTHING -----------------------------------------------------------

// To change the test name, we have to be able to access the live server,
Expand All @@ -142,6 +181,9 @@ step(
function() { setTestName('test2', this); },
function() { globalRequest(true, this); },
function() { localRequest(this); },
function() { requestWithHeader('test1', true, this); },
function() { requestWithHeader('test3', false, this); },
function() { requestWithHeader('test3', true, this); },
_.bind(httpServer.close, httpServer),
function() { process.exit(0); }
);
2 changes: 2 additions & 0 deletions src/cache.js
Expand Up @@ -77,6 +77,8 @@ module.exports.configure = function(mode) {
var filename = sepiaUtil.constructFilename(options.method, reqUrl,
reqBody.toString(), options.headers);

options.headers = sepiaUtil.removeInternalHeaders(options.headers);

var forceLive = sepiaUtil.shouldForceLive(reqUrl);

// Only called if either the fixture with the constructed filename
Expand Down
31 changes: 28 additions & 3 deletions src/util.js
Expand Up @@ -129,6 +129,22 @@ function filterByWhitelist(list, whitelist) {
});
}

function removeInternalHeaders(headers) {
if (!headers) {
return;
}

var filtered = {};

for (var key in headers) {
if (key.indexOf('x-sepia-') !== 0) {
filtered[key] = headers[key];
}
}

return filtered;
}

function applyMatchingFilters(reqUrl, reqBody) {
var filteredUrl = reqUrl;
var filteredBody = reqBody;
Expand Down Expand Up @@ -228,6 +244,8 @@ function parseCookiesNames(cookieValue) {
}

function parseHeaderNames(headers) {
headers = removeInternalHeaders(headers);

var headerNames = [];
for (var name in headers) {
if (headers.hasOwnProperty(name)) {
Expand Down Expand Up @@ -269,12 +287,18 @@ function gatherFilenameHashParts(method, reqUrl, reqBody, reqHeaders) {
}

function constructAndCreateFixtureFolder(reqUrl, reqHeaders) {
var language = reqHeaders && reqHeaders['accept-language'] || '';
reqHeaders = reqHeaders || {};

var language = reqHeaders['accept-language'] || '';
language = language.split(',')[0];

var testFolder = '';
if (!usesGlobalFixtures(reqUrl) && globalOptions.testOptions.testName) {
testFolder = globalOptions.testOptions.testName;
if (!usesGlobalFixtures(reqUrl)){
if (reqHeaders['x-sepia-test-name']) {
testFolder = reqHeaders['x-sepia-test-name'];
} else if (globalOptions.testOptions.testName) {
testFolder = globalOptions.testOptions.testName;
}
}

var folder = path.resolve(globalOptions.filenamePrefix, language,
Expand Down Expand Up @@ -328,6 +352,7 @@ module.exports.addFilter = addFilter;
module.exports.constructFilename = constructFilename;
module.exports.urlFromHttpRequestOptions = urlFromHttpRequestOptions;
module.exports.shouldForceLive = shouldForceLive;
module.exports.removeInternalHeaders = removeInternalHeaders;

module.exports.internal = {};
module.exports.internal.globalOptions = globalOptions;
Expand Down
70 changes: 69 additions & 1 deletion test/util.js
Expand Up @@ -12,8 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

require('should');
var should = require('should');
var sinon = require('sinon');
var _ = require('lodash');

var sepiaUtil = require('../src/util');
var fs = require('fs');
Expand Down Expand Up @@ -117,6 +118,41 @@ describe('utils.js', function() {
});
});

describe('#removeInternalHeaders', function() {
const removeInternalHeaders = sepiaUtil.removeInternalHeaders;

it('returns undefined if the input is undefined', function() {
var filtered = removeInternalHeaders();
should.not.exist(filtered);
});

it('filters out only internal headers', function() {
var filtered = removeInternalHeaders({
a: 1,
'x-sepia-internal': 2,
b: 3
});

filtered.should.eql({
a: 1,
b: 3
});
});

it('does not modify the original object', function() {
var original = {
a: 1,
'x-sepia-internal': 2,
b: 3
};

var input = _.cloneDeep(original);

removeInternalHeaders(input);
input.should.eql(original);
});
});

describe('#usesGlobalFixtures', function() {
const usesGlobalFixtures = sepiaUtil.internal.usesGlobalFixtures;

Expand Down Expand Up @@ -429,6 +465,14 @@ describe('utils.js', function() {
a: 3
}).should.eql(['a', 'b']);
});

it('filters out sepia headers', function() {
parseHeaderNames({
b: 1,
'x-sepia-internal-header': 2,
a: 3
}).should.eql(['a', 'b']);
});
});

describe('#gatherFilenameHashParts', function() {
Expand Down Expand Up @@ -563,6 +607,30 @@ describe('utils.js', function() {
folder.should.equal('/global/fixture/dir/en-US/test/name');
});

it('uses the "x-sepia-test-name" header as the test name', function() {
sepiaUtil.setFixtureDir('/global/fixture/dir');
// don't set the test name globally

var folder = constructAndCreateFixtureFolder('my-url', {
'accept-language': 'en-US',
'x-sepia-test-name': 'test/name'
});

folder.should.equal('/global/fixture/dir/en-US/test/name');
});

it('favors the "x-sepia-test-name" header as the test name', function() {
sepiaUtil.setFixtureDir('/global/fixture/dir');
sepiaUtil.setTestOptions({ testName: 'global/test/name' });

var folder = constructAndCreateFixtureFolder('my-url', {
'accept-language': 'en-US',
'x-sepia-test-name': 'test/name'
});

folder.should.equal('/global/fixture/dir/en-US/test/name');
});

it('ignores the test name if it should use global fixtures', function() {
sepiaUtil.setFixtureDir('/global/fixture/dir');
sepiaUtil.setTestOptions({ testName: 'test/name' });
Expand Down

0 comments on commit 55dc617

Please sign in to comment.