angular-apollo
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -11,8 +11,7 @@ import { ApiService } from './api/api'; | ||
constructor(apiService: ApiService); | ||
private getAuthTokenHeaders(token); | ||
login(details: LoginDetails): Observable<Account>; | ||
spotlight(token: string): Observable<Account>; | ||
lists(token: string): Observable<Account>; | ||
listById(id: number, token: string): Observable<any>; | ||
radios(token: string): Observable<Account>; | ||
spotlight(): Observable<{}>; | ||
lists(): Observable<{}>; | ||
listById(id: number): Observable<{}>; | ||
radios(): Observable<{}>; | ||
} |
@@ -19,9 +19,2 @@ "use strict"; | ||
} | ||
AccountService.prototype.getAuthTokenHeaders = function (token) { | ||
return { | ||
headers: new http_1.Headers({ | ||
'Authorization': "bearer " + token, | ||
}) | ||
}; | ||
}; | ||
AccountService.prototype.login = function (details) { | ||
@@ -33,13 +26,13 @@ var options = { | ||
}; | ||
AccountService.prototype.spotlight = function (token) { | ||
return this.apiService.fetch("me/spotlight", this.getAuthTokenHeaders(token)); | ||
AccountService.prototype.spotlight = function () { | ||
return this.apiService.authorisedFetch("me/spotlight"); | ||
}; | ||
AccountService.prototype.lists = function (token) { | ||
return this.apiService.fetch("me/lists", this.getAuthTokenHeaders(token)); | ||
AccountService.prototype.lists = function () { | ||
return this.apiService.authorisedFetch("me/lists"); | ||
}; | ||
AccountService.prototype.listById = function (id, token) { | ||
return this.apiService.fetch("me/lists/" + id, this.getAuthTokenHeaders(token)); | ||
AccountService.prototype.listById = function (id) { | ||
return this.apiService.authorisedFetch("me/lists/" + id); | ||
}; | ||
AccountService.prototype.radios = function (token) { | ||
return this.apiService.fetch("me/radios", this.getAuthTokenHeaders(token)); | ||
AccountService.prototype.radios = function () { | ||
return this.apiService.authorisedFetch("me/radios"); | ||
}; | ||
@@ -46,0 +39,0 @@ AccountService = __decorate([ |
@@ -1,13 +0,24 @@ | ||
import { Http, Response, RequestOptionsArgs } from '@angular/http'; | ||
import { Observable } from 'rxjs/Rx'; | ||
import { Http, RequestOptionsArgs } from '@angular/http'; | ||
import { Observable, Subject } from 'rxjs/Rx'; | ||
import { Config } from '../config'; | ||
import 'rxjs/Rx'; | ||
import { AuthTokensValidator } from '../auth-tokens-validator/auth-tokens-validator'; | ||
import 'rxjs/add/operator/map'; | ||
export interface ApiTokenStore { | ||
events: Subject<any>; | ||
get(): { | ||
authToken: string; | ||
refreshToken: string; | ||
}; | ||
} | ||
export declare class ApiService { | ||
private http; | ||
private config; | ||
constructor(http: Http, config: Config); | ||
getPath(path: string): string; | ||
private apiTokenStore; | ||
private authTokensValidator; | ||
constructor(http: Http, config: Config, apiTokenStore: ApiTokenStore, authTokensValidator: AuthTokensValidator); | ||
fetch(path: string, options?: RequestOptionsArgs): Observable<any>; | ||
authorisedFetch(path: string, options?: RequestOptionsArgs): Observable<{}>; | ||
post(path: string, data: any, options?: RequestOptionsArgs): Observable<any>; | ||
handleResponse(res: Response): any; | ||
private getPath(path); | ||
private handleResponse(res); | ||
} | ||
@@ -14,0 +25,0 @@ export declare class ApiError extends Error { |
@@ -18,21 +18,53 @@ "use strict"; | ||
var http_1 = require('@angular/http'); | ||
var Rx_1 = require('rxjs/Rx'); | ||
var config_1 = require('../config'); | ||
require('rxjs/Rx'); | ||
require('rxjs/add/operator/map'); | ||
var ApiAuthorisationError = (function (_super) { | ||
__extends(ApiAuthorisationError, _super); | ||
function ApiAuthorisationError(options) { | ||
_super.call(this, 'API Authorisation error'); | ||
Object.assign(this, options); | ||
} | ||
return ApiAuthorisationError; | ||
}(Error)); | ||
var ApiService = (function () { | ||
function ApiService(http, config) { | ||
function ApiService(http, config, apiTokenStore, authTokensValidator) { | ||
this.http = http; | ||
this.config = config; | ||
this.apiTokenStore = apiTokenStore; | ||
this.authTokensValidator = authTokensValidator; | ||
} | ||
; | ||
ApiService.prototype.getPath = function (path) { | ||
return this.config.baseUrl + "/" + path; | ||
}; | ||
ApiService.prototype.fetch = function (path, options) { | ||
if (options === void 0) { options = {}; } | ||
var url = this.getPath(path); | ||
return this.http.get(url, options) | ||
.map(this.handleResponse); | ||
return this.http.get(url, options).map(this.handleResponse); | ||
}; | ||
ApiService.prototype.authorisedFetch = function (path, options) { | ||
var _this = this; | ||
if (options === void 0) { options = {}; } | ||
var tokens = this.apiTokenStore.get(); | ||
console.log(tokens); | ||
if (!tokens) { | ||
this.apiTokenStore.events.error(new ApiAuthorisationError({ | ||
missingTokens: true | ||
})); | ||
return Rx_1.Observable.empty(); | ||
} | ||
return this.authTokensValidator | ||
.validateTokens(tokens.authToken, tokens.refreshToken) | ||
.flatMap(function (validatedTokens) { | ||
options.headers = options.headers || new http_1.Headers(); | ||
options.headers.set('Authorization', "bearer " + validatedTokens.authToken); | ||
if (validatedTokens.hasBeenRefreshed) { | ||
_this.apiTokenStore.events.next(validatedTokens); | ||
} | ||
return _this.fetch(path, options); | ||
}) | ||
.catch(function (err) { | ||
_this.apiTokenStore.events.error(new ApiAuthorisationError({ | ||
invalidAuthToken: true | ||
})); | ||
return Rx_1.Observable.empty(); | ||
}); | ||
}; | ||
ApiService.prototype.post = function (path, data, options) { | ||
if (options === void 0) { options = {}; } | ||
var url = this.getPath(path); | ||
@@ -42,2 +74,5 @@ return this.http.post(url, JSON.stringify(data), options) | ||
}; | ||
ApiService.prototype.getPath = function (path) { | ||
return this.config.baseUrl + "/" + path; | ||
}; | ||
ApiService.prototype.handleResponse = function (res) { | ||
@@ -51,3 +86,3 @@ if (res.status >= 400) { | ||
core_1.Injectable(), | ||
__metadata('design:paramtypes', [http_1.Http, config_1.Config]) | ||
__metadata('design:paramtypes', [http_1.Http, config_1.Config, Object, Object]) | ||
], ApiService); | ||
@@ -54,0 +89,0 @@ return ApiService; |
@@ -10,5 +10,6 @@ "use strict"; | ||
var testing_2 = require('@angular/core/testing'); | ||
require('rxjs/Rx'); | ||
var Rx_1 = require('rxjs/Rx'); | ||
var api_1 = require('./api'); | ||
var config_1 = require('../config'); | ||
var Rx_2 = require('rxjs/Rx'); | ||
var TestConfig = (function (_super) { | ||
@@ -23,4 +24,37 @@ __extends(TestConfig, _super); | ||
testing_2.describe('ApiService', function () { | ||
var apiTokens, apiTokenStore, fakeAuthTokensValidator, fakeAuthTokensValidatorResult; | ||
var FakeApiTokenStore = (function () { | ||
function FakeApiTokenStore() { | ||
this.events = new Rx_1.Subject(); | ||
} | ||
FakeApiTokenStore.prototype.get = function () { | ||
return apiTokens; | ||
}; | ||
return FakeApiTokenStore; | ||
}()); | ||
beforeEach(function () { | ||
fakeAuthTokensValidatorResult = Rx_2.Observable.from([{ | ||
authToken: 'validatedAuthToken', | ||
refreshToken: 'validatedRefreshToken', | ||
hasBeenRefreshed: false | ||
}]); | ||
apiTokenStore = new FakeApiTokenStore(); | ||
fakeAuthTokensValidator = { | ||
validateTokens: function (authToken, refreshToken) { | ||
return fakeAuthTokensValidatorResult; | ||
} | ||
}; | ||
apiTokens = { | ||
authToken: 'authToken', | ||
refreshToken: 'refreshToken' | ||
}; | ||
}); | ||
testing_2.beforeEachProviders(function () { return [ | ||
api_1.ApiService, | ||
{ | ||
provide: api_1.ApiService, | ||
deps: [http_1.Http, config_1.Config], | ||
useFactory: function (http, config) { | ||
return new api_1.ApiService(http, config, apiTokenStore, fakeAuthTokensValidator); | ||
} | ||
}, | ||
testing_1.MockBackend, | ||
@@ -36,76 +70,198 @@ http_1.BaseRequestOptions, | ||
useClass: TestConfig | ||
}]; }); | ||
} | ||
]; }); | ||
function makeResponse(response) { | ||
return new http_1.Response(new http_1.ResponseOptions(response)); | ||
} | ||
testing_2.it('Uses the baseUrl from the configuration', testing_2.async(testing_2.inject([api_1.ApiService, testing_1.MockBackend], function (api, mockBackEnd) { | ||
var response = { | ||
body: JSON.stringify({ id: 666 }), | ||
status: 200 | ||
}; | ||
mockBackEnd.connections.subscribe(function (connection) { | ||
testing_2.expect(connection.request.url) | ||
.toEqual('http://www.example.com/lists/666'); | ||
connection.mockRespond(makeResponse(response)); | ||
}); | ||
return api.fetch('lists/666') | ||
.subscribe(function (data) { console.log(); }); | ||
}))); | ||
testing_2.it('returns the json when response is 200', testing_2.async(testing_2.inject([api_1.ApiService, testing_1.MockBackend], function (api, mockBackEnd) { | ||
var response = { | ||
body: JSON.stringify({ id: 666 }), | ||
status: 200 | ||
}; | ||
mockBackEnd.connections.subscribe(function (connection) { | ||
connection.mockRespond(makeResponse(response)); | ||
}); | ||
return api.fetch('lists/666').subscribe(function (data) { | ||
testing_2.expect(data).toEqual({ id: 666 }); | ||
}); | ||
}))); | ||
testing_2.it('throws an error when the body is not valid json', testing_2.async(testing_2.inject([api_1.ApiService, testing_1.MockBackend], function (api, mockBackEnd) { | ||
mockBackEnd.connections | ||
.subscribe(function (connection) { | ||
var response = { | ||
body: '{!"', | ||
status: 200 | ||
}; | ||
function testApiCall(options) { | ||
return testing_2.async(testing_2.inject([api_1.ApiService, testing_1.MockBackend], function (api, mockBackEnd) { | ||
var response = { body: options.stubbedBody, status: options.stubbedStatusCode || 200 }; | ||
mockBackEnd.connections.subscribe(function (connection) { | ||
testing_2.expect(connection.request.url) | ||
.toEqual("http://www.example.com/" + options.expectedPath); | ||
if (options.expectedBody) { | ||
testing_2.expect(JSON.parse(connection.request.getBody())) | ||
.toEqual(options.expectedBody); | ||
} | ||
if (options.expectedHeaders) { | ||
Object.keys(options.expectedHeaders).forEach(function (header) { | ||
testing_2.expect(connection.request.headers.getAll(header)) | ||
.toEqual(options.expectedHeaders[header]); | ||
}); | ||
} | ||
if (options.stubbedError) { | ||
return connection.mockError(options.stubbedError); | ||
} | ||
connection.mockRespond(makeResponse(response)); | ||
}); | ||
return options.test(api); | ||
})); | ||
} | ||
testing_2.it('returns the json when response is 200 for post requests', testApiCall({ | ||
expectedPath: 'lists/666', | ||
expectedHeaders: { someHeader: ['blah'] }, | ||
expectedBody: { bodyData: 'fleh' }, | ||
stubbedBody: { id: 123 }, | ||
test: function (api) { | ||
var options = { | ||
headers: new http_1.Headers({ someHeader: 'blah' }) | ||
}; | ||
api.post('lists/666', { bodyData: 'fleh' }, options) | ||
.subscribe(function (data) { return testing_2.expect(data).toEqual({ id: 123 }); }); | ||
} | ||
})); | ||
testing_2.it('returns the json when response is 200 for fetch requests', testApiCall({ | ||
expectedPath: 'lists/666', | ||
expectedHeaders: { someHeader: ['woo'] }, | ||
stubbedBody: { id: 123 }, | ||
test: function (api) { | ||
var options = { | ||
headers: new http_1.Headers({ someHeader: 'woo' }) | ||
}; | ||
api.fetch('lists/666', options) | ||
.subscribe(function (data) { return testing_2.expect(data).toEqual({ id: 123 }); }); | ||
} | ||
})); | ||
testing_2.it('returns the json when response is 200 for authorised fetch requests', testApiCall({ | ||
expectedPath: 'lists/666', | ||
expectedHeaders: { someHeader: ['wee'] }, | ||
stubbedBody: { id: 123 }, | ||
test: function (api) { | ||
var options = { | ||
headers: new http_1.Headers({ someHeader: 'wee' }) | ||
}; | ||
api.authorisedFetch('lists/666', options) | ||
.subscribe(function (data) { return testing_2.expect(data).toEqual({ id: 123 }); }); | ||
} | ||
})); | ||
testing_2.it('appends the authorization header to authorised fetch requests', testApiCall({ | ||
expectedPath: 'test', | ||
expectedHeaders: { Authorization: ['bearer validatedAuthToken'] }, | ||
test: function (api) { return api.authorisedFetch('test').subscribe(); } | ||
})); | ||
testing_2.describe('with an empty api token store', function () { | ||
beforeEach(function () { return apiTokens = undefined; }); | ||
testing_2.it('raises unauthorised event when an authorised fetch request is made', testApiCall({ | ||
expectedPath: 'test', | ||
test: function (api) { | ||
apiTokenStore.events.subscribe(function () { return fail(new Error('Expected error event')); }, function (err) { return testing_2.expect(err.missingTokens).toEqual(true); }); | ||
api.authorisedFetch('test').subscribe(); | ||
} | ||
})); | ||
}); | ||
testing_2.describe('when api token validation fails', function () { | ||
beforeEach(function () { | ||
fakeAuthTokensValidatorResult = Rx_2.Observable.throw(new Error('plop')); | ||
}); | ||
return api.fetch('lists/666').subscribe(function (data) { | ||
testing_2.expect(data).toEqual({ id: 666 }); | ||
}, function (err) { | ||
testing_2.expect(err).toEqual(jasmine.any(Error)); | ||
testing_2.it('raises unauthorised event when an authorised fetch request is made', testApiCall({ | ||
expectedPath: 'test', | ||
test: function (api) { | ||
apiTokenStore.events.subscribe(function () { return fail(new Error('Expected error event')); }, function (err) { return testing_2.expect(err.invalidAuthToken).toEqual(true); }); | ||
api.authorisedFetch('test').subscribe(); | ||
} | ||
})); | ||
}); | ||
testing_2.describe('when the api token has been refreshed', function () { | ||
beforeEach(function () { | ||
fakeAuthTokensValidatorResult = Rx_2.Observable.from([{ | ||
authToken: 'new-token', | ||
refreshToken: 'new-refresh-token', | ||
hasBeenRefreshed: true | ||
}]); | ||
}); | ||
}))); | ||
[404, 500].forEach(function (statusCode) { | ||
testing_2.it("throws an error when response is " + statusCode, testing_2.async(testing_2.inject([api_1.ApiService, testing_1.MockBackend], function (api, mockBackEnd) { | ||
mockBackEnd.connections.subscribe(function (connection) { | ||
connection.mockRespond(new http_1.Response(new http_1.ResponseOptions({ | ||
status: statusCode | ||
}))); | ||
}); | ||
return api.fetch('lists/666') | ||
.subscribe(function (res) { | ||
throw new Error('This test should not get a successful response'); | ||
}, function (err) { | ||
testing_2.expect(err).toEqual(jasmine.any(api_1.ApiError)); | ||
testing_2.it('emits a refresh event', testApiCall({ | ||
expectedPath: 'test', | ||
test: function (api) { | ||
apiTokenStore.events.subscribe(function (evt) { return testing_2.expect(evt).toEqual({ | ||
hasBeenRefreshed: true, | ||
authToken: 'new-token', | ||
refreshToken: 'new-refresh-token' | ||
}); }, function (err) { return fail(err); }); | ||
api.authorisedFetch('test').subscribe(); | ||
} | ||
})); | ||
}); | ||
testing_2.describe('when the api token has not been refreshed', function () { | ||
beforeEach(function () { | ||
fakeAuthTokensValidatorResult = Rx_2.Observable.from([{ | ||
authToken: 'old-token', | ||
refreshToken: 'old-refresh-token', | ||
hasBeenRefreshed: false | ||
}]); | ||
}); | ||
testing_2.it('does not emit a refresh event', testApiCall({ | ||
expectedPath: 'test', | ||
test: function (api) { | ||
apiTokenStore.events.subscribe(function (evt) { return fail(new Error('should not emit an event')); }, function (err) { return fail(err); }); | ||
api.authorisedFetch('test').subscribe(); | ||
} | ||
})); | ||
}); | ||
testing_2.it('throws an error when the post response body is not valid json', testApiCall({ | ||
expectedPath: 'blah', | ||
stubbedBody: 'not json', | ||
test: function (api) { return api.post('blah', 'request body') | ||
.subscribe(function (data) { }, function (err) { return testing_2.expect(err).toEqual(jasmine.any(Error)); }); } | ||
})); | ||
testing_2.it('throws an error when the fetch response body is not valid json', testApiCall({ | ||
expectedPath: 'blah', | ||
stubbedBody: 'not json', | ||
test: function (api) { return api.fetch('blah') | ||
.subscribe(function (data) { }, function (err) { return testing_2.expect(err).toEqual(jasmine.any(Error)); }); } | ||
})); | ||
testing_2.it('throws an error when the authorised fetch response body is not valid json', testApiCall({ | ||
expectedPath: 'blah', | ||
stubbedBody: 'not json', | ||
test: function (api) { return api.authorisedFetch('blah') | ||
.subscribe(function () { return fail(new Error('ApiService should error')); }, function (err) { return testing_2.expect(err).toEqual(jasmine.any(Error)); }); } | ||
})); | ||
var errorStatusCodes = [404, 500]; | ||
errorStatusCodes.forEach(function (statusCode) { | ||
testing_2.it("throws an error when post response is " + statusCode, testApiCall({ | ||
expectedPath: 'blah', | ||
stubbedStatusCode: statusCode, | ||
test: function (api) { return api.post('blah', {}) | ||
.subscribe(function () { return fail(new Error('ApiService should error')); }, function (err) { | ||
testing_2.expect(err).toEqual(jasmine.any(Error)); | ||
testing_2.expect(err.response.status).toEqual(statusCode); | ||
}); | ||
}))); | ||
}); } | ||
})); | ||
testing_2.it("throws an error when fetch response is " + statusCode, testApiCall({ | ||
expectedPath: 'blah', | ||
stubbedStatusCode: statusCode, | ||
test: function (api) { return api.fetch('blah') | ||
.subscribe(function () { return fail(new Error('ApiService should error')); }, function (err) { | ||
testing_2.expect(err).toEqual(jasmine.any(Error)); | ||
testing_2.expect(err.response.status).toEqual(statusCode); | ||
}); } | ||
})); | ||
testing_2.it("throws an error when authorised fetch response is " + statusCode, testApiCall({ | ||
expectedPath: 'blah', | ||
stubbedStatusCode: statusCode, | ||
test: function (api) { return api.authorisedFetch('blah') | ||
.subscribe(function () { return fail(new Error('ApiService should error')); }, function (err) { | ||
testing_2.expect(err).toEqual(jasmine.any(Error)); | ||
testing_2.expect(err.response.status).toEqual(statusCode); | ||
}); } | ||
})); | ||
}); | ||
testing_2.it('fails gracefully when the API is unavailable', testing_2.async(testing_2.inject([api_1.ApiService, testing_1.MockBackend], function (api, mockBackEnd) { | ||
mockBackEnd.connections.subscribe(function (connection) { | ||
connection.mockError(new Error('error')); | ||
}); | ||
return api.fetch('lists/666') | ||
.subscribe(function (res) { | ||
throw new Error('This test should not get a successful response'); | ||
}, function (err) { | ||
testing_2.expect(err).toEqual(jasmine.any(Error)); | ||
}); | ||
}))); | ||
testing_2.it('fails post requests gracefully when the API is unavailable', testApiCall({ | ||
expectedPath: 'blah', | ||
stubbedError: new Error('boom'), | ||
test: function (api) { return api.post('blah', {}) | ||
.subscribe(function () { return fail(new Error('ApiService should error')); }, function (err) { return testing_2.expect(err).toEqual(jasmine.any(Error)); }); } | ||
})); | ||
testing_2.it('fails fetch requests gracefully when the API is unavailable', testApiCall({ | ||
expectedPath: 'blah', | ||
stubbedError: new Error('boom'), | ||
test: function (api) { return api.fetch('blah') | ||
.subscribe(function () { return fail(new Error('ApiService should error')); }, function (err) { return testing_2.expect(err).toEqual(jasmine.any(Error)); }); } | ||
})); | ||
testing_2.it('fails authorised fetch requests gracefully when the API is unavailable', testApiCall({ | ||
expectedPath: 'blah', | ||
stubbedError: new Error('boom'), | ||
test: function (api) { return api.authorisedFetch('blah') | ||
.subscribe(function () { return fail(new Error('ApiService should error')); }, function (err) { return testing_2.expect(err).toEqual(jasmine.any(Error)); }); } | ||
})); | ||
}); | ||
//# sourceMappingURL=api.test.js.map |
@@ -79,3 +79,3 @@ export interface Type { | ||
offline_id: string; | ||
player_id: string; | ||
play_id: string; | ||
isrc: string; | ||
@@ -82,0 +82,0 @@ release_date: string; |
{ | ||
"name": "angular-apollo", | ||
"description": "An API client for the 7digital Apollo platform built for Angular 2 projects.", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"main": "dist/index.js", | ||
@@ -6,0 +6,0 @@ "repository": "https://github.com/7digital/angular-apollo.git", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
64966
36
865