Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add x-sepia-test-name header support #1

Merged
merged 1 commit into from Dec 24, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use lodash _.pick instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sepia has no non-development dependencies. Even lodash is only as a dev dependency.

In any case, _.pick wouldn't work because what we're looking for is actually _.filter. or _.reject.

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