box-chrome-sdk
Version:
A Chrome App SDK for the Box V2 API
464 lines (437 loc) • 24.7 kB
JavaScript
/*jshint expr: true*/
describe('box.http', function() {
var mocks, boxHttp, observer, chromeStorage, momentStub, boxApiAuth, http, boxHttpResponseInterceptor, $q, apiUrl;
var clientId = 'i3p-----------------------------',
clientSecret = 'uII-----------------------------';
beforeEach(function() {
var provide;
mocks = sinon.sandbox.create();
observer = mocks.spy();
angular.module('box.conf')
.constant('clientSecret', clientSecret)
.constant('clientId', clientId);
module('box.http', 'rx.http', 'box.auth', 'chrome.storage', 'moment', function($provide) {
provide = $provide;
});
inject(function(_http_, _chromeStorage_, _boxApiAuth_, _$q_) {
http = mocks.stub(_http_);
provide.value('http', http);
chromeStorage = mocks.stub(_chromeStorage_);
provide.value('chromeStorage', chromeStorage);
momentStub = mocks.stub();
provide.value('moment', momentStub);
boxApiAuth = mocks.stub(_boxApiAuth_);
provide.value('boxApiAuth', boxApiAuth);
$q = mocks.stub(_$q_);
provide.value('$q', $q);
});
});
beforeEach(inject(function(_boxHttp_, _boxHttpResponseInterceptor_, _apiUrl_) {
boxHttp = _boxHttp_;
boxHttpResponseInterceptor = _boxHttpResponseInterceptor_;
apiUrl = _apiUrl_;
}));
afterEach(function() {
mocks.restore();
});
describe('auth()', function() {
describe('missing or expired tokens', function() {
it('should attempt login when tokens are missing', function() {
var moment = {add: mocks.spy(), toDate: mocks.stub()};
boxApiAuth.login.returns(Rx.Observable.return('code'));
boxApiAuth.getToken.returns(Rx.Observable.return({
data: {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600
}
}));
momentStub.returns(moment);
moment.toDate.returns('date');
chromeStorage.getLocal.withArgs('access_token').returns(Rx.Observable.return({}));
chromeStorage.getLocal.withArgs('refresh_token').returns(Rx.Observable.return({}));
chromeStorage.setLocal.returns(Rx.Observable.return({}));
boxHttp.auth().subscribe(observer);
expect(chromeStorage.getLocal).to.have.been.calledTwice
.and.to.have.been.calledWithExactly('access_token')
.and.to.have.been.calledWithExactly('refresh_token');
expect(boxApiAuth.login).to.have.been.calledOnce.and.to.have.been.calledWithExactly();
expect(boxApiAuth.getToken).to.have.been.calledOnce.and.to.have.been.calledWithExactly('code');
expect(momentStub).to.have.been.calledWithExactly();
expect(momentStub.callCount).to.equal(4);
expect(moment.add)
.and.to.have.been.calledWithExactly('seconds', 3600)
.and.to.have.been.calledWithExactly('days', 60);
expect(moment.add.callCount).to.equal(4);
expect(moment.toDate).to.have.been.calledWithExactly();
expect(moment.toDate.callCount).to.equal(4);
expect(chromeStorage.setLocal).to.have.been.calledTwice
.and.to.have.been.calledWithExactly({
refresh_token: {
token: 'refresh_token',
expires_at: 'date'
},
access_token: {
token: 'access_token',
expires_at: 'date'
}
});
expect(observer).to.have.been.calledOnce.and.to.have.been.calledWithExactly('access_token');
});
it('should attempt login when refresh token is expired', function() {
var moment = {add: mocks.spy(), toDate: mocks.stub(), isAfter: mocks.stub()};
var yesterdayMoment = mocks.spy(), tomorrowMoment = mocks.spy();
boxApiAuth.login.returns(Rx.Observable.return('code'));
boxApiAuth.getToken.returns(Rx.Observable.return({
data: {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600
}
}));
momentStub.withArgs('yesterday').returns(yesterdayMoment);
momentStub.withArgs('tomorrow').returns(tomorrowMoment);
momentStub.returns(moment);
moment.toDate.returns('date');
moment.isAfter
.withArgs(yesterdayMoment).returns(true)
.withArgs(tomorrowMoment).returns(true);
chromeStorage.getLocal.withArgs('access_token').returns(Rx.Observable.return({
token: 'access_token',
expires_at: 'yesterday'
}));
chromeStorage.getLocal.withArgs('refresh_token').returns(Rx.Observable.return({
token: 'refresh_token',
expires_at: 'tomorrow'
}));
chromeStorage.setLocal.returns(Rx.Observable.return({}));
boxHttp.auth().subscribe(observer);
expect(chromeStorage.getLocal).to.have.been.calledTwice
.and.to.have.been.calledWithExactly('access_token')
.and.to.have.been.calledWithExactly('refresh_token');
expect(boxApiAuth.login).to.have.been.calledOnce.and.to.have.been.calledWithExactly();
expect(boxApiAuth.getToken).to.have.been.calledOnce.and.to.have.been.calledWithExactly('code');
expect(momentStub).to.have.been.calledWithExactly();
expect(momentStub).to.have.been.calledWithExactly('yesterday');
expect(momentStub).to.have.been.calledWithExactly('tomorrow');
expect(momentStub.callCount).to.equal(8);
expect(moment.add)
.and.to.have.been.calledWithExactly('seconds', 3600)
.and.to.have.been.calledWithExactly('days', 60);
expect(moment.add.callCount).to.equal(4);
expect(moment.toDate).to.have.been.calledWithExactly();
expect(moment.toDate.callCount).to.equal(4);
expect(moment.isAfter).to.have.been.calledWithExactly(yesterdayMoment);
expect(moment.isAfter).to.have.been.calledWithExactly(tomorrowMoment);
expect(moment.isAfter.callCount).to.equal(2);
expect(chromeStorage.setLocal).to.have.been.calledTwice
.and.to.have.been.calledWithExactly({
refresh_token: {
token: 'refresh_token',
expires_at: 'date'
},
access_token: {
token: 'access_token',
expires_at: 'date'
}
});
expect(observer).to.have.been.calledOnce.and.to.have.been.calledWithExactly('access_token');
});
it('should attempt refresh when access token is expired', function() {
var moment = {add: mocks.spy(), toDate: mocks.stub(), isAfter: mocks.stub()};
var yesterdayMoment = mocks.spy(), tomorrowMoment = mocks.spy();
boxApiAuth.refreshToken.returns(Rx.Observable.return({
data: {
access_token: 'new_access_token',
refresh_token: 'new_refresh_token',
expires_in: 3600
}
}));
momentStub.withArgs('yesterday').returns(yesterdayMoment);
momentStub.returns(moment);
moment.toDate.returns('date');
moment.isAfter
.withArgs(yesterdayMoment).returns(true)
.withArgs(tomorrowMoment).returns(false);
chromeStorage.getLocal.withArgs('access_token').returns(Rx.Observable.return({
token: 'access_token',
expires_at: 'yesterday'
}));
chromeStorage.getLocal.withArgs('refresh_token').returns(Rx.Observable.return({
token: 'refresh_token',
expires_at: 'tomorrow'
}));
chromeStorage.setLocal.returns(Rx.Observable.return({}));
boxHttp.auth().subscribe(observer);
expect(chromeStorage.getLocal).to.have.been.calledTwice
.and.to.have.been.calledWithExactly('access_token')
.and.to.have.been.calledWithExactly('refresh_token');
expect(boxApiAuth.refreshToken).to.have.been.calledOnce.and.to.have.been.calledWithExactly('refresh_token');
expect(momentStub).to.have.been.calledWithExactly();
expect(momentStub).to.have.been.calledWithExactly('yesterday');
expect(momentStub).to.have.been.calledWithExactly('tomorrow');
expect(momentStub.callCount).to.equal(6);
expect(moment.add)
.and.to.have.been.calledWithExactly('seconds', 3600)
.and.to.have.been.calledWithExactly('days', 60);
expect(moment.add.callCount).to.equal(2);
expect(moment.toDate).to.have.been.calledWithExactly();
expect(moment.toDate.callCount).to.equal(2);
expect(moment.isAfter).to.have.been.calledWithExactly(yesterdayMoment);
expect(moment.isAfter.callCount).to.equal(2);
expect(chromeStorage.setLocal).to.have.been.calledOnce
.and.to.have.been.calledWithExactly({
refresh_token: {
token: 'new_refresh_token',
expires_at: 'date'
},
access_token: {
token: 'new_access_token',
expires_at: 'date'
}
});
expect(observer).to.have.been.calledOnce.and.to.have.been.calledWithExactly('new_access_token');
});
it('should return an error if login is required but noLogin is specified', function() {
var moment = {add: mocks.spy(), toDate: mocks.stub(), isAfter: mocks.stub()};
var yesterdayMoment = mocks.spy(), tomorrowMoment = mocks.spy();
var errObserver = mocks.spy(), completedObserver = mocks.spy();
momentStub.withArgs('yesterday').returns(yesterdayMoment);
momentStub.withArgs('tomorrow').returns(tomorrowMoment);
momentStub.returns(moment);
moment.toDate.returns('date');
moment.isAfter
.withArgs(yesterdayMoment).returns(true)
.withArgs(tomorrowMoment).returns(true);
chromeStorage.getLocal.withArgs('access_token').returns(Rx.Observable.return({
token: 'access_token',
expires_at: 'yesterday'
}));
chromeStorage.getLocal.withArgs('refresh_token').returns(Rx.Observable.return({
token: 'refresh_token',
expires_at: 'tomorrow'
}));
boxHttp.auth(true).subscribe(observer, errObserver, completedObserver);
expect(chromeStorage.getLocal).to.have.been.calledTwice
.and.to.have.been.calledWithExactly('access_token')
.and.to.have.been.calledWithExactly('refresh_token');
expect(boxApiAuth.login).to.not.have.been.called;
expect(boxApiAuth.getToken).to.not.have.been.called;
expect(momentStub).to.have.been.calledWithExactly();
expect(momentStub).to.have.been.calledWithExactly('yesterday');
expect(momentStub).to.have.been.calledWithExactly('tomorrow');
expect(momentStub.callCount).to.equal(4);
expect(moment.isAfter).to.have.been.calledWithExactly(yesterdayMoment);
expect(moment.isAfter).to.have.been.calledWithExactly(tomorrowMoment);
expect(moment.isAfter.callCount).to.equal(2);
expect(observer).to.not.have.been.called;
expect(completedObserver).to.not.have.been.called;
expect(errObserver).to.have.been.calledOnce.and.to.have.been.calledWithExactly(new Error('Not logged in!'));
});
it('should attempt login if refresh fails', function() {
var moment = {add: mocks.spy(), toDate: mocks.stub(), isAfter: mocks.stub()};
var yesterdayMoment = mocks.spy(), tomorrowMoment = mocks.spy();
boxApiAuth.refreshToken.returns(Rx.Observable.throw(new Error('bad request')));
boxApiAuth.login.returns(Rx.Observable.return('code'));
boxApiAuth.getToken.returns(Rx.Observable.return({
data: {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600
}
}));
momentStub.withArgs('yesterday').returns(yesterdayMoment);
momentStub.withArgs('tomorrow').returns(tomorrowMoment);
momentStub.returns(moment);
moment.toDate.returns('date');
moment.isAfter
.withArgs(yesterdayMoment).returns(true)
.withArgs(tomorrowMoment).returns(false);
chromeStorage.getLocal.withArgs('access_token').returns(Rx.Observable.return({
token: 'access_token',
expires_at: 'yesterday'
}));
chromeStorage.getLocal.withArgs('refresh_token').returns(Rx.Observable.return({
token: 'refresh_token',
expires_at: 'tomorrow'
}));
chromeStorage.setLocal.returns(Rx.Observable.return({}));
chromeStorage.removeLocal.returns(Rx.Observable.return({}));
boxHttp.auth().subscribe(observer);
expect(chromeStorage.getLocal).to.have.been.calledTwice
.and.to.have.been.calledWithExactly('access_token')
.and.to.have.been.calledWithExactly('refresh_token');
expect(boxApiAuth.refreshToken).to.have.been.calledOnce.and.to.have.been.calledWithExactly('refresh_token');
expect(boxApiAuth.login).to.have.been.calledOnce.and.to.have.been.calledWithExactly();
expect(boxApiAuth.getToken).to.have.been.calledOnce.and.to.have.been.calledWithExactly('code');
expect(momentStub).to.have.been.calledWithExactly();
expect(momentStub.callCount).to.equal(8);
expect(moment.add)
.and.to.have.been.calledWithExactly('seconds', 3600)
.and.to.have.been.calledWithExactly('days', 60);
expect(moment.add.callCount).to.equal(4);
expect(moment.toDate).to.have.been.calledWithExactly();
expect(moment.toDate.callCount).to.equal(4);
expect(moment.isAfter).to.have.been.calledWithExactly(yesterdayMoment);
expect(moment.isAfter).to.have.been.calledWithExactly(tomorrowMoment);
expect(moment.isAfter.callCount).to.equal(2);
expect(chromeStorage.setLocal).to.have.been.calledTwice
.and.to.have.been.calledWithExactly({
refresh_token: {
token: 'refresh_token',
expires_at: 'date'
},
access_token: {
token: 'access_token',
expires_at: 'date'
}
});
expect(chromeStorage.removeLocal).to.have.been.calledTwice
.and.to.have.been.calledWith('access_token')
.and.to.have.been.calledWith('refresh_token');
expect(observer).to.have.been.calledOnce.and.to.have.been.calledWithExactly('access_token');
});
});
describe('happy path', function() {
it('should return the stored access token if not expired', function() {
var moment = {add: mocks.spy(), toDate: mocks.stub(), isAfter: mocks.stub()}, yesterdayMoment = mocks.spy();
momentStub.withArgs('yesterday').returns(yesterdayMoment);
momentStub.returns(moment);
chromeStorage.getLocal.withArgs('access_token').returns(Rx.Observable.return({
token: 'access_token',
expires_at: 'yesterday'
}));
chromeStorage.setLocal.returns(Rx.Observable.return({}));
moment.isAfter.withArgs(yesterdayMoment).returns(false);
boxHttp.auth().subscribe(observer);
expect(chromeStorage.getLocal).to.have.been.calledOnce.and.to.have.been.calledWithExactly('access_token');
expect(momentStub).to.have.been.calledTwice.and.to.have.been.calledWithExactly().and.to.have.been.calledWithExactly('yesterday');
expect(moment.isAfter).to.have.been.calledOnce.and.to.have.been.calledWithExactly(yesterdayMoment);
expect(observer).to.have.been.calledOnce.and.to.have.been.calledWithExactly('access_token');
});
});
});
describe('requests', function() {
var moment, yesterdayMoment;
beforeEach(function() {
moment = {add: mocks.spy(), toDate: mocks.stub(), isAfter: mocks.stub()};
yesterdayMoment = mocks.spy();
momentStub.withArgs('yesterday').returns(yesterdayMoment);
momentStub.returns(moment);
chromeStorage.getLocal.withArgs('access_token').returns(Rx.Observable.return({
token: 'access_token',
expires_at: 'yesterday'
}));
chromeStorage.setLocal.returns(Rx.Observable.return({}));
moment.isAfter.withArgs(yesterdayMoment).returns(false);
});
['GET', 'POST', 'PUT', 'DELETE'].forEach(function(method) {
it('should make authorized request for ' + method, function() {
var url = 'https://example.com', config = {}, data = {};
http.getObservable.returns(Rx.Observable.return({
data: 'data'
}));
boxHttp[method.toLowerCase()](url, config, data).subscribe(observer);
expect(chromeStorage.getLocal).to.have.been.calledOnce.and.to.have.been.calledWithExactly('access_token');
expect(momentStub).to.have.been.calledTwice.and.to.have.been.calledWithExactly().and.to.have.been.calledWithExactly('yesterday');
expect(moment.isAfter).to.have.been.calledOnce.and.to.have.been.calledWithExactly(yesterdayMoment);
expect(http.getObservable).to.have.been.calledOnce.and.to.have.been.calledWithExactly(
method,
url,
{headers: {'Authorization': 'Bearer access_token'}},
data
);
expect(observer).to.have.been.calledOnce.and.to.have.been.calledWithExactly('data');
});
});
[202, 429].forEach(function(status) {
it('should retry ' + status + ' responses', function(done) {
var method = 'GET', url = 'https://example.com', config = {}, data = {}, delay = 0.001;
http.getObservable.onFirstCall().returns(Rx.Observable.return({
status: status,
headers: {'Retry-After': delay}
}));
http.getObservable.returns(Rx.Observable.return({
data: 'data'
}));
boxHttp[method.toLowerCase()](url, config, data).do(observer).subscribe(function() {
expect(http.getObservable).to.have.been.calledTwice.and.to.have.been.calledWithExactly(
method,
url,
{headers: {'Authorization': 'Bearer access_token'}},
data
);
expect(chromeStorage.getLocal).to.have.been.calledTwice.and.to.have.been.calledWithExactly('access_token');
expect(momentStub).to.have.been.calledWithExactly().and.to.have.been.calledWithExactly('yesterday');
expect(momentStub.callCount).to.equal(4);
expect(moment.isAfter).to.have.been.calledTwice.and.to.have.been.calledWithExactly(yesterdayMoment);
expect(observer).to.have.been.calledOnce.and.to.have.been.calledWithExactly('data');
done();
});
});
});
it('should try login for 401 responses', function() {
var method = 'GET', url = 'https://example.com', config = {}, data = {};
var moment = {add: mocks.spy(), toDate: mocks.stub(), isAfter: mocks.stub()};
momentStub.returns(moment);
moment.toDate.returns('date');
http.getObservable.onFirstCall().returns(Rx.Observable.return({
status: 401
}));
http.getObservable.returns(Rx.Observable.return({
data: 'data'
}));
boxApiAuth.login.returns(Rx.Observable.return('code'));
boxApiAuth.getToken.returns(Rx.Observable.return({
data: {
access_token: 'access_token',
refresh_token: 'refresh_token',
expires_in: 3600
}
}));
chromeStorage.setLocal.returns(Rx.Observable.return({}));
boxHttp[method.toLowerCase()](url, config, data).do(observer).subscribe(function() {
expect(http.getObservable).to.have.been.calledTwice.and.to.have.been.calledWithExactly(
method,
url,
{headers: {'Authorization': 'Bearer access_token'}},
data
);
});
expect(chromeStorage.getLocal).to.have.been.calledOnce.and.to.have.been.calledWithExactly('access_token');
expect(momentStub.callCount).to.equal(4);
expect(moment.add)
.and.to.have.been.calledWithExactly('seconds', 3600)
.and.to.have.been.calledWithExactly('days', 60);
expect(moment.add.callCount).to.equal(2);
expect(moment.toDate).to.have.been.calledWithExactly();
expect(moment.toDate.callCount).to.equal(2);
expect(observer).to.have.been.calledOnce.and.to.have.been.calledWithExactly('data');
});
});
describe('boxHttpResponseInterceptor', function() {
beforeEach(function() {
$q.reject.returns('rejected');
});
[401, 429].forEach(function(status) {
it('should recover from the error for ' + status, function() {
var response = {
status: status,
config: {url: apiUrl + '/some/path'}
};
var intercepted = boxHttpResponseInterceptor.responseError(response);
expect(intercepted).to.equal(response);
});
});
[400, 403].forEach(function(status) {
it('should reject the response for ' + status, function() {
var response = {
status: status,
config: {url: apiUrl + '/some/path'}
};
var intercepted = boxHttpResponseInterceptor.responseError(response);
expect(intercepted).to.equal('rejected');
});
});
});
});