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

Commit 00b3814

Browse files
committed
Minor tweaks
- reworks givenModel - add shouldNotBeFound test generator - allow befores to override url - reworked loggedInAsUser
1 parent e8abd6e commit 00b3814

File tree

2 files changed

+209
-32
lines changed

2 files changed

+209
-32
lines changed

index.js

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,23 @@ _beforeEach.withArgs = function() {
7979
}
8080

8181
_beforeEach.givenModel = function(modelName, attrs, optionalHandler) {
82+
var modelKey = modelName
83+
8284
if(typeof attrs === 'funciton') {
8385
optionalHandler = attrs;
8486
attrs = undefined;
8587
}
8688

89+
if(typeof optionalHandler === 'string') {
90+
modelKey = optionalHandler;
91+
}
92+
8793
attrs = attrs || {};
8894

8995
var model = loopback.getModel(modelName);
9096

97+
assert(model, 'cannot get model of name ' + modelName);
98+
9199
beforeEach(function(done) {
92100
var test = this;
93101

@@ -105,7 +113,7 @@ _beforeEach.givenModel = function(modelName, attrs, optionalHandler) {
105113
if(err) {
106114
done(err);
107115
} else {
108-
test[modelName] = result;
116+
test[modelKey] = result;
109117
done();
110118
}
111119
});
@@ -116,14 +124,37 @@ _beforeEach.givenModel = function(modelName, attrs, optionalHandler) {
116124
}
117125

118126
afterEach(function(done) {
119-
this[modelName].destroy(done);
127+
this[modelKey].destroy(done);
120128
});
121129
}
122130

123131
_beforeEach.givenUser = function(attrs, optionalHandler) {
124132
_beforeEach.givenModel('user', attrs, optionalHandler);
125133
}
126134

135+
_beforeEach.givenLoggedInUser = function(credentials, optionalHandler) {
136+
_beforeEach.givenUser(credentials, function(done) {
137+
var test = this;
138+
this.user.constructor.login(credentials, function(err, token) {
139+
if(err) {
140+
done(err);
141+
} else {
142+
test.loggedInAccessToken = token;
143+
done();
144+
}
145+
});
146+
});
147+
148+
afterEach(function(done) {
149+
var test = this;
150+
this.loggedInAccessToken.destroy(function(err) {
151+
if(err) return done(err);
152+
test.loggedInAccessToken = undefined;
153+
done();
154+
});
155+
});
156+
}
157+
127158
_beforeEach.givenAnUnauthenticatedToken = function(attrs, optionalHandler) {
128159
_beforeEach.givenModel('accessToken', attrs, optionalHandler);
129160
}
@@ -144,12 +175,16 @@ _describe.whenCalledRemotely = function(verb, url, cb) {
144175
}
145176
this.remotely = true;
146177
this.verb = verb.toUpperCase();
147-
this.url = url;
178+
this.url = this.url || url;
148179
var methodForVerb = verb.toLowerCase();
149180
if(methodForVerb === 'delete') methodForVerb = 'del';
150181

151-
this.http = this.request[methodForVerb](url);
182+
this.http = this.request[methodForVerb](this.url);
183+
this.url = undefined;
152184
this.http.set('Accept', 'application/json');
185+
if(this.loggedInAccessToken) {
186+
this.http.set('authorization', this.loggedInAccessToken.id);
187+
}
153188
this.req = this.http.req;
154189
var test = this;
155190
this.http.end(function(err) {
@@ -162,45 +197,31 @@ _describe.whenCalledRemotely = function(verb, url, cb) {
162197
});
163198
}
164199

200+
_describe.whenLoggedInAsUser = function(credentials, cb) {
201+
describe('when logged in as user', function () {
202+
_beforeEach.givenLoggedInUser(credentials);
203+
cb();
204+
});
205+
}
206+
165207
_describe.whenCalledByUser = function(credentials, verb, url, cb) {
166-
_describe.whenLoggedInAsUser(credentials, function() {
208+
describe('when called by logged in user', function () {
209+
_beforeEach.givenLoggedInUser(credentials);
167210
_describe.whenCalledRemotely(verb, url, cb);
168211
});
169212
}
170213

171214
_describe.whenCalledAnonymously = function(verb, url, cb) {
172-
_describe.whenCalledRemotely(verb, url, function() {
215+
describe('when called anonymously', function () {
173216
_beforeEach.givenAnAnonymousToken();
174-
cb();
217+
_describe.whenCalledRemotely(verb, url, cb);
175218
});
176219
}
177220

178221
_describe.whenCalledUnauthenticated = function(verb, url, cb) {
179-
_describe.whenCalledRemotely(verb, url, function() {
180-
_beforeEach.givenAnUnauthenticatedToken();
181-
cb();
182-
});
183-
}
184-
185-
_describe.whenLoggedInAsUser = function(credentials, cb) {
186-
describe('when logged in user', function() {
187-
_beforeEach.givenUser(credentials, function(done) {
188-
var test = this;
189-
this.remotely = true;
190-
this.user.constructor.login(credentials, function(err, token) {
191-
if(err) {
192-
done(err);
193-
} else {
194-
test.accessToken = token;
195-
test.req.set('authorization', token.id);
196-
done();
197-
}
198-
});
199-
});
200-
201-
afterEach(function(done) {
202-
this.accessToken.destroy(done);
203-
});
222+
describe('when called with unauthenticated token', function () {
223+
_beforeEach.givenAnAnonymousToken();
224+
_describe.whenCalledRemotely(verb, url, cb);
204225
});
205226
}
206227

@@ -219,6 +240,13 @@ _it.shouldBeDenied = function() {
219240
});
220241
}
221242

243+
_it.shouldNotBeFound = function() {
244+
it('should not be found', function() {
245+
assert(this.res);
246+
assert.equal(this.res.statusCode, 404);
247+
});
248+
}
249+
222250
_it.shouldBeAllowedWhenCalledAnonymously =
223251
function(verb, url) {
224252
_describe.whenCalledAnonymously(verb, url, function() {

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)