Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Commit d10a03d

Browse files
committed
Merge branch 'release/0.0.3' into production
2 parents c64fe34 + 64086af commit d10a03d

File tree

5 files changed

+487
-44
lines changed

5 files changed

+487
-44
lines changed

index.js

Lines changed: 54 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
var _describe = {};
22
var _it = {};
33
var _beforeEach = {};
4-
var helpers = module.exports = {
4+
var helpers = exports = module.exports = {
55
describe: _describe,
66
it: _it,
77
beforeEach: _beforeEach
@@ -79,7 +79,7 @@ _beforeEach.withArgs = function() {
7979
}
8080

8181
_beforeEach.givenModel = function(modelName, attrs, optionalHandler) {
82-
var modelKey = modelName;
82+
var modelKey = modelName
8383

8484
if(typeof attrs === 'funciton') {
8585
optionalHandler = attrs;
@@ -94,6 +94,8 @@ _beforeEach.givenModel = function(modelName, attrs, optionalHandler) {
9494

9595
var model = loopback.getModel(modelName);
9696

97+
assert(model, 'cannot get model of name ' + modelName);
98+
9799
beforeEach(function(done) {
98100
var test = this;
99101

@@ -132,6 +134,29 @@ _beforeEach.givenUser = function(attrs, optionalHandler) {
132134
_beforeEach.givenModel('user', attrs, optionalHandler);
133135
}
134136

137+
_beforeEach.givenLoggedInUser = function(credentials, optionalHandler) {
138+
_beforeEach.givenUser(credentials, function(done) {
139+
var test = this;
140+
this.user.constructor.login(credentials, function(err, token) {
141+
if(err) {
142+
done(err);
143+
} else {
144+
test.loggedInAccessToken = token;
145+
done();
146+
}
147+
});
148+
});
149+
150+
afterEach(function(done) {
151+
var test = this;
152+
this.loggedInAccessToken.destroy(function(err) {
153+
if(err) return done(err);
154+
test.loggedInAccessToken = undefined;
155+
done();
156+
});
157+
});
158+
}
159+
135160
_beforeEach.givenAnUnauthenticatedToken = function(attrs, optionalHandler) {
136161
_beforeEach.givenModel('accessToken', attrs, optionalHandler);
137162
}
@@ -158,14 +183,12 @@ _describe.whenCalledRemotely = function(verb, url, cb) {
158183
if(methodForVerb === 'delete') methodForVerb = 'del';
159184

160185
this.http = this.request[methodForVerb](this.url);
161-
162-
// request settings
186+
this.url = undefined;
163187
this.http.set('Accept', 'application/json');
164-
this.http.set('authorization', null);
165-
if(this.accessToken) {
166-
this.http.set('authorization', this.accessToken.id);
188+
if(this.loggedInAccessToken) {
189+
this.http.set('authorization', this.loggedInAccessToken.id);
167190
}
168-
191+
this.req = this.http.req;
169192
var test = this;
170193
this.http.end(function(err) {
171194
test.req = test.http.req;
@@ -179,49 +202,31 @@ _describe.whenCalledRemotely = function(verb, url, cb) {
179202
});
180203
}
181204

205+
_describe.whenLoggedInAsUser = function(credentials, cb) {
206+
describe('when logged in as user', function () {
207+
_beforeEach.givenLoggedInUser(credentials);
208+
cb();
209+
});
210+
}
211+
182212
_describe.whenCalledByUser = function(credentials, verb, url, cb) {
183-
_describe.whenLoggedInAsUser(credentials, function() {
213+
describe('when called by logged in user', function () {
214+
_beforeEach.givenLoggedInUser(credentials);
184215
_describe.whenCalledRemotely(verb, url, cb);
185216
});
186217
}
187218

188219
_describe.whenCalledAnonymously = function(verb, url, cb) {
189-
_describe.whenCalledRemotely(verb, url, function() {
220+
describe('when called anonymously', function () {
190221
_beforeEach.givenAnAnonymousToken();
191-
cb();
222+
_describe.whenCalledRemotely(verb, url, cb);
192223
});
193224
}
194225

195226
_describe.whenCalledUnauthenticated = function(verb, url, cb) {
196-
_describe.whenCalledRemotely(verb, url, function() {
197-
_beforeEach.givenAnUnauthenticatedToken();
198-
cb();
199-
});
200-
}
201-
202-
_describe.whenLoggedInAsUser = function(credentials, cb) {
203-
describe('when logged in user', function() {
204-
_beforeEach.givenUser(credentials, function(done) {
205-
var test = this;
206-
this.user.constructor.login(credentials, function(err, token) {
207-
if(err) {
208-
done(err);
209-
} else {
210-
test.accessToken = token;
211-
done();
212-
}
213-
});
214-
});
215-
216-
afterEach(function(done) {
217-
this.accessToken.destroy(done);
218-
});
219-
220-
afterEach(function() {
221-
this.accessToken = undefined;
222-
});
223-
224-
cb();
227+
describe('when called with unauthenticated token', function () {
228+
_beforeEach.givenAnAnonymousToken();
229+
_describe.whenCalledRemotely(verb, url, cb);
225230
});
226231
}
227232

@@ -241,6 +246,13 @@ _it.shouldBeDenied = function() {
241246
});
242247
}
243248

249+
_it.shouldNotBeFound = function() {
250+
it('should not be found', function() {
251+
assert(this.res);
252+
assert.equal(this.res.statusCode, 404);
253+
});
254+
}
255+
244256
_it.shouldBeAllowedWhenCalledAnonymously =
245257
function(verb, url) {
246258
_describe.whenCalledAnonymously(verb, url, function() {
@@ -282,3 +294,5 @@ function(credentials, verb, url) {
282294
_it.shouldBeDenied();
283295
});
284296
}
297+
298+
exports.TestDataBuilder = require('./lib/test-data-builder');

lib/request-builder.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
var extend = require('util')._extend;
2+
var async = require('async');
3+
4+
module.exports = exports = TestFLow;
5+
6+
/**
7+
* Make HTTP requests against loopback apps.
8+
*
9+
* Usage:
10+
* ```js
11+
12+
* ```
13+
* @constructor
14+
*/
15+
function TestFLow() {
16+
this._definitions = [];
17+
}
18+
19+
/**
20+
* Define a new model instance.
21+
* @param {string} name Name of the instance.
22+
* `buildTo()` will save the instance created as context[name].
23+
* @param {constructor} Model Model class/constructor.
24+
* @param {Object.<string, Object>=} properties
25+
* Properties to set in the object.
26+
* Intelligent default values are supplied by the builder
27+
* for required properties not listed.
28+
* @return TestFLow (fluent interface)
29+
*/
30+
TestFLow.prototype.define = function(name, Model, properties) {
31+
this._definitions.push({
32+
name: name,
33+
model: Model,
34+
properties: properties
35+
});
36+
return this;
37+
};
38+
39+
/**
40+
* Reference the value of a property from a model instance defined before.
41+
* @param {string} path Generally in the form '{name}.{property}', where {name}
42+
* is the name passed to `define()` and {property} is the name of
43+
* the property to use.
44+
*/
45+
TestFLow.ref = function(path) {
46+
return new Reference(path);
47+
};
48+
49+
/**
50+
* Asynchronously build all models defined via `define()` and save them in
51+
* the supplied context object.
52+
* @param {Object.<string, Object>} context The context to object to populate.
53+
* @param {function(Error)} callback Callback.
54+
*/
55+
TestFLow.prototype.buildTo = function(context, callback) {
56+
this._context = context;
57+
async.eachSeries(
58+
this._definitions,
59+
this._buildObject.bind(this),
60+
callback);
61+
};
62+
63+
TestFLow.prototype._buildObject = function(definition, callback) {
64+
var defaultValues = this._gatherDefaultPropertyValues(definition.model);
65+
var values = extend(defaultValues, definition.properties || {});
66+
var resolvedValues = this._resolveValues(values);
67+
68+
definition.model.create(resolvedValues, function(err, result) {
69+
if (err) {
70+
console.error(
71+
'Cannot build object %j - %s\nDetails: %j',
72+
definition,
73+
err.message,
74+
err.details);
75+
} else {
76+
this._context[definition.name] = result;
77+
}
78+
79+
callback(err);
80+
}.bind(this));
81+
};
82+
83+
TestFLow.prototype._resolveValues = function(values) {
84+
var result = {};
85+
for (var key in values) {
86+
var val = values[key];
87+
if (val instanceof Reference) {
88+
val = values[key].resolveFromContext(this._context);
89+
}
90+
result[key] = val;
91+
}
92+
return result;
93+
};
94+
95+
var valueCounter = 0;
96+
TestFLow.prototype._gatherDefaultPropertyValues = function(Model) {
97+
var result = {};
98+
Model.forEachProperty(function createDefaultPropertyValue(name) {
99+
var prop = Model.definition.properties[name];
100+
if (!prop.required) return;
101+
102+
switch (prop.type) {
103+
case String:
104+
result[name] = 'a test ' + name + ' #' + (++valueCounter);
105+
break;
106+
case Number:
107+
result[name] = 1230000 + (++valueCounter);
108+
break;
109+
case Date:
110+
result[name] = new Date(
111+
2222, 12, 12, // yyyy, mm, dd
112+
12, 12, 12, // hh, MM, ss
113+
++valueCounter // milliseconds
114+
);
115+
break;
116+
case Boolean:
117+
// There isn't much choice here, is it?
118+
// Let's use "false" to encourage users to be explicit when they
119+
// require "true" to turn some flag/behaviour on
120+
result[name] = false;
121+
break;
122+
// TODO: support nested structures - array, object
123+
}
124+
});
125+
return result;
126+
};
127+
128+
/**
129+
* Placeholder for values that will be resolved during build.
130+
* @param path
131+
* @constructor
132+
* @private
133+
*/
134+
function Reference(path) {
135+
this._path = path;
136+
}
137+
138+
Reference.prototype.resolveFromContext = function(context) {
139+
var elements = this._path.split('.');
140+
141+
var result = elements.reduce(
142+
function(obj, prop) {
143+
return obj[prop];
144+
},
145+
context
146+
);
147+
148+
return result;
149+
};

0 commit comments

Comments
 (0)