Skip to content
This repository has been archived by the owner on Dec 16, 2023. It is now read-only.

feat: allow to manually remove unused fixtures #156

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,24 @@ Replay.recordResponseControl = {
};
```

## Removing unused fixtures
`replay` automatically records which fixtures have not been used during the test run. To auto-delete those, access `Replay.unmatchedFixtures` and remove them.

Example:

```javascript
const unlink = util.promisify(fs.unlink)

afterAll(async () => {
const unlinkPromises = []
Replay.unmatchedFixtures.map(matcher => {
unlinkPromises.push(unlink(matcher.fixturePath))
})
await Promise.all(unlinkPromises)
console.log(`Removed ${unlinkPromises.length} unused fixtures.`)
})
```


## Geeking

Expand Down
17 changes: 12 additions & 5 deletions src/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,20 +193,23 @@ module.exports = class Catalog {
files = files.filter(f => !/^\./.test(f));
for (let file of files) {
let mapping = this._read(`${pathname}/${file}`);
newMatchers.push(Matcher.fromMapping(host, mapping));
newMatchers.push(Matcher.fromMapping(`${pathname}/${file}`, host, mapping));
}
} else {
const mapping = this._read(pathname);
newMatchers.push(Matcher.fromMapping(host, mapping));
newMatchers.push(Matcher.fromMapping(`${pathname}`, host, mapping));
}

return newMatchers;
}

getUnmatchedFixtures() {
return Object.keys(this.matchers).map(host => {
return this.matchers[host].filter(matcher => !matcher.isMatched)
}).reduce((a, b) => a.concat(b), []);
}

save(host, request, response, callback) {
const matcher = Matcher.fromMapping(host, { request, response });
const matchers = this.matchers[host] || [];
matchers.push(matcher);
const requestHeaders = this.settings.headers;

const uid = `${Date.now()}${Math.floor(Math.random() * 100000)}`;
Expand All @@ -224,6 +227,10 @@ module.exports = class Catalog {
}

const filename = `${pathname}/${uid}`;
const matcher = Matcher.fromMapping(filename, host, { request, response });
const matchers = this.matchers[host] || [];
matchers.push(matcher);

try {
const file = File.createWriteStream(tmpfile, { encoding: 'utf-8' });
file.write(`${request.method.toUpperCase()} ${request.url.path || '/'}\n`);
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ class Replay extends EventEmitter {
this.catalog.setFixturesDir(dir);
}

get unmatchedFixtures() {
return this.catalog.getUnmatchedFixtures();
}
}


Expand Down
30 changes: 18 additions & 12 deletions src/matcher.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// A matcher is a function that, given a request, returns an appropriate response or nothing.
//
// The most common use case is to calling `Matcher.fromMapping(mapping)`.
// The most common use case is to calling `Matcher.fromMapping(filename, host, mapping)`.
//
// The request consists of:
// url - URL object
Expand All @@ -25,7 +25,7 @@ const jsStringEscape = require('js-string-escape');
// To create a matcher from request/response mapping use `fromMapping`.
module.exports = class Matcher {

constructor(request, response) {
constructor(fixturePath, request, response) {
// Map requests to object properties. We do this for quick matching.
assert(request.url || request.regexp, 'I need at least a URL to match request to response');
if (request.regexp) {
Expand All @@ -38,6 +38,9 @@ module.exports = class Matcher {
this.path = url.path;
}

this.fixturePath = fixturePath;
this.isMatched = false;

this.method = (request.method && request.method.toUpperCase()) || 'GET';
this.headers = {};
if (request.headers)
Expand Down Expand Up @@ -103,20 +106,27 @@ module.exports = class Matcher {
data += chunks[0];
data = jsStringEscape(data);

return this.body instanceof RegExp ?
this.body.test(data) :
this.body === data;
if (this.body instanceof RegExp && this.body.test(data)) {
this.isMatched = true;
return this.response;
} else if(this.body === data) {
this.isMatched = true;
return this.response;
}
return false
}

return true;
this.isMatched = true;

return this.response;
}


// Returns new matcher function based on the supplied mapping.
//
// Mapping can contain `request` and `response` object. As shortcut, mapping can specify `path` and `method` (optional)
// directly, and also any of the response properties.
static fromMapping(host, mapping) {
static fromMapping(fileName, host, mapping) {
assert(!!mapping.path ^ !!mapping.request, 'Mapping must specify path or request object');

let matchingRequest;
Expand All @@ -141,11 +151,7 @@ module.exports = class Matcher {
body: mapping.request.body
};

const matcher = new Matcher(matchingRequest, mapping.response || {});
return function(request) {
if (matcher.match(request))
return matcher.response;
};
return new Matcher(fileName, matchingRequest, mapping.response || {});
}

};
Expand Down
2 changes: 1 addition & 1 deletion src/recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = function recorded(settings) {
const matchers = catalog.find(host);
if (matchers)
for (let matcher of matchers) {
let response = matcher(request);
let response = matcher.match(request);
if (response) {
callback(null, response);
return;
Expand Down
5 changes: 5 additions & 0 deletions test/replay_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ describe('Replay', function() {

describe('listeners', function() {
let response;
let unmatchedFixtures;

before(function(done) {
HTTP
.get(`http://example.com:${INACTIVE_PORT}/weather?c=94606`, function(_) {
response = _;
unmatchedFixtures = Replay.unmatchedFixtures
done();
})
.on('error', done);
Expand All @@ -46,6 +48,9 @@ describe('Replay', function() {
it('should return response trailers', function() {
assert.deepEqual(response.trailers, { });
});
it('should have matched', function() {
assert.equal(unmatchedFixtures.filter(matcher => matcher.path === '/weather?c=94606').length, 0);
})
mweibel marked this conversation as resolved.
Show resolved Hide resolved
});

describe('Old http status line format', function() {
Expand Down