Directive | 79 |How | 80 |Source | 81 |Rendered | 82 |
ng-bind-html | 85 |Automatically uses $sanitize | 86 |<div ng-bind-html="snippet"> |
87 | 88 | |
ng-bind-html | 91 |Bypass $sanitize by explicitly trusting the dangerous value | 92 |
93 | <div ng-bind-html="deliberatelyTrustDangerousSnippet()"> 94 | </div>95 | |
96 | 97 | |
ng-bind | 100 |Automatically escapes | 101 |<div ng-bind="snippet"> |
102 | 103 | |
an html\nclick here\nsnippet
'); 111 | }); 112 | 113 | it('should inline raw snippet if bound to a trusted value', function() { 114 | expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). 115 | toBe("an html\n" + 116 | "click here\n" + 117 | "snippet
"); 118 | }); 119 | 120 | it('should escape snippet without any filter', function() { 121 | expect(element(by.css('#bind-default div')).getInnerHtml()). 122 | toBe("<p style=\"color:blue\">an html\n" + 123 | "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 124 | "snippet</p>"); 125 | }); 126 | 127 | it('should update', function() { 128 | element(by.model('snippet')).clear(); 129 | element(by.model('snippet')).sendKeys('new text'); 130 | expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). 131 | toBe('new text'); 132 | expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( 133 | 'new text'); 134 | expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( 135 | "new <b onclick=\"alert(1)\">text</b>"); 136 | }); 137 |Filter | 567 |Source | 568 |Rendered | 569 |
linky filter | 572 |
573 | <div ng-bind-html="snippet | linky">574 | |
575 | 576 | 577 | | 578 |
linky target | 581 |
582 | <div ng-bind-html="snippetWithTarget | linky:'_blank'">583 | |
584 | 585 | 586 | | 587 |
no filter | 590 |<div ng-bind="snippet"> |
591 | 592 | |
Traverson makes 14 | it easy to work with REST/HATEOAS APIs. It works in Node.js as well as 15 | in the browser. traverson-angular makes it easy to 16 | integrate Traverson into your AngularJS application. This example shows 17 | how to use traverson-angular with Browserify. It talks to the local 18 | Traverson test server.
19 |There is also an example on how to use traverson-angular without 20 | browserify: Non-browserify Example and an 21 | example using the GitHub API with 22 | traverson-angular. 23 |
24 |28 | You need to start the test server contained in the traverson-angular 29 | repository with node bin/start-test-server.js. This 30 | serves this page as well as the REST API that is used in the examples. 31 | Open the URL 32 | 33 | http://localhost:2808/static/browser/example/browserify/index.html 34 | in your browser. If the URL in your browser's address bar says 35 | file:... the examples on this page will not work. 36 |
37 | 38 | 39 |Traverson makes 14 | it easy to work with REST/HATEOAS APIs. It works in Node.js as well as 15 | in the browser. traverson-angular makes it easy to 16 | integrate Traverson into your AngularJS application.This example talks 17 | to the GitHub API - from your browser, in an AngularJS app.
18 |There is also the 19 | Traverson Test Server Example, an 20 | example using traverson-angular with 21 | Browserify and an example using HAL. 22 |
23 |27 | To run this example locally, you should start the test server contained 28 | in the traverson-angular repository with 29 | node bin/start-test-server.js. 30 | This serves this page and its static assets. Then open the URL 31 | http://localhost:2808/static/browser/example/github.html 33 | in your browser. 34 |
35 | 36 | 37 |Traverson makes 13 | it easy to work with REST/HATEOAS APIs. It works in Node.js as well as 14 | in the browser. traverson-angular makes it easy to 15 | integrate Traverson into your AngularJS application. This example 16 | demonstrates how to use traverson-angular together with media type 17 | plug-ins like traverson-hal. This example talks to the local Traverson 18 | test server.
19 |There is also the 20 | more comprehensive example without HAL, an 21 | example using the GitHub API Example and 22 | an example using traverson-angular with 23 | Browserify. 24 |
25 |29 | To run this example locally, you should start the test server contained 30 | in the traverson-angular repository with 31 | node bin/start-test-server.js. 32 | This serves this page as well as the REST API that is used in the 33 | examples. Open the URL 34 | http://localhost:2808/static/browser/example 36 | in your browser. If the URL in your browser's address bar says 37 | file:... the examples on this page will not work. 38 |
39 | 40 | 41 |Traverson makes 13 | it easy to work with REST/HATEOAS APIs. It works in Node.js as well as 14 | in the browser. traverson-angular makes it easy to 15 | integrate Traverson into your AngularJS application. This example talks 16 | to the local Traverson test server.
17 |There is also the GitHub API Example, the 18 | HAL example and an 19 | example using traverson-angular with 20 | Browserify. 21 |
22 |26 | To run this example locally, you should start the test server contained 27 | in the traverson-angular repository with 28 | node bin/start-test-server.js. 29 | This serves this page as well as the REST API that is used in the 30 | examples. Open the URL 31 | http://localhost:2808/static/browser/example 33 | in your browser. If the URL in your browser's address bar says 34 | file:... the examples on this page will not work. 35 |
36 | 37 | 38 |
114 | traverson
115 | .from('http://api.example.com')
116 | .newRequest()
117 | .follow('link_to', 'resource')
118 | .getResource(function(error, document) {
119 | if (error) {
120 | console.error('No luck :-)')
121 | } else {
122 | console.log('We have followed the path and reached our destination.')
123 | console.log(JSON.stringify(document))
124 | }
125 | });
126 |
127 | becomes this with traverson-angular:
128 |
129 | traverson
130 | .from('http://api.example.com')
131 | .newRequest()
132 | .follow('link_to', 'resource')
133 | .getResource()
134 | .result
135 | .then(function(document) {
136 | console.log('We have followed the path and reached our destination.')
137 | console.log(JSON.stringify(document))
138 | }, function(err) {
139 | console.error('No luck');
140 | });
141 |
142 |
143 | The only difference is `.getResource(function(error, document) {` => `.getResource().result.then(function(document) {`.
144 |
145 | Actually, the object returned by `getResource` has three properties:
146 | * `result`: the promise representing the link traversal,
147 | * `continue`: a function that can be used to [continue](#continuing-a-link-traversal) a finished link traversal and
148 | * `abort`: a function that can be used to [abort](#aborting-the-link-traversal) link traversal that is in progress.
149 |
150 | The following action methods of the Traverson request builder return such an object (`{ result, continue, abort }`) when used via traverson-angular:
151 |
152 | * `get()`
153 | * `getResource()`
154 | * `getUri()`
155 | * `post(payload)`
156 | * `put(payload)`
157 | * `patch(payload)`
158 | * `delete`
159 |
160 | ### How HTTP Status Code Are Handled
161 |
162 | In contrast to AngularJS' `$http` service, Traverson and traverson-angular do not interpret status codes outside of the 2xx range as an error condition. Only network problems (host not reachable, timeouts, etc.) lead to a rejection of the promise, that is, only those trigger the error callback. Completed HTTP requests, even those with status 4xx or 5xx are interpreted as a success and trigger the success callback. This applies only to the last request in a traversal, HTTP requests *during* the traversal that respond with 4xx/5xx are interpreted as an error (because the traversal can not continue).
163 |
164 | This also holds when using `.useAngularHttp()` (see below).
165 |
166 | ### Using AngularJS' $http Service Instead Of Traverson's HTTP Module
167 |
168 | Traverson has it's own HTTP module (based on [superagent](https://github.com/visionmedia/superagent)) and by default, this is used to make HTTP requests. If you want to use Traverson in a project that makes use of AngularJS' $http service and its configuration possibilities (default headers, interceptors and so on), these configurations do not apply automatically to the requests issued by Traverson. If you want that, you can configure traverson-angular to use $http instead of Traverson's HTTP module by calling `useAngularHttp()` on the request builder.
169 |
170 | Example:
171 |
172 |
173 | traverson
174 | .from('http://api.example.com')
175 | .useAngularHttp()
176 | .newRequest()
177 | .follow('link_to', 'resource')
178 | .getResource()
179 | .result
180 | .then(function(document) {
181 | ...
182 | });
183 |
184 |
185 | ### Continuing a Link Traversal
186 |
187 | See [Traverson's README](https://github.com/traverson/traverson#continuing-a-link-traversal) for a general description of the `continue()` feature. This section just describes how to use it with traverson-angular.
188 |
189 | The object returned by the action methods (`get`, `getResource`, `getUrl`, `post`, `put`, `patch`, `delete`) have a property `continue` which is a function that can be used to obtain a promise that is resolved when the link traversal finishes (as does the `result` promise) and which gives you a request builder instance that starts at the last URL/resource of the finished link traversal. It can be used just as the standard [request builder](https://github.com/traverson/traverson/blob/master/api.markdown#request-builder). That is, it has the same configuration and action methods. It enables you to continue the link traversal from the last target resource and follow more links from there.
190 |
191 | So while with plain vanilla Traverson (not traverson-angular) you would continue a successful link traversal process like this:
192 |
193 | ```javascript
194 | traverson
195 | .from(rootUrl)
196 | .follow('link1', 'link2')
197 | .getResource(function(err, firstResource, traversal) {
198 | if (err) { return done(err); }
199 | // do something with the first resource, maybe decide where to go from here.
200 | traversal
201 | .continue()
202 | .follow('link3', 'link3')
203 | .getResource(function(err, secondResource) {
204 | if (err) { return done(err); }
205 | // do something with the second resource
206 | });
207 | });
208 | ```
209 |
210 | ...this is how it is done with traverson-angular:
211 |
212 |
213 | var request =
214 | traverson
215 | .from('http://api.example.com')
216 | .follow('link1', 'link2');
217 | .getResource();
218 |
219 | request.result.then(successCallback, errorCallback);
220 |
221 | request.continue().then(function(request) {
222 | request
223 | .follow('link3', 'link4');
224 | .getResource()
225 | .result
226 | .then(successCallback2, errorCallback2);
227 | });
228 |
229 |
230 | ### Aborting the Link Traversal
231 |
232 | As mentioned above, the object returned by the action methods returns an object which also has an `abort()` function.
233 |
234 | So while with plain vanilla Traverson (not traverson-angular) you would abort a link traversal process like this
235 |
236 |
237 | var handle =
238 | traverson
239 | .from('http://api.example.com')
240 | .newRequest()
241 | .follow('link_to', 'resource')
242 | .getResource(...);
243 |
244 | // abort the link traversal
245 | handle.abort();
246 |
247 |
248 | ...this is how it is done with traverson-angular:
249 |
250 |
251 | var handle =
252 | traverson
253 | .from('http://api.example.com')
254 | .newRequest()
255 | .follow('link_to', 'resource')
256 | .getResource();
257 |
258 | // register callbacks
259 | handle.result.then(successCallback, errorCallback);
260 |
261 | // abort the link traversal
262 | handle.abort()
263 |
264 |
265 | traverson-angular With Media Type Plug-Ins
266 | ------------------------------------------
267 |
268 | You can use all media type plug-ins that are available for Traverson with traverson-angular. Here is how:
269 |
270 | * Make sure the JavaScript for the media type plug-in has been loaded (for example, add a script tag for traverson-hal.min.js).
271 | * Register the media type with a line like this: `traverson.registerMediaType(TraversonJsonHalAdapter.mediaType, TraversonJsonHalAdapter);`.
272 | * If necessary force Traverson to use the media type in question with `setMediaType(...)` (for HAL, you can use the convenience method `.jsonHal()` instead).
273 | * If necessary, add Accept headers so your server knows you want to receive a particular media type. Example: `.withRequestOptions({ headers: { 'accept': 'application/hal+json' } })`.
274 |
275 | Here is a snippet outlining how to use traverson-angular with [traverson-hal](https://github.com/traverson/traverson-hal):
276 |
277 | ```html
278 |
279 |
280 | ```
281 |
282 | ```javascript
283 | traverson.registerMediaType(TraversonJsonHalAdapter.mediaType,
284 | TraversonJsonHalAdapter);
285 | traverson
286 | .from(rootUri)
287 | .jsonHal()
288 | .withRequestOptions({ headers: { 'accept': 'application/hal+json' } })
289 | .follow(...)
290 | .getResource()
291 | .result
292 | .then(...);
293 | ```
294 |
295 | You can find a complete working example for integrating traverson-hal with traverson-anglar in [browser/example/hal.html](https://github.com/traverson/traverson-angular/blob/master/browser/example/hal.html) and [browser/example/hal.js](https://github.com/traverson/traverson-angular/blob/master/browser/example/hal.js).
296 |
297 |
298 | Contributing
299 | ------------
300 |
301 | See [Contributing to traverson-angular](https://github.com/traverson/traverson-angular/blob/master/CONTRIBUTING.md).
302 |
303 |
304 | Code of Conduct
305 | ---------------
306 |
307 | See [Code of Conduct](https://github.com/traverson/traverson-angular/blob/master/CODE_OF_CONDUCT.md).
308 |
309 |
310 | Release Notes
311 | -------------
312 |
313 | See [CHANGELOG](https://github.com/traverson/traverson-angular/blob/master/CHANGELOG.md).
314 |
315 |
316 | License
317 | -------
318 |
319 | MIT
320 |
--------------------------------------------------------------------------------
/test/abort_request.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var chai = require('chai')
4 | , sinon = require('sinon')
5 | , sinonChai = require('sinon-chai')
6 | , assert = chai.assert
7 | , expect = chai.expect
8 | , mockResponse = require('traverson-mock-response')()
9 | , waitFor = require('poll-forever')
10 | , events = require('events')
11 | , EventEmitter = events.EventEmitter
12 | , testUtil = require('./util')
13 | , traversonAngular = require('./angular_test_helper');
14 |
15 | chai.use(sinonChai);
16 |
17 | function RequestMock() {
18 | EventEmitter.call(this);
19 | }
20 | testUtil.inherits(RequestMock, EventEmitter);
21 |
22 | RequestMock.prototype.abort = function() {
23 | this.emit('abort');
24 | };
25 |
26 | describe('Aborting', function() {
27 |
28 | var rootUri = 'http://api.example.org';
29 | var api = traversonAngular.from(rootUri).json();
30 | var get;
31 | var successCallback;
32 | var errorCallback;
33 |
34 | beforeEach(function() {
35 | get = sinon.stub();
36 | api.requestModuleInstance = { get: get };
37 | successCallback = sinon.spy();
38 | errorCallback = sinon.spy();
39 | });
40 |
41 | it('should abort the link traversal process', function(done) {
42 | var path1 = rootUri + '/path/1';
43 | var path2 = rootUri + '/path/2';
44 |
45 | var root = mockResponse({ link1: path1 });
46 | var response2 = mockResponse({ link2: path2 });
47 | var result = mockResponse({ should: 'not reach this' });
48 |
49 | get.returns(new RequestMock());
50 |
51 | get
52 | .withArgs(rootUri, sinon.match.any, sinon.match.func)
53 | .callsArgWithAsync(2, null, root);
54 | var secondGet = get
55 | .withArgs(path1, sinon.match.any, sinon.match.func);
56 | secondGet
57 | .callsArgWithAsync(2, null, response2);
58 |
59 | var handle = api
60 | .newRequest()
61 | .follow('link1', 'link2', 'link3')
62 | .getResource();
63 |
64 | handle.result.then(successCallback, errorCallback);
65 |
66 | waitFor(
67 | function() {
68 | return secondGet.called;
69 | },
70 | function() {
71 | handle.abort();
72 | waitFor(
73 | function() {
74 | return errorCallback.called;
75 | },
76 | function() {
77 | assert(errorCallback.calledOnce);
78 | expect(successCallback).to.not.have.been.called;
79 | expect(errorCallback).to.have.been.calledWith(sinon.match.
80 | instanceOf(Error));
81 | var error = errorCallback.args[0][0];
82 | expect(error.message)
83 | .to.equal('Link traversal process has been aborted.');
84 | done();
85 | }
86 | );
87 | }
88 | );
89 | });
90 | });
91 |
--------------------------------------------------------------------------------
/test/angular_test_helper.js:
--------------------------------------------------------------------------------
1 | /* global angular */
2 | 'use strict';
3 |
4 | var testModule = angular.module('testModule', ['traverson'])
5 | , injector = angular.injector(['ng', 'testModule'])
6 | , traversonAngular = injector.get('traverson')
7 | ;
8 |
9 | module.exports = traversonAngular;
10 |
--------------------------------------------------------------------------------
/test/browser_suite.js:
--------------------------------------------------------------------------------
1 | require('./abort_request.js');
2 | require('./continue.js');
3 | require('./json_get_resource');
4 | require('./json_requests.js');
5 | require('./localhost.js');
6 | require('./using_angular_mocks.js');
7 |
--------------------------------------------------------------------------------
/test/continue.js:
--------------------------------------------------------------------------------
1 | /* global angular */
2 | /* jshint maxparams: 6 */
3 | /* jshint maxcomplexity: 12 */
4 |
5 | 'use strict';
6 |
7 | var util = require('util')
8 | , mockResponse = require('traverson-mock-response')()
9 | , waitFor = require('poll-forever')
10 | , chai = require('chai')
11 | , sinon = require('sinon')
12 | , sinonChai = require('sinon-chai')
13 | , assert = chai.assert
14 | , expect = chai.expect
15 | , traversonAngular = require('./angular_test_helper');
16 |
17 | chai.use(sinonChai);
18 |
19 | // TODOs:
20 | // - error handling in a continued traversal
21 | // - cloning with traversal.continue().newRequest(), splitting into multiple
22 | // follow up traversals
23 | // - abort a continuation
24 | // - mixed continuations (first with getResource second with get or vice versa
25 | // plus other combinations, getUrl, post, ...)
26 |
27 | describe('Continuation of traversals', function() {
28 |
29 | var get;
30 | var post;
31 | var put;
32 | var patch;
33 | var del;
34 |
35 | var successCallback1;
36 | var successCallback2;
37 | var errorCallback1;
38 | var errorCallback2;
39 |
40 | var rootUrl = 'http://api.example.org';
41 | var api = traversonAngular.from(rootUrl).json();
42 |
43 | var url1 = rootUrl + '/1';
44 | var rootResponse = mockResponse({ link1: url1 });
45 | var url2 = rootUrl + '/2';
46 | var response2 = mockResponse({ link2: url2 });
47 | var url3 = rootUrl + '/3';
48 | var response3 = mockResponse({ link3: url3 });
49 | var response4 = mockResponse({ foo: 'bar' });
50 |
51 | var payload = { some: 'stuff' };
52 |
53 | beforeEach(function() {
54 | get = sinon.stub();
55 | post = sinon.stub();
56 | put = sinon.stub();
57 | patch = sinon.stub();
58 | del = sinon.stub();
59 | api.requestModuleInstance = {
60 | get: get,
61 | post: post,
62 | put: put,
63 | patch: patch,
64 | del: del,
65 | };
66 | successCallback1 = sinon.spy();
67 | successCallback2 = sinon.spy();
68 | errorCallback1 = sinon.spy();
69 | errorCallback2 = sinon.spy();
70 | setupMocks();
71 | });
72 |
73 | describe('get', function() {
74 | defineTestsForMethod(api.get);
75 | });
76 |
77 | describe('getResource', function() {
78 | defineTestsForMethod(api.getResource);
79 | });
80 |
81 | describe('getUrl', function() {
82 | defineTestsForMethod(api.getUrl);
83 | });
84 |
85 | describe('post', function() {
86 | defineTestsForMethod(api.post, payload);
87 | });
88 |
89 | describe('put', function() {
90 | defineTestsForMethod(api.put, payload);
91 | });
92 |
93 | describe('patch', function() {
94 | defineTestsForMethod(api.patch, payload);
95 | });
96 |
97 | describe('delete', function() {
98 | defineTestsForMethod(api.delete);
99 | });
100 |
101 | it('should branch out with continue and newRequest', function(done) {
102 | var successCallback3 = sinon.spy();
103 | var errorCallback3 = sinon.spy();
104 | var successCallback4 = sinon.spy();
105 | var errorCallback4 = sinon.spy();
106 | var request = api
107 | .newRequest()
108 | .follow('link1')
109 | .getResource();
110 | request.result.then(successCallback1, errorCallback1);
111 | request.continue().then(function(nextBuilder) {
112 | var request1 = nextBuilder.newRequest();
113 | var request2 = nextBuilder.newRequest();
114 | request1
115 | .follow('link2')
116 | .getResource()
117 | .result
118 | .then(successCallback3, errorCallback3);
119 | request2
120 | .follow('link2', 'link3')
121 | .getResource()
122 | .result
123 | .then(successCallback4, errorCallback4);
124 | });
125 | waitFor(
126 | function() {
127 | console.log('successCallback1.called', successCallback1.called);
128 | console.log('successCallback3.called', successCallback3.called);
129 | console.log('successCallback4.called', successCallback4.called);
130 | return successCallback1.called &&
131 | successCallback3.called &&
132 | successCallback4.called;
133 | },
134 | function() {
135 | expect(errorCallback1).to.not.have.been.called;
136 | expect(errorCallback3).to.not.have.been.called;
137 | expect(errorCallback4).to.not.have.been.called;
138 | expect(successCallback1).to.have.been.calledWith(response2.doc);
139 | expect(successCallback3).to.have.been.calledWith(response3.doc);
140 | expect(successCallback4).to.have.been.calledWith(response4.doc);
141 | expect(get.callCount).to.equal(5);
142 | done();
143 | }
144 | );
145 | });
146 |
147 |
148 | function defineTestsForMethod(method, body) {
149 |
150 | it('should continue with links after a no-link traversal',
151 | function(done) {
152 | setupTest(
153 | method,
154 | body,
155 | [],
156 | ['link1', 'link2', 'link3'], {
157 | method: method,
158 | firstResponse: rootResponse,
159 | secondResponse: response4,
160 | expectedUrl1: rootUrl,
161 | expectedUrl2: url3,
162 | expectedNumberOfHttpGetRequests: 4,
163 | noLinksForSecondTraversal: false,
164 | }, done);
165 | });
166 |
167 | it('should continue with a link (1|1)', function(done) {
168 | setupTest(
169 | method,
170 | body,
171 | ['link1'],
172 | ['link2'], {
173 | method: method,
174 | firstResponse: response2,
175 | secondResponse: response3,
176 | expectedUrl1: url1,
177 | expectedUrl2: url2,
178 | expectedNumberOfHttpGetRequests: 3,
179 | noLinksForSecondTraversal: false,
180 | }, done);
181 | });
182 |
183 | it('should continue with a link (2|1)', function(done) {
184 | setupTest(
185 | method,
186 | body,
187 | ['link1', 'link2'],
188 | ['link3'], {
189 | method: method,
190 | firstResponse: response3,
191 | secondResponse: response4,
192 | expectedUrl1: url2,
193 | expectedUrl2: url3,
194 | expectedNumberOfHttpGetRequests: 4,
195 | noLinksForSecondTraversal: false,
196 | }, done);
197 | });
198 |
199 | it('should continue with a link (1|2)', function(done) {
200 | setupTest(
201 | method,
202 | body,
203 | ['link1'],
204 | ['link2', 'link3'], {
205 | method: method,
206 | firstResponse: response2,
207 | secondResponse: response4,
208 | expectedUrl1: url1,
209 | expectedUrl2: url3,
210 | expectedNumberOfHttpGetRequests: 4,
211 | noLinksForSecondTraversal: false,
212 | }, done);
213 | });
214 |
215 | it('should continue with no links', function(done) {
216 | setupTest(
217 | method,
218 | body,
219 | ['link1', 'link2', 'link3'],
220 | [], {
221 | method: method,
222 | firstResponse: response4,
223 | secondResponse: response4,
224 | expectedUrl1: url3,
225 | expectedUrl2: url3,
226 | expectedNumberOfHttpGetRequests: 4,
227 | noLinksForSecondTraversal: true,
228 | }, done);
229 | });
230 |
231 | it('should continue with no links after a no-link traversal',
232 | function(done) {
233 | setupTest(
234 | method,
235 | body,
236 | [],
237 | [], {
238 | method: method,
239 | firstResponse: rootResponse,
240 | secondResponse: rootResponse,
241 | expectedUrl1: rootUrl,
242 | expectedUrl2: rootUrl,
243 | expectedNumberOfHttpGetRequests: 1,
244 | noLinksForSecondTraversal: true,
245 | }, done);
246 | });
247 | } // function defineTestsForMethod
248 |
249 | function setupMocks() {
250 | [get, post, put, patch, del].forEach(function(fn) {
251 | fn
252 | .withArgs(rootUrl, sinon.match.object, sinon.match.func)
253 | .callsArgWithAsync(2, null, rootResponse);
254 | fn
255 | .withArgs(url1, sinon.match.object, sinon.match.func)
256 | .callsArgWithAsync(2, null, response2);
257 | fn
258 | .withArgs(url2, sinon.match.object, sinon.match.func)
259 | .callsArgWithAsync(2, null, response3);
260 | fn
261 | .withArgs(url3, sinon.match.object, sinon.match.func)
262 | .callsArgWithAsync(2, null, response4);
263 | });
264 | }
265 |
266 | function setupTest(method, body, links1, links2, results, done) {
267 | var builder = api.newRequest().follow(links1);
268 |
269 | var request = method.apply(builder, (body ? [body] : []));
270 | request.result.then(successCallback1, errorCallback1);
271 |
272 | request.continue().then(function(nextBuilder) {
273 | nextBuilder.follow(links2);
274 | method
275 | .apply(nextBuilder, (body ? [body] : []))
276 | .result
277 | .then(successCallback2, errorCallback2);
278 | });
279 |
280 | waitFor(
281 | function() {
282 | return successCallback1.called &&
283 | successCallback2.called;
284 | },
285 | function() {
286 | checkResult(results);
287 | done();
288 | }
289 | );
290 | }
291 |
292 | function checkResult(results) {
293 | expect(errorCallback1).to.not.have.been.called;
294 | expect(errorCallback2).to.not.have.been.called;
295 | if (results.method === api.get) {
296 | expect(successCallback1).to.have.been.calledWith(
297 | results.firstResponse);
298 | expect(successCallback2).to.have.been.calledWith(
299 | results.secondResponse);
300 | expect(get.callCount).to.equal(results.expectedNumberOfHttpGetRequests);
301 | } else if (results.method === api.getResource) {
302 | expect(successCallback1).to.have.been.calledWith(
303 | results.firstResponse.doc);
304 | expect(successCallback2).to.have.been.calledWith(
305 | results.secondResponse.doc);
306 | expect(get.callCount).to.equal(results.expectedNumberOfHttpGetRequests);
307 | } else if (results.method === api.getUrl) {
308 | expect(successCallback1).to.have.been.calledWith(
309 | results.expectedUrl1);
310 | expect(successCallback2).to.have.been.calledWith(
311 | results.expectedUrl2);
312 | expect(get.callCount).to.equal(
313 | results.expectedNumberOfHttpGetRequests - 1);
314 | } else if (results.method === api.post) {
315 | expect(successCallback1).to.have.been.calledWith(
316 | results.firstResponse);
317 | expect(successCallback2).to.have.been.calledWith(
318 | results.secondResponse);
319 | expect(get.callCount).to.equal(results.expectedNumberOfHttpGetRequests -
320 | (results.noLinksForSecondTraversal ? 1 : 2));
321 | expect(post.callCount).to.equal(2);
322 | } else if (results.method === api.put) {
323 | expect(successCallback1).to.have.been.calledWith(
324 | results.firstResponse);
325 | expect(successCallback2).to.have.been.calledWith(
326 | results.secondResponse);
327 | expect(get.callCount).to.equal(results.expectedNumberOfHttpGetRequests -
328 | (results.noLinksForSecondTraversal ? 1 : 2));
329 | expect(put.callCount).to.equal(2);
330 | } else if (results.method === api.patch) {
331 | expect(successCallback1).to.have.been.calledWith(
332 | results.firstResponse);
333 | expect(successCallback2).to.have.been.calledWith(
334 | results.secondResponse);
335 | expect(get.callCount).to.equal(results.expectedNumberOfHttpGetRequests -
336 | (results.noLinksForSecondTraversal ? 1 : 2));
337 | expect(patch.callCount).to.equal(2);
338 | } else if (results.method === api.delete) {
339 | expect(successCallback1).to.have.been.calledWith(
340 | results.firstResponse);
341 | expect(successCallback2).to.have.been.calledWith(
342 | results.secondResponse);
343 | expect(get.callCount).to.equal(results.expectedNumberOfHttpGetRequests -
344 | (results.noLinksForSecondTraversal ? 1 : 2));
345 | expect(del.callCount).to.equal(2);
346 | } else {
347 | throw new Error('Unknown method: ' + results.method.name + ': ' +
348 | results.method);
349 | }
350 | }
351 |
352 | });
353 |
--------------------------------------------------------------------------------
/test/json_get_resource.js:
--------------------------------------------------------------------------------
1 | /* global angular */
2 | 'use strict';
3 |
4 | var chai = require('chai')
5 | , sinon = require('sinon')
6 | , sinonChai = require('sinon-chai')
7 | , mockResponse = require('traverson-mock-response')()
8 | , waitFor = require('poll-forever')
9 | , assert = chai.assert
10 | , expect = chai.expect
11 | , traversonAngular = require('./angular_test_helper');
12 |
13 | chai.use(sinonChai);
14 |
15 | describe('traverson-angular', function() {
16 |
17 | var rootUri = 'http://api.example.org';
18 | var api = traversonAngular.from(rootUri).json();
19 | var get;
20 | var successCallback;
21 | var errorCallback;
22 |
23 | var result = mockResponse({ foo: 'bar' });
24 |
25 | beforeEach(function() {
26 | get = sinon.stub();
27 | api.requestModuleInstance = { get: get };
28 | successCallback = sinon.spy();
29 | errorCallback = sinon.spy();
30 | });
31 |
32 | describe('using Traverson\'s basic features', function() {
33 | var rootStep = {
34 | uri: rootUri
35 | };
36 | var rootResponse = mockResponse({
37 | irrelevant: { stuff: 'to be ignored' },
38 | link: rootUri + '/link/to/thing',
39 | more: { stuff: { that: 'we do not care about' } }
40 | });
41 |
42 | it('should access the root URI', function() {
43 | api
44 | .newRequest()
45 | .follow()
46 | .getResource()
47 | .result
48 | .then(successCallback, errorCallback);
49 | expect(get).to.have.been.calledWith(
50 | rootUri, sinon.match.any, sinon.match.func);
51 | });
52 |
53 | it('should call successCallback with the root doc', function(done) {
54 | get.callsArgWithAsync(2, null, rootResponse);
55 |
56 | api
57 | .newRequest()
58 | .follow()
59 | .getResource()
60 | .result
61 | .then(successCallback, errorCallback);
62 |
63 | waitFor(
64 | function() { return successCallback.called; },
65 | function() {
66 | expect(successCallback).to.have.been.calledWith(rootResponse.doc);
67 | expect(errorCallback).to.not.have.been.called;
68 | done();
69 | }
70 | );
71 | });
72 |
73 | it('should call errorCallback with err', function(done) {
74 | var err = new Error('test error');
75 | get.callsArgWithAsync(2, err);
76 |
77 | api
78 | .follow()
79 | .getResource()
80 | .result
81 | .then(successCallback, errorCallback);
82 |
83 | waitFor(
84 | function() { return errorCallback.called; },
85 | function() {
86 | expect(successCallback).to.not.have.been.called;
87 | expect(errorCallback).to.have.been.calledWith(err);
88 | done();
89 | }
90 | );
91 | });
92 |
93 | it('should follow a single element path', function(done) {
94 | get
95 | .withArgs(rootUri, sinon.match.any, sinon.match.func)
96 | .callsArgWithAsync(2, null, rootResponse);
97 | get
98 | .withArgs(rootUri + '/link/to/thing', sinon.match.any, sinon.match.func)
99 | .callsArgWithAsync(2, null, result);
100 |
101 | api
102 | .newRequest()
103 | .follow('link')
104 | .getResource()
105 | .result
106 | .then(successCallback, errorCallback);
107 |
108 | waitFor(
109 | function() { return successCallback.called; },
110 | function() {
111 | expect(successCallback).to.have.been.calledWith(result.doc);
112 | expect(errorCallback).to.not.have.been.called;
113 | done();
114 | }
115 | );
116 | });
117 |
118 | it('should follow a single element path as array', function(done) {
119 | get
120 | .withArgs(rootUri, sinon.match.any, sinon.match.func)
121 | .callsArgWithAsync(2, null, rootResponse);
122 | get
123 | .withArgs(rootUri + '/link/to/thing', sinon.match.any, sinon.match.func)
124 | .callsArgWithAsync(2, null, result);
125 |
126 | api
127 | .newRequest()
128 | .follow(['link'])
129 | .getResource()
130 | .result
131 | .then(successCallback, errorCallback);
132 |
133 | waitFor(
134 | function() { return successCallback.called; },
135 | function() {
136 | expect(successCallback).to.have.been.calledWith(result.doc);
137 | expect(errorCallback).to.not.have.been.called;
138 | done();
139 | }
140 | );
141 | });
142 |
143 | it('should call errorCallback with err if link is not found',
144 | function(done) {
145 | get
146 | .withArgs(rootUri, sinon.match.any, sinon.match.func)
147 | .callsArgWithAsync(2, null, rootResponse);
148 |
149 | api
150 | .newRequest()
151 | .follow('non-existing-link')
152 | .getResource()
153 | .result
154 | .then(successCallback, errorCallback);
155 |
156 | waitFor(
157 | function() { return errorCallback.called; },
158 | function() {
159 | expect(successCallback).to.not.have.been.called;
160 | assert(errorCallback.calledOnce);
161 | expect(errorCallback).to.have.been.calledWith(sinon.match.
162 | instanceOf(Error));
163 | expect(errorCallback.args[0][0].message).to.contain('Could not ' +
164 | 'find property non-existing-link');
165 | done();
166 | }
167 | );
168 | });
169 |
170 | it('should call errorCallback with err inside recursion', function(done) {
171 | var err = new Error('test error');
172 |
173 | get
174 | .withArgs(rootUri, sinon.match.any, sinon.match.func)
175 | .callsArgWithAsync(2, null,
176 | mockResponse({ firstLink: rootUri + '/first' }));
177 | get
178 | .withArgs(rootUri + '/first', sinon.match.any, sinon.match.func)
179 | .callsArgWithAsync(2, err);
180 |
181 | api
182 | .newRequest()
183 | .follow('firstLink')
184 | .getResource()
185 | .result
186 | .then(successCallback, errorCallback);
187 |
188 | waitFor(
189 | function() { return errorCallback.called; },
190 | function() {
191 | expect(successCallback).to.not.have.been.called;
192 | expect(errorCallback).to.have.been.calledWith(err);
193 | done();
194 | }
195 | );
196 | });
197 | });
198 | });
199 |
--------------------------------------------------------------------------------
/test/json_requests.js:
--------------------------------------------------------------------------------
1 | /* global angular */
2 | 'use strict';
3 |
4 | var chai = require('chai')
5 | , sinon = require('sinon')
6 | , sinonChai = require('sinon-chai')
7 | , mockResponse = require('traverson-mock-response')()
8 | , waitFor = require('poll-forever')
9 | , assert = chai.assert
10 | , expect = chai.expect
11 | , traversonAngular = require('./angular_test_helper');
12 |
13 | chai.use(sinonChai);
14 |
15 | /*
16 | * Tests for all of Json Walker's request methods except getResource, which is
17 | * tested extensively in json_get_resource.js. This test suite contains tests
18 | * for get, post, put, delete and patch. Each http method verb has it's own
19 | * describe-section. Since most of the code path is the same for getResource
20 | * and get, post, ..., there are just a few basic tests here for each verb.
21 | * The getResource tests are more comprehensive.
22 | */
23 | describe('The JSON client\'s', function() {
24 |
25 | var get;
26 | var post;
27 | var put;
28 | var patch;
29 | var del;
30 |
31 | var successCallback;
32 | var errorCallback;
33 |
34 | var rootUri = 'http://api.io';
35 | var api = traversonAngular.from(rootUri).json();
36 |
37 | var getUri = rootUri + '/link/to/resource';
38 | var postUri = rootUri + '/post/something/here';
39 | var putUri = rootUri + '/put/something/here';
40 | var patchUri = rootUri + '/patch/me';
41 | var deleteUri = rootUri + '/delete/me';
42 | var templateUri = rootUri + '/template/{param}';
43 |
44 | var rootResponse = mockResponse({
45 | 'get_link': getUri,
46 | 'post_link': postUri,
47 | 'put_link': putUri,
48 | 'patch_link': patchUri,
49 | 'delete_link': deleteUri,
50 | 'template_link': templateUri
51 | });
52 |
53 | var result = mockResponse({ result: 'success' });
54 |
55 | var payload = {
56 | some: 'stuff',
57 | data: 4711
58 | };
59 |
60 | beforeEach(function() {
61 | get = sinon.stub();
62 | post = sinon.stub();
63 | put = sinon.stub();
64 | patch = sinon.stub();
65 | del = sinon.stub();
66 | api.requestModuleInstance = {
67 | get: get,
68 | post: post,
69 | put: put,
70 | patch: patch,
71 | del: del,
72 | };
73 | successCallback = sinon.spy();
74 | errorCallback = sinon.spy();
75 |
76 | get
77 | .withArgs(rootUri, sinon.match.any, sinon.match.func)
78 | .callsArgWithAsync(2, null, rootResponse, rootResponse.body);
79 | get
80 | .withArgs(getUri, sinon.match.any, sinon.match.func)
81 | .callsArgWithAsync(2, null, result, result.body);
82 | get
83 | .withArgs(postUri, sinon.match.any, sinon.match.func)
84 | .callsArgWithAsync(2,
85 | new Error('GET is not implemented for this URL, please POST ' +
86 | 'something'));
87 | });
88 |
89 | describe('get method', function() {
90 |
91 | it('should follow the links', function(done) {
92 | api
93 | .newRequest()
94 | .follow('get_link')
95 | .get()
96 | .result
97 | .then(successCallback, errorCallback);
98 |
99 | waitFor(
100 | function() { return successCallback.called; },
101 | function() {
102 | expect(successCallback).to.have.been.calledWith(result);
103 | expect(errorCallback).to.not.have.been.called;
104 | done();
105 | }
106 | );
107 | });
108 |
109 | it('should call errorCallback with err', function(done) {
110 | var err = new Error('test error');
111 | // Default stubbing from beforeEach is not what we want here.
112 | // IMO, get.reset() should be enough, but isnt?
113 | get = sinon.stub();
114 | api.requestModuleInstance = { get: get };
115 | get.callsArgWithAsync(2, err);
116 |
117 | api
118 | .newRequest()
119 | .get()
120 | .result
121 | .then(successCallback, errorCallback);
122 |
123 | waitFor(
124 | function() { return errorCallback.called; },
125 | function() {
126 | expect(successCallback).to.not.have.been.called;
127 | expect(errorCallback).to.have.been.calledWith(err);
128 | done();
129 | }
130 | );
131 | });
132 |
133 | it('should call errorCallback with err when walking along the links fails',
134 | function(done) {
135 | var err = new Error('test error');
136 | // Default stubbing from beforeEach is not what we want here.
137 | // IMO, get.reset() should be enough, but isnt?
138 | get = sinon.stub();
139 | api.requestModuleInstance = { get: get };
140 | get
141 | .withArgs(rootUri, sinon.match.any, sinon.match.func)
142 | .callsArgWithAsync(2, null, rootResponse);
143 | get
144 | .withArgs(getUri, sinon.match.any, sinon.match.func)
145 | .callsArgWithAsync(2, err);
146 |
147 | api
148 | .newRequest()
149 | .follow('get_link', 'another_link')
150 | .get()
151 | .result
152 | .then(successCallback, errorCallback);
153 |
154 | waitFor(
155 | function() { return errorCallback.called; },
156 | function() {
157 | expect(successCallback).to.not.have.been.called;
158 | expect(errorCallback).to.have.been.calledWith(err);
159 | done();
160 | }
161 | );
162 | });
163 |
164 | });
165 |
166 | describe('getUrl method', function() {
167 |
168 | it('should follow the links and yield the last URL', function(done) {
169 | api
170 | .newRequest()
171 | .follow('get_link')
172 | .getUrl()
173 | .result
174 | .then(successCallback, errorCallback);
175 |
176 | waitFor(
177 | function() { return successCallback.called; },
178 | function() {
179 | expect(successCallback).to.have.been.calledWith(getUri);
180 | expect(errorCallback).to.not.have.been.called;
181 | expect(get.callCount).to.equal(1);
182 | done();
183 | }
184 | );
185 | });
186 |
187 | it('should yield resolved URL if last URL is a URI template',
188 | function(done) {
189 | api
190 | .newRequest()
191 | .follow('template_link')
192 | .withTemplateParameters({ param: 'substituted' })
193 | .getUrl()
194 | .result
195 | .then(successCallback, errorCallback);
196 |
197 | waitFor(
198 | function() { return successCallback.called; },
199 | function() {
200 | expect(successCallback).to.have.been.calledWith(
201 | rootUri + '/template/substituted');
202 | expect(errorCallback).to.not.have.been.called;
203 | expect(get.callCount).to.equal(1);
204 | done();
205 | }
206 | );
207 | });
208 | });
209 |
210 |
211 | describe('post method', function() {
212 |
213 | var result = mockResponse({ result: 'success' }, 201);
214 |
215 | it('should follow the links and post to the last URL',
216 | function(done) {
217 | post
218 | .withArgs(postUri, sinon.match.object, sinon.match.func)
219 | .callsArgWithAsync(2, null, result);
220 |
221 | api
222 | .newRequest()
223 | .follow('post_link')
224 | .post(payload)
225 | .result
226 | .then(successCallback, errorCallback);
227 |
228 | waitFor(
229 | function() { return successCallback.called; },
230 | function() {
231 | expect(errorCallback).to.not.have.been.called;
232 | expect(successCallback).to.have.been.calledWith(result);
233 | expect(post.firstCall.args[1].body).to.exist;
234 | expect(post.firstCall.args[1].body).to.contain(payload.some);
235 | expect(post.firstCall.args[1].body).to.contain(payload.data);
236 | done();
237 | }
238 | );
239 | });
240 |
241 | it('should call errorCallback with err when post fails',
242 | function(done) {
243 | var err = new Error('test error');
244 | post
245 | .withArgs(postUri, sinon.match.object, sinon.match.func)
246 | .callsArgWithAsync(2, err);
247 |
248 | api
249 | .newRequest()
250 | .follow('post_link')
251 | .post(payload)
252 | .result
253 | .then(successCallback, errorCallback);
254 |
255 | waitFor(
256 | function() { return errorCallback.called; },
257 | function() {
258 | expect(errorCallback).to.have.been.calledWith(err);
259 | expect(successCallback).to.not.have.been.called;
260 | done();
261 | }
262 | );
263 | });
264 |
265 | });
266 |
267 | describe('put method', function() {
268 |
269 | var result = mockResponse({ result: 'success' }, 200);
270 |
271 | it('should follow the links and put to the last URL',
272 | function(done) {
273 | put
274 | .withArgs(putUri, sinon.match.object, sinon.match.func)
275 | .callsArgWithAsync(2, null, result);
276 |
277 | api
278 | .newRequest()
279 | .follow('put_link')
280 | .put(payload)
281 | .result
282 | .then(successCallback, errorCallback);
283 |
284 | waitFor(
285 | function() { return successCallback.called; },
286 | function() {
287 | expect(successCallback).to.have.been.calledWith(result);
288 | expect(errorCallback).to.not.have.been.called;
289 | expect(put.firstCall.args[1].body).to.exist;
290 | expect(put.firstCall.args[1].body).to.contain(payload.some);
291 | expect(put.firstCall.args[1].body).to.contain(payload.data);
292 | done();
293 | }
294 | );
295 | });
296 |
297 | it('should call errorCallback with err when put fails',
298 | function(done) {
299 | var err = new Error('test error');
300 | put
301 | .withArgs(putUri, sinon.match.object, sinon.match.func)
302 | .callsArgWithAsync(2, err);
303 |
304 | api
305 | .newRequest()
306 | .follow('put_link')
307 | .put(payload)
308 | .result
309 | .then(successCallback, errorCallback);
310 |
311 | waitFor(
312 | function() { return errorCallback.called; },
313 | function() {
314 | expect(errorCallback).to.have.been.calledWith(err);
315 | expect(successCallback).to.not.have.been.called;
316 | done();
317 | }
318 | );
319 | });
320 | });
321 |
322 | describe('patch method', function() {
323 |
324 | var result = mockResponse({ result: 'success' }, 200);
325 |
326 | it('should follow the links and patch the last URL',
327 | function(done) {
328 | patch
329 | .withArgs(patchUri, sinon.match.object, sinon.match.func)
330 | .callsArgWithAsync(2, null, result);
331 |
332 | api
333 | .newRequest()
334 | .follow('patch_link')
335 | .patch(payload)
336 | .result
337 | .then(successCallback, errorCallback);
338 |
339 | waitFor(
340 | function() { return successCallback.called; },
341 | function() {
342 | expect(successCallback).to.have.been.calledWith(result);
343 | expect(errorCallback).to.not.have.been.called;
344 | expect(patch.firstCall.args[1].body).to.exist;
345 | expect(patch.firstCall.args[1].body).to.contain(payload.some);
346 | expect(patch.firstCall.args[1].body).to.contain(payload.data);
347 | done();
348 | }
349 | );
350 | });
351 |
352 | it('should call errorCallback with err when patch fails',
353 | function(done) {
354 | var err = new Error('test error');
355 | patch
356 | .withArgs(patchUri, sinon.match.object, sinon.match.func)
357 | .callsArgWithAsync(2, err);
358 |
359 | api
360 | .newRequest()
361 | .follow('patch_link')
362 | .patch(payload)
363 | .result
364 | .then(successCallback, errorCallback);
365 |
366 | waitFor(
367 | function() { return errorCallback.called; },
368 | function() {
369 | expect(errorCallback).to.have.been.calledWith(err);
370 | expect(successCallback).to.not.have.been.called;
371 | done();
372 | }
373 | );
374 | });
375 | });
376 |
377 | describe('delete method', function() {
378 |
379 | var result = mockResponse(null, 204);
380 |
381 | it('should follow the links and delete the last URL',
382 | function(done) {
383 | del
384 | .withArgs(deleteUri, sinon.match.object, sinon.match.func)
385 | .callsArgWithAsync(2, null, result);
386 |
387 | api
388 | .newRequest()
389 | .follow('delete_link')
390 | .delete()
391 | .result
392 | .then(successCallback, errorCallback);
393 |
394 | waitFor(
395 | function() { return successCallback.called; },
396 | function() {
397 | expect(successCallback).to.have.been.calledWith(result);
398 | expect(errorCallback).to.not.have.been.called;
399 | done();
400 | }
401 | );
402 | });
403 |
404 | it('should call errorCallback with err when deleting fails',
405 | function(done) {
406 | var err = new Error('test error');
407 | del
408 | .withArgs(deleteUri, sinon.match.object, sinon.match.func)
409 | .callsArgWithAsync(2, err);
410 |
411 | api
412 | .newRequest()
413 | .follow('delete_link')
414 | .del()
415 | .result
416 | .then(successCallback, errorCallback);
417 |
418 | waitFor(
419 | function() { return errorCallback.called; },
420 | function() {
421 | expect(errorCallback).to.have.been.calledWith(err);
422 | expect(successCallback).to.not.have.been.called;
423 | done();
424 | }
425 | );
426 | });
427 | });
428 | });
429 |
--------------------------------------------------------------------------------
/test/localhost.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var waitFor = require('poll-forever')
4 | , chai = require('chai')
5 | , sinon = require('sinon')
6 | , sinonChai = require('sinon-chai')
7 | , traversonAngular = require('./angular_test_helper')
8 | , assert = chai.assert
9 | , expect = chai.expect;
10 |
11 | chai.use(sinonChai);
12 |
13 | describe('traverson-angular (when tested against a local server)', function() {
14 |
15 | var api;
16 | var testServer;
17 | var rootUri = 'http://127.0.0.1:2808/';
18 | var successCallback;
19 | var errorCallback;
20 |
21 | beforeEach(function() {
22 | api = traversonAngular
23 | .from(rootUri)
24 | .json()
25 | .withRequestOptions({
26 | headers: {
27 | 'Accept': 'application/json',
28 | 'Content-Type': 'application/json'
29 | }
30 | });
31 | successCallback = sinon.spy();
32 | errorCallback = sinon.spy();
33 | });
34 |
35 | it('should fetch the root response', function(done) {
36 | api
37 | .newRequest()
38 | .get()
39 | .result
40 | .then(successCallback, errorCallback);
41 | waitFor(
42 | function() { return successCallback.called; },
43 | function() {
44 | var resultDoc = checkResponseWithBody();
45 | expect(resultDoc.first).to.exist;
46 | expect(resultDoc.first).to.equal(rootUri + 'first');
47 | done();
48 | }
49 | );
50 | });
51 |
52 | it('should fetch the root document', function(done) {
53 | api
54 | .newRequest()
55 | .getResource()
56 | .result
57 | .then(successCallback, errorCallback);
58 | waitFor(
59 | function() { return successCallback.called; },
60 | function() {
61 | var resultDoc = checkResultDoc();
62 | expect(resultDoc.first).to.exist;
63 | expect(resultDoc.first).to.equal(rootUri + 'first');
64 | done();
65 | }
66 | );
67 | });
68 |
69 | it('should follow a single element path', function(done) {
70 | api
71 | .newRequest()
72 | .follow('first')
73 | .getResource()
74 | .result
75 | .then(successCallback, errorCallback);
76 | waitFor(
77 | function() { return successCallback.called; },
78 | function() {
79 | var resultDoc = checkResultDoc();
80 | expect(resultDoc.first).to.exist;
81 | expect(resultDoc.first).to.equal('document');
82 | done();
83 | }
84 | );
85 | });
86 |
87 | it('should follow a multi-element path', function(done) {
88 | api
89 | .newRequest()
90 | .follow('second', 'doc')
91 | .get()
92 | .result
93 | .then(successCallback, errorCallback);
94 | waitFor(
95 | function() { return successCallback.called; },
96 | function() {
97 | var resultDoc = checkResponseWithBody();
98 | expect(resultDoc.second).to.exist;
99 | expect(resultDoc.second).to.equal('document');
100 | done();
101 | }
102 | );
103 | });
104 |
105 | it('should follow a multi-element path (/w AngularJS\' $http)',
106 | function(done) {
107 | api
108 | .newRequest()
109 | .useAngularHttp()
110 | .follow('second', 'doc')
111 | .get()
112 | .result
113 | .then(successCallback, errorCallback);
114 | waitFor(
115 | function() { return successCallback.called; },
116 | function() {
117 | var resultDoc = checkResponseWithBody();
118 | expect(resultDoc.second).to.exist;
119 | expect(resultDoc.second).to.equal('document');
120 | done();
121 | }
122 | );
123 | });
124 |
125 | it('should follow a multi-element path to a resource', function(done) {
126 | api
127 | .newRequest()
128 | .follow('second', 'doc')
129 | .getResource()
130 | .result
131 | .then(successCallback, errorCallback);
132 | waitFor(
133 | function() { return successCallback.called; },
134 | function() {
135 | var resultDoc = checkResultDoc();
136 | expect(resultDoc.second).to.exist;
137 | expect(resultDoc.second).to.equal('document');
138 | done();
139 | }
140 | );
141 | });
142 |
143 | it('should follow a multi-element path to a resource (/w AngularJS\' $http)',
144 | function(done) {
145 | api
146 | .newRequest()
147 | .useAngularHttp()
148 | .follow('second', 'doc')
149 | .getResource()
150 | .result
151 | .then(successCallback, errorCallback);
152 | waitFor(
153 | function() { return successCallback.called; },
154 | function() {
155 | var resultDoc = checkResultDoc();
156 | expect(resultDoc.second).to.exist;
157 | expect(resultDoc.second).to.equal('document');
158 | done();
159 | }
160 | );
161 | });
162 |
163 | it('should authenticate', function(done) {
164 | api
165 | .newRequest()
166 | .withRequestOptions({
167 | auth: {
168 | user: 'traverson',
169 | pass: 'verysecretpassword',
170 | sendImmediately: false
171 | }
172 | })
173 | .follow('auth')
174 | .getResource()
175 | .result
176 | .then(successCallback, errorCallback);
177 | waitFor(
178 | function() { return successCallback.called; },
179 | function() {
180 | var resultDoc = checkResultDoc();
181 | expect(resultDoc.user).to.exist;
182 | expect(resultDoc.user).to.equal('authenticated');
183 | done();
184 | }
185 | );
186 | });
187 |
188 | it('should authenticate (/w AngularJS\' $http)', function(done) {
189 | api
190 | .newRequest()
191 | .useAngularHttp()
192 | .withRequestOptions({
193 | auth: {
194 | user: 'traverson',
195 | pass: 'verysecretpassword',
196 | sendImmediately: false
197 | }
198 | })
199 | .follow('auth')
200 | .getResource()
201 | .result
202 | .then(successCallback, errorCallback);
203 | waitFor(
204 | function() { return successCallback.called; },
205 | function() {
206 | var resultDoc = checkResultDoc();
207 | expect(resultDoc.user).to.exist;
208 | expect(resultDoc.user).to.equal('authenticated');
209 | done();
210 | }
211 | );
212 | });
213 |
214 | it('should leverage JSONPath', function(done) {
215 | api
216 | .newRequest()
217 | .follow('$.jsonpath.nested.key')
218 | .getResource()
219 | .result
220 | .then(successCallback, errorCallback);
221 | waitFor(
222 | function() { return successCallback.called; },
223 | function() {
224 | var resultDoc = checkResultDoc();
225 | expect(resultDoc.third).to.exist;
226 | expect(resultDoc.third).to.equal('document');
227 | done();
228 | }
229 | );
230 | });
231 |
232 | it('should leverage URI templates', function(done) {
233 | api
234 | .newRequest()
235 | .withTemplateParameters({param: 'foobar', id: 13})
236 | .follow('uri_template')
237 | .getResource()
238 | .result
239 | .then(successCallback, errorCallback);
240 | waitFor(
241 | function() { return successCallback.called; },
242 | function() {
243 | var resultDoc = checkResultDoc();
244 | expect(resultDoc.some).to.equal('document');
245 | expect(resultDoc.param).to.equal('foobar');
246 | expect(resultDoc.id).to.equal('13');
247 | done();
248 | }
249 | );
250 | });
251 |
252 | it('should follow the location header', function(done) {
253 | api
254 | .newRequest()
255 | .follow('respond_location')
256 | .followLocationHeader()
257 | .follow('doc')
258 | .getResource()
259 | .result
260 | .then(successCallback, errorCallback);
261 | waitFor(
262 | function() { return successCallback.called; },
263 | function() {
264 | var resultDoc = checkResultDoc();
265 | expect(resultDoc).to.eql({ second: 'document' });
266 | done();
267 | }
268 | );
269 | });
270 |
271 | // this is a 404 *during* the traversal, which is interpreted as an error
272 | // condition
273 | it('should fail gracefully on 404 during traversal (get())', function(done) {
274 | api
275 | .newRequest()
276 | .follow('blind_alley', 'more', 'links')
277 | .get()
278 | .result
279 | .then(successCallback, errorCallback);
280 | waitFor(
281 | function() { return errorCallback.called; },
282 | function() {
283 | expect(successCallback.callCount).to.equal(0);
284 | expect(errorCallback.callCount).to.equal(1);
285 | var error = errorCallback.firstCall.args[0];
286 | expect(error).to.exist;
287 | expect(error.name).to.equal('HTTPError');
288 | expect(error.message).to.equal('HTTP GET request to ' + rootUri +
289 | 'does/not/exist' + ' resulted in HTTP status code 404.');
290 | expect(error.url).to.equal(rootUri + 'does/not/exist');
291 | expect(error.httpStatus).to.equal(404);
292 |
293 | var lastBody = error.body;
294 | expect(lastBody).to.exist;
295 | expect(lastBody).to.contain('message');
296 | expect(lastBody).to.contain('resource not found');
297 | done();
298 | }
299 | );
300 | });
301 |
302 | // same as above (that is, a 404 *during* the traversal, which is interpreted
303 | // as an error condition), this time using AngularJS' $http service
304 | it('should fail gracefully on 404 during traversal ' +
305 | '(get(), /w AngularJS\' $http)', function(done) {
306 | api
307 | .newRequest()
308 | .useAngularHttp()
309 | .follow('blind_alley', 'more', 'links')
310 | .get()
311 | .result
312 | .then(successCallback, errorCallback);
313 | waitFor(
314 | function() { return errorCallback.called; },
315 | function() {
316 | expect(successCallback.callCount).to.equal(0);
317 | expect(errorCallback.callCount).to.equal(1);
318 | var error = errorCallback.firstCall.args[0];
319 |
320 | expect(error).to.exist;
321 | expect(error.name).to.equal('HTTPError');
322 | expect(error.message).to.equal('HTTP GET request to ' + rootUri +
323 | 'does/not/exist' + ' resulted in HTTP status code 404.');
324 | expect(error.url).to.equal(rootUri + 'does/not/exist');
325 | expect(error.httpStatus).to.equal(404);
326 |
327 | var lastBody = error.body;
328 | expect(lastBody).to.exist;
329 | expect(lastBody).to.contain('message');
330 | expect(lastBody).to.contain('resource not found');
331 | done();
332 | }
333 | );
334 | });
335 |
336 | // this is a 404 *at the end* of the traversal, which is *not* interpreted as
337 | // an error condition
338 | it('should just deliver the last response of get(), even when the last ' +
339 | 'response is a 404',
340 | function(done) {
341 | api
342 | .newRequest()
343 | .follow('blind_alley')
344 | .get()
345 | .result
346 | .then(successCallback, errorCallback);
347 | waitFor(
348 | function() { return successCallback.called; },
349 | function() {
350 | var resultDoc = checkResponseWithBody(404);
351 | expect(resultDoc).to.exist;
352 | expect(resultDoc.message).to.exist;
353 | expect(resultDoc.message).to.equal('resource not found');
354 | done();
355 | }
356 | );
357 | });
358 |
359 | // same as above, that is, a 404 *at the end* of the traversal, which is
360 | // *not* interpreted as an error condition, this time using AngularJS' $http
361 | // service
362 | it('should just deliver the last response of get(), even when the last ' +
363 | 'response is a 404 (/w AngularJS\' $http)',
364 | function(done) {
365 | api
366 | .newRequest()
367 | .useAngularHttp()
368 | .follow('blind_alley')
369 | .get()
370 | .result
371 | .then(successCallback, errorCallback);
372 | waitFor(
373 | function() { return successCallback.called; },
374 | function() {
375 | var resultDoc = checkResponseWithBody(404);
376 | expect(resultDoc).to.exist;
377 | expect(resultDoc.message).to.exist;
378 | expect(resultDoc.message).to.equal('resource not found');
379 | done();
380 | }
381 | );
382 | });
383 |
384 | /*
385 | it('https://github.com/traverson/traverson-angular/issues/14',
386 | function(done) {
387 | traversonAngular
388 | .from(rootUri)
389 | .useAngularHttp()
390 | .follow()
391 | .post({})
392 | .result
393 | .then(successCallback, errorCallback);
394 |
395 | waitFor(
396 | function() { return successCallback.called || errorCallback.called; },
397 | function() {
398 | console.log('successCallback.called', successCallback.called);
399 | console.log('errorCallback.called', errorCallback.called);
400 | done();
401 | }
402 | );
403 | });
404 | */
405 |
406 | // again, 404 during traversal => error, this time with getResouce()
407 | it('should fail gracefully on 404 during traversal (getResource())',
408 | function(done) {
409 | api
410 | .newRequest()
411 | .follow('blind_alley')
412 | .getResource().result.then(successCallback, errorCallback);
413 | waitFor(
414 | function() { return errorCallback.called; },
415 | function() {
416 | expect(successCallback.callCount).to.equal(0);
417 | expect(errorCallback.callCount).to.equal(1);
418 | var error = errorCallback.firstCall.args[0];
419 | expect(error).to.exist;
420 | expect(error.name).to.equal('HTTPError');
421 | expect(error.message).to.equal('HTTP GET request to ' + rootUri +
422 | 'does/not/exist' + ' resulted in HTTP status code 404.');
423 | expect(error.url).to.equal(rootUri + 'does/not/exist');
424 | expect(error.httpStatus).to.equal(404);
425 |
426 | var lastBody = error.body;
427 | expect(lastBody).to.exist;
428 | expect(lastBody).to.contain('message');
429 | expect(lastBody).to.contain('resource not found');
430 | done();
431 | }
432 | );
433 | });
434 |
435 | it('should fail gracefully on syntactically incorrect JSON',
436 | function(done) {
437 | traversonAngular
438 | .from(rootUri)
439 | .json()
440 | .follow('garbage')
441 | .getResource().result.then(successCallback, errorCallback);
442 | waitFor(
443 | function() { return errorCallback.called; },
444 | function() {
445 | expect(successCallback.callCount).to.equal(0);
446 | expect(errorCallback.callCount).to.equal(1);
447 | var error = errorCallback.firstCall.args[0];
448 | expect(error).to.exist;
449 | expect(error.name).to.equal('JSONError');
450 | expect(error.message).to.equal('The document at ' + rootUri + 'junk' +
451 | ' could not be parsed as JSON: { this will :: not parse');
452 | expect(error.url).to.equal(rootUri + 'junk');
453 | expect(error.body).to.equal('{ this will :: not parse');
454 | done();
455 | }
456 | );
457 | });
458 |
459 | it('should abort a link traversal process and the current request',
460 | function(done) {
461 | var handle =
462 | api
463 | .newRequest()
464 | .follow('second', 'doc')
465 | .getResource();
466 |
467 | handle.result.then(successCallback, errorCallback);
468 | handle.abort();
469 | waitFor(
470 | function() { return errorCallback.called; },
471 | function() {
472 | expect(successCallback.callCount).to.equal(0);
473 | expect(errorCallback.callCount).to.equal(1);
474 | var error = errorCallback.firstCall.args[0];
475 | expect(error).to.exist;
476 | expect(error.message).to.equal(
477 | 'Link traversal process has been aborted.');
478 | done();
479 | }
480 | );
481 | });
482 |
483 | it('should abort a post request',
484 | function(done) {
485 | var handle =
486 | api
487 | .newRequest()
488 | .post({});
489 |
490 | handle.result.then(successCallback, errorCallback);
491 | handle.abort();
492 | waitFor(
493 | function() { return errorCallback.called; },
494 | function() {
495 | expect(successCallback.callCount).to.equal(0);
496 | expect(errorCallback.callCount).to.equal(1);
497 | var error = errorCallback.firstCall.args[0];
498 | expect(error).to.exist;
499 | expect(error.message).to.equal(
500 | 'Link traversal process has been aborted.');
501 | done();
502 | }
503 | );
504 | });
505 |
506 | it('should yield the last URI', function(done) {
507 | api
508 | .newRequest()
509 | .follow('second', 'doc')
510 | .getUrl()
511 | .result
512 | .then(successCallback, errorCallback);
513 | waitFor(
514 | function() { return successCallback.called; },
515 | function() {
516 | expect(successCallback.callCount).to.equal(1);
517 | expect(errorCallback.callCount).to.equal(0);
518 | var result = successCallback.firstCall.args[0];
519 | expect(result).to.exist;
520 | expect(result).to.equal(rootUri + 'second/document');
521 | done();
522 | }
523 | );
524 | });
525 |
526 | it('should post', function(done) {
527 | var payload = {'new': 'document'};
528 | api
529 | .newRequest()
530 | .follow('post_link')
531 | .post(payload)
532 | .result
533 | .then(successCallback, errorCallback);
534 | waitFor(
535 | function() { return successCallback.called; },
536 | function() {
537 | var resultDoc = checkResponseWithBody(201);
538 | expect(resultDoc.document).to.exist;
539 | expect(resultDoc.document).to.equal('created');
540 | expect(resultDoc.received).to.exist;
541 | expect(resultDoc.received).to.deep.equal(payload);
542 | done();
543 | }
544 | );
545 | });
546 |
547 | it('should put', function(done) {
548 | var payload = {'updated': 'document'};
549 | api
550 | .newRequest()
551 | .follow('put_link')
552 | .put(payload)
553 | .result
554 | .then(successCallback, errorCallback);
555 | waitFor(
556 | function() { return successCallback.called; },
557 | function() {
558 | var resultDoc = checkResponseWithBody();
559 | expect(resultDoc.document).to.exist;
560 | expect(resultDoc.document).to.equal('overwritten');
561 | expect(resultDoc.received).to.exist;
562 | expect(resultDoc.received).to.deep.equal(payload);
563 | done();
564 | }
565 | );
566 | });
567 |
568 | // This test will not work via mocha-phantomjs since PhantomJS currently
569 | // sends an empty body with a PATCH request, see
570 | // https://github.com/ariya/phantomjs/issues/11384
571 | it.skip('should patch', function(done) {
572 | var payload = {'patched': 'document'};
573 | api
574 | .newRequest()
575 | .follow('patch_link')
576 | .patch(payload)
577 | .result
578 | .then(successCallback, errorCallback);
579 | waitFor(
580 | function() { return successCallback.called; },
581 | function() {
582 | var resultDoc = checkResponseWithBody();
583 | expect(resultDoc.document).to.exist;
584 | expect(resultDoc.document).to.equal('patched');
585 | expect(resultDoc.received).to.exist;
586 | expect(resultDoc.received).to.deep.equal(payload);
587 | done();
588 | }
589 | );
590 | });
591 |
592 | it('should delete', function(done) {
593 | api
594 | .newRequest()
595 | .follow('delete_link')
596 | .delete()
597 | .result
598 | .then(successCallback, errorCallback);
599 | waitFor(
600 | function() { return successCallback.called; },
601 | function() {
602 | var response = checkResponse(204);
603 | done();
604 | }
605 | );
606 | });
607 |
608 | it('should use provided request options', function(done) {
609 | api
610 | .newRequest()
611 | .withRequestOptions({
612 | headers: {
613 | 'Accept': 'application/json',
614 | 'X-Traverson-Test-Header': 'Traverson rocks!'
615 | }
616 | })
617 | .follow('echo-headers')
618 | .getResource()
619 | .result
620 | .then(successCallback, errorCallback);
621 | waitFor(
622 | function() { return successCallback.called; },
623 | function() {
624 | var resultDoc = checkResultDoc();
625 | var testResponseHeader =
626 | resultDoc['X-Traverson-Test-Header'] ||
627 | resultDoc['x-traverson-test-header'];
628 | expect(testResponseHeader).to.exist;
629 | expect(testResponseHeader).to.equal('Traverson rocks!');
630 | done();
631 | }
632 | );
633 | });
634 |
635 | it('should use provided request options (/w AngularJS\' $http)',
636 | function(done) {
637 | var payload = {'updated': 'document'};
638 | api
639 | .newRequest()
640 | .follow('put_link')
641 | .useAngularHttp()
642 | .withRequestOptions({
643 | headers: {
644 | 'Content-Type': 'application/json',
645 | 'If-Match': 'something'
646 | }
647 | })
648 | .put(payload)
649 | .result
650 | .then(successCallback, errorCallback);
651 | waitFor(
652 | function() { return successCallback.called; },
653 | function() {
654 | var resultDoc = checkResponseWithBody();
655 | expect(resultDoc.document).to.exist;
656 | expect(resultDoc.document).to.equal('overwritten');
657 | expect(resultDoc.received).to.exist;
658 | expect(resultDoc.received).to.deep.equal(payload);
659 | expect(resultDoc.headers).to.exist;
660 | expect(resultDoc.headers['content-type']).to.equal('application/json');
661 | expect(resultDoc.headers['if-match']).to.equal('something');
662 | done();
663 | }
664 | );
665 | });
666 |
667 | it('should use provided query string options', function(done) {
668 | api
669 | .newRequest()
670 | .withRequestOptions({
671 | qs: {
672 | 'token': 'foobar'
673 | }
674 | })
675 | .follow('echo-query')
676 | .getResource()
677 | .result
678 | .then(successCallback, errorCallback);
679 | waitFor(
680 | function() { return successCallback.called; },
681 | function() {
682 | var resultDoc = checkResultDoc();
683 | expect(resultDoc.token).to.exist;
684 | expect(resultDoc.token).to.equal('foobar');
685 | done();
686 | }
687 | );
688 | });
689 |
690 | it('should add request options on top of each other', function(done) {
691 | api
692 | .newRequest()
693 | .addRequestOptions({
694 | headers: { 'Accept': 'application/json', }
695 | })
696 | .addRequestOptions({
697 | headers: { 'X-Traverson-Test-Header': 'Traverson rocks!' }
698 | })
699 | .addRequestOptions({
700 | qs: { 'token': 'foobar' }
701 | })
702 | .follow('echo-all')
703 | .getResource()
704 | .result
705 | .then(successCallback, errorCallback);
706 | waitFor(
707 | function() { return successCallback.called; },
708 | function() {
709 | var resultDoc = checkResultDoc();
710 | var responseAcceptHeader =
711 | resultDoc.headers.Accept ||
712 | resultDoc.headers.accept;
713 | var responseTestHeader =
714 | resultDoc.headers['X-Traverson-Test-Header'] ||
715 | resultDoc.headers['x-traverson-test-header'];
716 | expect(responseAcceptHeader).to.exist;
717 | expect(responseAcceptHeader).to.equal('application/json');
718 | expect(responseTestHeader).to.exist;
719 | expect(responseTestHeader).to.equal('Traverson rocks!');
720 | expect(resultDoc.query.token).to.equal('foobar');
721 | done();
722 | }
723 | );
724 | });
725 |
726 | it(
727 | 'should add request options on top of each other (/w AngularJS\' $http)',
728 | function(done) {
729 | api
730 | .newRequest()
731 | .useAngularHttp()
732 | .addRequestOptions({
733 | headers: { 'Accept': 'application/json', }
734 | })
735 | .addRequestOptions({
736 | headers: { 'X-Traverson-Test-Header': 'Traverson rocks!' }
737 | })
738 | .addRequestOptions({
739 | qs: { 'token': 'foobar' }
740 | })
741 | .follow('echo-all')
742 | .getResource()
743 | .result
744 | .then(successCallback, errorCallback);
745 | waitFor(
746 | function() { return successCallback.called; },
747 | function() {
748 | var resultDoc = checkResultDoc();
749 | var responseAcceptHeader =
750 | resultDoc.headers.Accept ||
751 | resultDoc.headers.accept;
752 | var responseTestHeader =
753 | resultDoc.headers['X-Traverson-Test-Header'] ||
754 | resultDoc.headers['x-traverson-test-header'];
755 | expect(responseAcceptHeader).to.exist;
756 | expect(responseAcceptHeader).to.equal('application/json');
757 | expect(responseTestHeader).to.exist;
758 | expect(responseTestHeader).to.equal('Traverson rocks!');
759 | expect(resultDoc.query.token).to.equal('foobar');
760 | done();
761 | }
762 | );
763 | });
764 |
765 | it('should use provided request options with post', function(done) {
766 | var payload = { what: 'ever' };
767 | api
768 | .newRequest()
769 | .withRequestOptions({
770 | headers: {
771 | 'Accept': 'application/json',
772 | 'Content-Type': 'application/json',
773 | 'X-Traverson-Test-Header': 'Traverson rocks!'
774 | },
775 | qs: { 'token': 'foobar' }
776 | })
777 | .follow('echo-all')
778 | .post(payload)
779 | .result
780 | .then(successCallback, errorCallback);
781 | waitFor(
782 | function() { return successCallback.called; },
783 | function() {
784 | var resultDoc = checkResponseWithBody(201);
785 | var responseAcceptHeader =
786 | resultDoc.headers.Accept ||
787 | resultDoc.headers.accept;
788 | var responseTestHeader =
789 | resultDoc.headers['X-Traverson-Test-Header'] ||
790 | resultDoc.headers['x-traverson-test-header'];
791 | expect(responseAcceptHeader).to.exist;
792 | expect(responseAcceptHeader).to.equal('application/json');
793 | expect(responseTestHeader).to.exist;
794 | expect(responseTestHeader).to.equal('Traverson rocks!');
795 | expect(resultDoc.query.token).to.equal('foobar');
796 | expect(resultDoc.received).to.exist;
797 | expect(resultDoc.received).to.deep.equal(payload);
798 | done();
799 | }
800 | );
801 | });
802 |
803 | it('should post with x-www-form-urlencoded',
804 | function(done) {
805 | var payload = { item: '#4711', quantity: 1 };
806 | traversonAngular
807 | .from(rootUri)
808 | .withRequestOptions([
809 | { headers: { 'Accept': 'application/json' } },
810 | {
811 | headers: {
812 | 'Accept': 'application/json',
813 | 'Content-Type': 'application/x-www-form-urlencoded',
814 | }
815 | }
816 | ])
817 | .follow('echo-all')
818 | .post(payload).result.then(successCallback, errorCallback);
819 | waitFor(
820 | function() { return successCallback.called; },
821 | function() {
822 | var resultDoc = checkResponseWithBody(201);
823 | var responseAcceptHeader =
824 | resultDoc.headers.Accept ||
825 | resultDoc.headers.accept;
826 | var responseContentType =
827 | resultDoc.headers['Content-Type'] ||
828 | resultDoc.headers['content-type'];
829 | expect(responseAcceptHeader).to.exist;
830 | expect(responseAcceptHeader).to.equal('application/json');
831 | expect(responseContentType).to.exist;
832 | expect(responseContentType)
833 | .to.equal('application/x-www-form-urlencoded');
834 | expect(resultDoc.received).to.exist;
835 | expect(JSON.stringify(resultDoc.received)).to.contain('item');
836 | expect(JSON.stringify(resultDoc.received)).to.contain('#4711');
837 | done();
838 | }
839 | );
840 | });
841 |
842 | it('should post form via request options with x-www-form-urlencoded',
843 | function(done) {
844 | var order = { item: '#4711', quantity: '1'};
845 | traversonAngular
846 | .from(rootUri)
847 | .withRequestOptions([
848 | { headers: { 'Accept': 'application/json' } },
849 | {
850 | headers: { 'Accept': 'application/json' },
851 | form: order,
852 | }
853 | ])
854 | .follow('echo-all')
855 | .post(null).result.then(successCallback, errorCallback);
856 | waitFor(
857 | function() { return successCallback.called; },
858 | function() {
859 | var resultDoc = checkResponseWithBody(201);
860 | var responseAcceptHeader =
861 | resultDoc.headers.Accept ||
862 | resultDoc.headers.accept;
863 | var responseContentType =
864 | resultDoc.headers['Content-Type'] ||
865 | resultDoc.headers['content-type'];
866 | expect(responseAcceptHeader).to.exist;
867 | expect(responseAcceptHeader).to.equal('application/json');
868 | expect(responseContentType).to.exist;
869 | expect(responseContentType)
870 | .to.equal('application/x-www-form-urlencoded');
871 | expect(resultDoc.received).to.exist;
872 | expect(resultDoc.received).to.deep.equal(order);
873 | done();
874 | }
875 | );
876 | });
877 |
878 | function checkResponseWithBody(httpStatus) {
879 | var response = checkResponse(httpStatus);
880 | var body = response.body;
881 | expect(body).to.exist;
882 | var resultDoc = JSON.parse(body);
883 | return resultDoc;
884 | }
885 |
886 | function checkResponse(httpStatus) {
887 | httpStatus = httpStatus || 200;
888 | expect(successCallback.callCount).to.equal(1);
889 | expect(errorCallback.callCount).to.equal(0);
890 | var response = successCallback.firstCall.args[0];
891 | expect(response).to.exist;
892 | expect(response.statusCode).to.exist;
893 | expect(response.statusCode).to.equal(httpStatus);
894 | return response;
895 | }
896 |
897 | function checkResultDoc() {
898 | expect(successCallback.callCount).to.equal(1);
899 | expect(errorCallback.callCount).to.equal(0);
900 | var resultDoc = successCallback.firstCall.args[0];
901 | expect(resultDoc).to.exist;
902 | return resultDoc;
903 | }
904 | });
905 |
--------------------------------------------------------------------------------
/test/using_angular_mocks.js:
--------------------------------------------------------------------------------
1 | /* global angular */
2 | 'use strict';
3 |
4 | var chai = require('chai')
5 | , expect = chai.expect;
6 |
7 | describe('traverson-angular using angular-mocks', function () {
8 |
9 | var rootUri = 'http://api.example.org';
10 | var httpBackend, traverson;
11 |
12 | beforeEach(angular.mock.module('traverson'));
13 |
14 | beforeEach(angular.mock.inject(function (_$httpBackend_, _traverson_) {
15 | traverson = _traverson_;
16 |
17 | httpBackend = _$httpBackend_;
18 | httpBackend.whenGET(rootUri).respond(JSON.stringify({
19 | stuff: 'a value',
20 | link: rootUri + '/link/to/thing'
21 | }));
22 | httpBackend.whenGET(rootUri + '/link/to/thing').respond(JSON.stringify({
23 | foo: 'bar'
24 | }));
25 | }));
26 |
27 | afterEach(function () {
28 | httpBackend.verifyNoOutstandingExpectation();
29 | httpBackend.verifyNoOutstandingRequest();
30 | });
31 |
32 | it('should access the root URI', function (done) {
33 | traverson
34 | .from(rootUri)
35 | .useAngularHttp()
36 | .json()
37 | .newRequest()
38 | .getResource()
39 | .result
40 | .then(function (response) {
41 | expect(response.stuff).to.equal('a value');
42 | done();
43 | }, function (error) {
44 | done(error);
45 | });
46 |
47 | httpBackend.flush();
48 | });
49 |
50 | it('should access the link', function (done) {
51 | traverson
52 | .from(rootUri)
53 | .useAngularHttp()
54 | .json()
55 | .newRequest()
56 | .follow('link')
57 | .getResource()
58 | .result
59 | .then(function (response) {
60 | expect(response.foo).to.equal('bar');
61 | done();
62 | }, function (error) {
63 | done(error);
64 | });
65 |
66 | httpBackend.flush();
67 | setTimeout(function() {
68 | httpBackend.flush();
69 | }, 0);
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/test/util.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // from node core util, copied here so the production code shim
4 | // browser/lib/shim/node-util.js does not need to have this.
5 | exports.inherits = function(ctor, superCtor) {
6 | ctor.super_ = superCtor;
7 | ctor.prototype = Object.create(superCtor.prototype, {
8 | constructor: {
9 | value: ctor,
10 | enumerable: false,
11 | writable: true,
12 | configurable: true
13 | }
14 | });
15 | };
16 |
17 |
--------------------------------------------------------------------------------
/traverson-angular.js:
--------------------------------------------------------------------------------
1 | /* global angular */
2 | 'use strict';
3 |
4 | var traverson = require('traverson');
5 |
6 | var ng;
7 | if (typeof angular !== 'undefined') {
8 | // angular is defined globally, use this
9 | ng = angular;
10 | } else {
11 | // angular is not defined globally, try to require it
12 | ng = require('angular');
13 | if (typeof ng.module !== 'function') {
14 | throw new Error('angular has either to be provided globally or made ' +
15 | 'available as a shim for browserify. (Also, if the angular module on ' +
16 | 'npm would actually be a proper CommonJS module, this error ' +
17 | 'wouldn\'t be a thing.)');
18 | }
19 | }
20 |
21 | var traversonAngular = ng.module('traverson', []);
22 | var Builder = traverson._Builder;
23 | var originalMethods = {
24 | get: Builder.prototype.get,
25 | getResource: Builder.prototype.getResource,
26 | getUrl: Builder.prototype.getUrl,
27 | post: Builder.prototype.post,
28 | put: Builder.prototype.put,
29 | patch: Builder.prototype.patch,
30 | delete: Builder.prototype.delete,
31 | };
32 |
33 | traversonAngular.factory('traverson',
34 | ['$q', '$httpTraversonAdapter',
35 | function traversonFactory($q, $httpTraversonAdapter) {
36 |
37 | function promisify(that, originalMethod, argsArray) {
38 | var deferred = $q.defer();
39 |
40 | argsArray = argsArray || [];
41 |
42 | var traversal;
43 | var callback = function(err, result, _traversal) {
44 | if (err) {
45 | err.result = result;
46 | deferred.reject(err);
47 | } else {
48 | traversal = _traversal;
49 | deferred.resolve(result);
50 | }
51 | };
52 |
53 | argsArray.push(callback);
54 |
55 | var traversalHandler = originalMethod.apply(that, argsArray);
56 |
57 | function continueTraversal() {
58 | var deferredContinue = $q.defer();
59 | deferred.promise.then(function() {
60 | deferredContinue.resolve(traversal.continue());
61 | }, function() {
62 | var error = new Error('Can\'t continue from a broken traversal.');
63 | error.name = 'InvalidStateError';
64 | throw error;
65 | });
66 | return deferredContinue.promise;
67 | }
68 |
69 | return {
70 | result: deferred.promise,
71 | continue: continueTraversal,
72 | abort: traversalHandler.abort,
73 | then: function() {
74 | throw new Error('As of version 2.0.0, Traverson\'s action methods ' +
75 | 'do no longer return the promise directly. Code like \n' +
76 | 'traverson.from(url).follow(...).getResource().then(...)\n' +
77 | 'needs to be changed to \n' +
78 | 'traverson.from(url).follow(...).getResource().result.then(...)');
79 | },
80 | };
81 | }
82 |
83 | Builder.prototype.get = function() {
84 | return promisify(this, originalMethods.get);
85 | };
86 |
87 | Builder.prototype.getResource = function() {
88 | return promisify(this, originalMethods.getResource);
89 | };
90 |
91 | Builder.prototype.getUrl = Builder.prototype.getUri = function() {
92 | return promisify(this, originalMethods.getUrl);
93 | };
94 |
95 | Builder.prototype.post = function(body) {
96 | return promisify(this, originalMethods.post, [body]);
97 | };
98 |
99 | Builder.prototype.put = function(body) {
100 | return promisify(this, originalMethods.put, [body]);
101 | };
102 |
103 | Builder.prototype.patch = function(body) {
104 | return promisify(this, originalMethods.patch, [body]);
105 | };
106 |
107 | Builder.prototype.delete = Builder.prototype.del = function() {
108 | return promisify(this, originalMethods.delete);
109 | };
110 |
111 | Builder.prototype.useAngularHttp = function() {
112 | this.withRequestLibrary($httpTraversonAdapter);
113 | return this;
114 | };
115 |
116 | return traverson;
117 | }]);
118 |
119 | traversonAngular.factory('$httpTraversonAdapter', [
120 | '$http', '$q', function $httpTraversonAdapterFactory($http, $q) {
121 |
122 | function Request() { }
123 |
124 | Request.prototype.get = function(uri, options, callback) {
125 | options = mapOptions(options);
126 | $http
127 | .get(uri, options)
128 | .then(handleResponse(callback))
129 | .catch(handleError(callback));
130 | return new AbortHandle(options.timeout);
131 | };
132 |
133 | Request.prototype.post = function(uri, options, callback) {
134 | options = mapOptions(options);
135 | $http
136 | .post(uri, options.data, options)
137 | .then(handleResponse(callback))
138 | .catch(handleError(callback));
139 | return new AbortHandle(options.timeout);
140 | };
141 |
142 | Request.prototype.put = function(uri, options, callback) {
143 | options = mapOptions(options);
144 | $http
145 | .put(uri, options.data, options)
146 | .then(handleResponse(callback))
147 | .catch(handleError(callback));
148 | return new AbortHandle(options.timeout);
149 | };
150 |
151 | Request.prototype.patch = function(uri, options, callback) {
152 | options = mapOptions(options);
153 | $http
154 | .patch(uri, options.data, options)
155 | .then(handleResponse(callback))
156 | .catch(handleError(callback));
157 | return new AbortHandle(options.timeout);
158 | };
159 |
160 | Request.prototype.del = function(uri, options, callback) {
161 | options = mapOptions(options);
162 | $http
163 | .delete(uri, options)
164 | .then(handleResponse(callback))
165 | .catch(handleError(callback));
166 | return new AbortHandle(options.timeout);
167 | };
168 |
169 | function mapOptions(options) {
170 | options = options || {};
171 | var mappedOptions = {};
172 | mapQuery(mappedOptions, options);
173 | mapHeaders(mappedOptions, options);
174 | mapAuth(mappedOptions, options);
175 | mapBody(mappedOptions, options);
176 | mapForm(mappedOptions, options);
177 | // do not parse JSON automatically, this will trip up Traverson
178 | mappedOptions.transformResponse = function(data, headersGetter, status) {
179 | return data;
180 | };
181 | // hook to abort the request, if necessary
182 | mappedOptions.timeout = $q.defer();
183 | return mappedOptions;
184 | }
185 |
186 | function mapQuery(mappedOptions, options) {
187 | // options.qs would be correct since we are using request/request options
188 | // object API, but a previous version of traverson-angular incorrectly
189 | // used options.query instead, so we allow this also, to not break
190 | // backwards compatibility.
191 | var qs = options.qs || options.query;
192 | if (qs) {
193 | mappedOptions.params = qs;
194 | }
195 | }
196 |
197 | function mapHeaders(mappedOptions, options) {
198 | if (options.headers) {
199 | mappedOptions.headers = options.headers;
200 | }
201 | }
202 |
203 | function mapAuth(mappedOptions, options) {
204 | var auth = options.auth;
205 | if (auth) {
206 | var username = auth.user || auth.username;
207 | var password = auth.pass || auth.password;
208 | mappedOptions.headers = mappedOptions.headers || {};
209 | mappedOptions.headers.Authorization = 'Basic ' + btoa(username + ':' +
210 | password);
211 | }
212 | }
213 |
214 | function mapBody(mappedOptions, options) {
215 | if (options.body) {
216 | mappedOptions.data = options.body;
217 | }
218 | }
219 |
220 | function mapForm(mappedOptions, options) {
221 | var form = options.form;
222 | if (form) {
223 | mappedOptions.data = form;
224 | mappedOptions.headers = mappedOptions.headers || {};
225 | mappedOptions.headers['Content-Type'] =
226 | 'application/x-www-form-urlencoded';
227 | }
228 | }
229 |
230 | function mapResponse(response) {
231 | response.body = response.data;
232 | response.headers = response.headers();
233 | response.statusCode = response.status;
234 | return response;
235 | }
236 |
237 | function handleResponse(callback) {
238 | return function(response) {
239 | return callback(null, mapResponse(response));
240 | };
241 | }
242 |
243 | function handleError(callback) {
244 | return function(response) {
245 | if (response.status >= 100 && response.status < 600) {
246 | // This happens on a completed HTTP request with a status code outside
247 | // of the 2xx range. In the context of Traverson, this is not an
248 | // error, in particular, if this is the last request in a traversal.
249 | // Thus, we re-route it to the successCallback. Handling 4xx and 5xx
250 | // errors during the traversal is the responsibility of traverson, not
251 | // traverson-angular.
252 | return callback(null, mapResponse(response));
253 | } else {
254 | // This happens on network errors, timeouts etc. In this case,
255 | // AngularJS sets the status property to 0. In the context of
256 | // Traverson, only these are to be interpreted as errors.
257 | return callback(response);
258 | }
259 | };
260 | }
261 |
262 | return new Request();
263 | }
264 | ]);
265 |
266 | function AbortHandle(abortPromise) {
267 | this.abortPromise = abortPromise;
268 | this.listeners = [];
269 | }
270 |
271 | AbortHandle.prototype.abort = function() {
272 | this.abortPromise.resolve();
273 | this.listeners.forEach(function(fn) {
274 | fn.call();
275 | });
276 | };
277 |
278 | AbortHandle.prototype.on = function(event, fn) {
279 | if (event !== 'abort') {
280 | var error = new Error('Event ' + event + ' not supported');
281 | error.name = 'InvalidArgumentError';
282 | throw error;
283 | }
284 | this.listeners.push(fn);
285 | };
286 |
287 | module.exports = traversonAngular;
288 |
--------------------------------------------------------------------------------