Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 09354f7

Browse files
Merge pull request #5 from jimmyeisenhauer/dynamic-data
1.1.0 release
2 parents 1b98baf + 322991b commit 09354f7

13 files changed

+261
-11
lines changed

CHANGELOG.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@ The changelog for BOKOR includes information about the each release including an
44

55

66
---
7-
## 1.0.1
7+
## 1.0.1
88

99
### Release Notes
1010
#### Added
1111

1212
- I AM BORN!
13+
14+
15+
## 1.1.0
16+
17+
### Release Notes
18+
#### Added
19+
20+
- Relative Dates Time objects! Ability to have dynamic mocked date time values.
21+
- Ability to turn off the admin server via configuration.

README.md

+55
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@
66

77
Bokor is a simple, Record and Playback Mock Server written in Node.js, utilized for Service Virtualization.
88

9+
Bokor is very similar to the many VCR-like tools out there today, but prides itself on its ease of use and speed to setup. It can be utilized for any mocking needs, but was primarily developed for mocking back end service calls in automated UI testing.
10+
11+
Bokor was developed and is in use at Nike since early 2016. There, it is used to improve the speed, reliability and test coverage of the integration and user interface test suites for native mobile applications.
12+
913
- [Installation](#installation)
1014
- [Usage](#usage)
1115
- [Server Configuration](#server-configuration)
1216
- [Filter Configuration](#filter-configuration)
1317
- [Advanced Configuration](#advanced-configuration)
18+
- [Relative Date Time Objects](#relative-date-time-objects)
19+
- [Static Resources](#static-resources)
20+
- [Admin Server](#admin-server)
21+
- [Port](#port)
1422
- [Data Fixtures](#data-fixtures)
1523
- [Data Bins](#data-bins)
1624
- [Fixture Filenames](#fixture-filenames)
@@ -125,6 +133,43 @@ bokor server rolled lucky 7777
125133

126134
### Advanced Configuration
127135

136+
#### Relative Date Time Objects
137+
Often we find the need to have date time results relative from the current date time. For example; "5 minutes from now" or "Exactly 3 days from now". With Bokor you can manipulate your recorded date time values in any possible way you can imagine.
138+
139+
##### Create `datetimes.properties` file
140+
First create a key name that is meaningful like: `NOW_DATETIME_UTC`. Next utilizing the [Moment.JS library](http://momentjs.com/) create your javascript function that will manipulate the date time to your needs like: `moment().utc().format()`, making sure to escape any single quotes.
141+
142+
```javascript
143+
var datetimes = {
144+
datetime1: {
145+
key: 'NOW_DATETIME_UTC',
146+
value: 'moment().utc().format()',
147+
},
148+
datetime2: {
149+
key: 'NOW_DATETIME_UTC_PLUS_10_MINUTES',
150+
value: 'moment().utc().add(10, \'m\').format()',
151+
}
152+
};
153+
module.exports = datetimes;
154+
```
155+
156+
##### Add the date time configuration to the `server.js` file.
157+
```javascript
158+
var serversProperties = require('./servers.properties');
159+
var filtersProperties = require('./filters.properties');
160+
var datetimesProperties = require('./datetimes.properties');
161+
162+
var bokor = require('bokor');
163+
164+
bokor.start({
165+
servers : serversProperties,
166+
filters : filtersProperties,
167+
datetimes : datetimesProperties
168+
});
169+
```
170+
Once you have completed the configuration modify your recorded data fixtures, by replacing date time values with a the key values you created. Bokor will dynamically create date time values the next time you request that data fixture.
171+
172+
128173
#### Static Resources
129174
By default Bokor serves any static resource in the `static_files` folder. You can modify this folder name by adjusting the server config.
130175
```javascript
@@ -135,6 +180,16 @@ staticFileLocation: customFolder
135180
});
136181
```
137182

183+
#### Admin Server
184+
By default Bokor runs an admin server on port 58080. If you do not need this feature you can turn off the admin server by adjusting the server config.
185+
```javascript
186+
bokor.start({
187+
servers : serversProperties,
188+
filters : filtersProperties,
189+
admin: false
190+
});
191+
```
192+
138193
#### Port
139194
By default Bokor runs on port 7777. You can modify this port by adjusting the server config.
140195
```javascript
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"login": "jimmyeisenhauer",
3+
"id": 791447,
4+
"avatar_url": "https://avatars.githubusercontent.com/u/791447?v=3",
5+
"gravatar_id": "",
6+
"url": "https://api.github.com/users/jimmyeisenhauer",
7+
"html_url": "https://github.com/jimmyeisenhauer",
8+
"followers_url": "https://api.github.com/users/jimmyeisenhauer/followers",
9+
"following_url": "https://api.github.com/users/jimmyeisenhauer/following{/other_user}",
10+
"gists_url": "https://api.github.com/users/jimmyeisenhauer/gists{/gist_id}",
11+
"starred_url": "https://api.github.com/users/jimmyeisenhauer/starred{/owner}{/repo}",
12+
"subscriptions_url": "https://api.github.com/users/jimmyeisenhauer/subscriptions",
13+
"organizations_url": "https://api.github.com/users/jimmyeisenhauer/orgs",
14+
"repos_url": "https://api.github.com/users/jimmyeisenhauer/repos",
15+
"events_url": "https://api.github.com/users/jimmyeisenhauer/events{/privacy}",
16+
"received_events_url": "https://api.github.com/users/jimmyeisenhauer/received_events",
17+
"type": "User",
18+
"site_admin": false,
19+
"name": "Jimmy Eisenhauer",
20+
"company": "@Nike-Inc",
21+
"blog": "http://www.twitter.com/jimmyeisenhauer",
22+
"location": "Beaverton, Oregon",
23+
"email": null,
24+
"hireable": null,
25+
"bio": "Software Engineer ",
26+
"public_repos": 42,
27+
"public_gists": 8,
28+
"followers": 4,
29+
"following": 14,
30+
"created_at": "NOW_DATETIME_UTC",
31+
"updated_at": "NOW_DATETIME_UTC_PLUS_10_MINUTES"
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"statusCode": 200,
3+
"headers": {
4+
"server": "GitHub.com",
5+
"date": "Sat, 28 Jan 2017 23:29:11 GMT",
6+
"content-type": "application/json; charset=utf-8",
7+
"status": "200 OK",
8+
"x-ratelimit-limit": "60",
9+
"x-ratelimit-remaining": "57",
10+
"x-ratelimit-reset": "1485646272",
11+
"cache-control": "public, max-age=60, s-maxage=60",
12+
"vary": "Accept, Accept-Encoding",
13+
"etag": "\"7c0f10ed5c54c3f458b1b2e8809d9c80\"",
14+
"last-modified": "Tue, 24 Jan 2017 09:46:25 GMT",
15+
"x-github-media-type": "github.v3; format=json",
16+
"access-control-expose-headers": "ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval",
17+
"access-control-allow-origin": "*",
18+
"content-security-policy": "default-src 'none'",
19+
"strict-transport-security": "max-age=31536000; includeSubdomains; preload",
20+
"x-content-type-options": "nosniff",
21+
"x-frame-options": "deny",
22+
"x-xss-protection": "1; mode=block",
23+
"x-served-by": "8dd185e423974a7e13abbbe6e060031e",
24+
"x-github-request-id": "C542:03EA:766947E:9B03009:588D2946"
25+
},
26+
"url": "https://api.github.com:443/users/jimmyeisenhauer",
27+
"time": 697,
28+
"request": {
29+
"method": "GET",
30+
"headers": {
31+
"accept": "*/*",
32+
"user-agent": "curl/7.51.0",
33+
"host": "api.github.com"
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
var datetimes = {
2+
datetime1: {
3+
key: 'NOW_DATETIME_UTC',
4+
value: 'moment().utc().format()',
5+
},
6+
datetime2: {
7+
key: 'NOW_DATETIME_UTC_PLUS_10_MINUTES',
8+
value: 'moment().utc().add(10, \'m\').format()',
9+
}
10+
};
11+
module.exports = datetimes;

examples/source_example/server.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515

1616
var serversProperties = require('./servers.properties');
1717
var filtersProperties = require('./filters.properties');
18+
var datetimesProperties = require('./datetimes.properties');
1819

1920
var bokor = require('../../');
2021

2122
bokor.start({
2223
servers : serversProperties,
23-
filters : filtersProperties
24+
filters : filtersProperties,
25+
datetimes : datetimesProperties
2426
});

lib/bokor.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
var express = require('express');
1616
var httpProxy = require('http-proxy');
1717
var https = require('https');
18+
var moment = require('moment'); // jshint ignore:line
1819
var bokorOptions = {};
1920
require('colors');
2021
require('http');
@@ -42,7 +43,14 @@ function start(options) {
4243
bokorOptions.staticFileLocation = options.staticFileLocation || 'static_files'; // bokor server static file location
4344

4445
// -- sepia config --------------
45-
var sepia = require('./sepia').withSepiaServer();
46+
// default bokor admin server on by default
47+
var sepia;
48+
if (options.admin === false) {
49+
sepia = require('./sepia');
50+
} else {
51+
sepia = require('./sepia').withSepiaServer();
52+
}
53+
4654
sepia.configure({
4755
verbose: true,
4856
debug: false,
@@ -57,6 +65,16 @@ function start(options) {
5765
});
5866

5967

68+
// setup the dynamic datetimes for sepia
69+
if (options.datetimes != null) {
70+
bokorOptions.datetimes = options.datetimes;
71+
var datetimeConfigs = Object.keys(bokorOptions.datetimes);
72+
datetimeConfigs.forEach(datetime => {
73+
var datetimeConfig = options.datetimes[datetime];
74+
sepia.substitute(datetimeConfig.key, function () { return eval(datetimeConfig.value) ;}); // jshint ignore:line
75+
});
76+
}
77+
6078
// setup the url filters for sepia
6179
var filterConfigs = Object.keys(bokorOptions.filters);
6280
filterConfigs.forEach(filterName => {

lib/sepia/README.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,16 @@ Examples of this functionality can be seen in `examples/headers.js`:
261261
rm -r fixtures # in case you had previously generated fixtures
262262
VCR_MODE=cache node examples/headers
263263

264+
## Hiding Sensitive Data
265+
266+
It is good practice to avoid checking sensitive data into source control. Sepia
267+
can substituting specific text in headers and bodies with values you specify. The substitute function takes a substitution string as a first argument and a function which
268+
returns the actual value, presumably retrieved from the environment. Your fixtures
269+
will contain the substitution string, and can be safely committed to source control.
270+
271+
var sepia = require('sepia');
272+
sepia.substitute('<SUBSTITUTION1>', function() { return process.env.MY_API_SECRET; });
273+
264274
## Languages
265275

266276
A downstream request may return different data based on the language requested
@@ -407,4 +417,3 @@ data is retrieved from a file and sent back using a dummy response object.
407417
* [Deepank Gupta](https://github.com/deepankgupta)
408418
* [Priyanka Salvi](https://github.com/salvipriyanka/)
409419
* [Ashima Atul](https://github.com/ashimaatul)
410-

lib/sepia/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ function shutdown(next) {
6060

6161
var sepiaUtil = require('./src/util');
6262
module.exports.filter = sepiaUtil.addFilter;
63+
module.exports.substitute = sepiaUtil.addSubstitution;
6364
module.exports.fixtureDir = sepiaUtil.setFixtureDir;
6465
module.exports.configure = sepiaUtil.configure;
6566
module.exports.withSepiaServer = withSepiaServer;

lib/sepia/src/cache.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ module.exports.configure = function(mode) {
9191
// exists, or we're playing back passed in data.
9292
function playback(resHeaders, resBody) {
9393
if (!forceLive) {
94-
var headerContent = fs.readFileSync(filename + '.headers');
94+
var headerContent = sepiaUtil.substituteWithRealValues(fs.readFileSync(filename + '.headers').toString());
9595
resHeaders = JSON.parse(headerContent);
9696
}
9797

@@ -123,7 +123,7 @@ module.exports.configure = function(mode) {
123123
}
124124

125125
if (!forceLive) {
126-
resBody = fs.readFileSync(filename);
126+
resBody = sepiaUtil.substituteWithRealValues(fs.readFileSync(filename).toString());
127127
}
128128

129129
req.emit('response', res);
@@ -191,7 +191,7 @@ module.exports.configure = function(mode) {
191191
};
192192

193193
fs.writeFileSync(filename + '.headers',
194-
JSON.stringify(headers, null, 2));
194+
sepiaUtil.substituteWithOpaqueKeys(JSON.stringify(headers, null, 2)));
195195
}
196196

197197
// Suppose the request times out while recording. We don't want the
@@ -232,7 +232,7 @@ module.exports.configure = function(mode) {
232232
headers: res.headers
233233
}, resBody);
234234
} else {
235-
fs.writeFileSync(filename, resBody);
235+
fs.writeFileSync(filename,sepiaUtil.substituteWithOpaqueKeys(resBody.toString()));
236236

237237
// Store the request, if debug is true
238238
if (debug) {

lib/sepia/src/util.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ function reset() {
3333
path.join(process.cwd(), 'bins/');
3434

3535
globalOptions.filenameFilters = [];
36+
globalOptions.substitutions = [];
3637

3738
globalOptions.includeHeaderNames = true;
3839
globalOptions.headerWhitelist = [];
@@ -117,6 +118,32 @@ function addFilter(inFilter) {
117118
globalOptions.filenameFilters.push(filter);
118119
}
119120

121+
//
122+
// substitutions
123+
//
124+
function addSubstitution(opaqueKey, actualValueFn) {
125+
globalOptions.substitutions.push({opaqueKey: opaqueKey, actualValueFn: actualValueFn});
126+
}
127+
128+
function substituteWithOpaqueKeys(text) {
129+
var substitutions = globalOptions.substitutions;
130+
for (var i=0; i<substitutions.length; i++) {
131+
var subst = substitutions[i];
132+
text = text.replace(subst.actualValueFn(), subst.opaqueKey);
133+
}
134+
return text;
135+
}
136+
137+
function substituteWithRealValues(text) {
138+
var substitutions = globalOptions.substitutions;
139+
for (var i=0; i<substitutions.length; i++) {
140+
var subst = substitutions[i];
141+
text = text.replace(subst.opaqueKey, subst.actualValueFn());
142+
}
143+
return text;
144+
}
145+
146+
120147
// -- UTILITY FUNCTIONS --------------------------------------------------------
121148

122149
function mkdirpSync(folder) {
@@ -346,7 +373,6 @@ function constructFilename(method, reqUrl, reqBody, reqHeaders) {
346373

347374
logFixtureStatus(hashFile, hashParts);
348375
touchOnHit(hashFile);
349-
350376
return hashFile;
351377
}
352378

@@ -440,6 +466,9 @@ module.exports.shouldForceLive = shouldForceLive;
440466
module.exports.removeInternalHeaders = removeInternalHeaders;
441467
module.exports.findTheBestMatchingFixture = findTheBestMatchingFixture;
442468
module.exports.shouldFindMatchingFixtures = shouldFindMatchingFixtures;
469+
module.exports.addSubstitution = addSubstitution;
470+
module.exports.substituteWithRealValues = substituteWithRealValues;
471+
module.exports.substituteWithOpaqueKeys = substituteWithOpaqueKeys;
443472

444473
module.exports.internal = {};
445474
module.exports.internal.globalOptions = globalOptions;

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bokor",
3-
"version": "1.0.1",
3+
"version": "1.1.0",
44
"description": "Bokor is a simple, Record and Playback Mock Server written in Node.js, utilized for Service Virtualization.",
55
"main": "index.js",
66
"scripts": {
@@ -38,6 +38,7 @@
3838
"colors": "^1.1.2",
3939
"express": "^4.14.0",
4040
"http-proxy": "^1.15.2",
41-
"levenshtein": "^1.0.5"
41+
"levenshtein": "^1.0.5",
42+
"moment": "^2.17.1"
4243
}
4344
}

0 commit comments

Comments
 (0)