diff --git a/src/js/dao/invitation-dao.js b/src/js/dao/invitation-dao.js index 47e0bf8..761a823 100644 --- a/src/js/dao/invitation-dao.js +++ b/src/js/dao/invitation-dao.js @@ -1,9 +1,92 @@ define(function() { 'use strict'; + /** + * The InvitationDAO is a high level Data Access Object that access the invitation service REST endpoint. + * @param {Object} restDao The REST Data Access Object abstraction + */ var InvitationDAO = function(restDao) { this._restDao = restDao; }; + // + // Constants + // + + InvitationDAO.INVITE_MISSING = 1; + InvitationDAO.INVITE_PENDING = 2; + InvitationDAO.INVITE_SUCCESS = 4; + + // + // API + // + + /** + * Notes an invite for the recipient by the sender in the invitation web service + * @param {String} recipient User ID of the recipient + * @param {String} sender User ID of the sender + * @param {Function} callback(error, status) Returns information if the invitation worked (INVITE_SUCCESS), if an invitation is already pendin (INVITE_PENDING), or information if an error occurred. + */ + InvitationDAO.prototype.invite = function(recipient, sender, callback) { + this._restDao.put(null, uri(recipient, sender), completed); + + function completed(error, res, status) { + if (error) { + callback(error); + return; + } + + if (status === 201) { + callback(null, InvitationDAO.INVITE_SUCCESS); + return; + } else if (status === 304) { + callback(null, InvitationDAO.INVITE_PENDING); + return; + } + + callback({ + errMsg: 'unexpected invitation state' + }); + } + }; + + /** + * Checks if an invitation for the recipient by the sender is present in the invitation web service + * @param {String} recipient User ID of the recipient + * @param {String} sender User ID of the sender + * @param {Function} callback(error, status) Returns information about the invitation status, either an invitation is already on place (INVITE_PENDING), or not (INVITE_MISSING), or information if an error occurred. + */ + InvitationDAO.prototype.check = function(recipient, sender, callback) { + this._restDao.get(null, uri(recipient, sender), completed); + + function completed(error, res, status) { + // 404 is a meaningful return value from the web service + if (error && error.code !== 404) { + callback(error); + return; + } + + if (error && error.code === 404) { + callback(null, InvitationDAO.INVITE_MISSING); + return; + } else if (status === 200) { + callback(null, InvitationDAO.INVITE_PENDING); + return; + } + + callback({ + errMsg: 'unexpected invitation state' + }); + } + }; + + // + // Helper functions + // + + function uri(a, b) { + return '/invitation/recipient/' + a + '/sender/' + b; + } + return InvitationDAO; }); \ No newline at end of file diff --git a/src/js/dao/rest-dao.js b/src/js/dao/rest-dao.js index 12df9b4..b0a02fd 100644 --- a/src/js/dao/rest-dao.js +++ b/src/js/dao/rest-dao.js @@ -51,8 +51,8 @@ define(function(require) { headers: { 'Accept': acceptHeader }, - success: function(res) { - callback(null, res); + success: function(res, textStatus, xhr) { + callback(null, res, xhr.status); }, error: function(xhr, textStatus, err) { callback({ @@ -73,8 +73,8 @@ define(function(require) { type: 'PUT', data: JSON.stringify(item), contentType: 'application/json', - success: function() { - callback(); + success: function(res, textStatus, xhr) { + callback(null, res, xhr.status); }, error: function(xhr, textStatus, err) { callback({ @@ -93,8 +93,8 @@ define(function(require) { $.ajax({ url: this._baseUri + uri, type: 'DELETE', - success: function() { - callback(); + success: function(res, textStatus, xhr) { + callback(null, res, xhr.status); }, error: function(xhr, textStatus, err) { callback({ diff --git a/test/new-unit/invitation-dao-test.js b/test/new-unit/invitation-dao-test.js new file mode 100644 index 0000000..2645d1e --- /dev/null +++ b/test/new-unit/invitation-dao-test.js @@ -0,0 +1,122 @@ +define(function(require) { + 'use strict'; + + var RestDAO = require('js/dao/rest-dao'), + InvitationDAO = require('js/dao/invitation-dao'), + expect = chai.expect; + + describe('Invitation DAO unit tests', function() { + var restDaoStub, invitationDao, + alice = 'zuhause@aol.com', + bob = 'manfred.mustermann@musterdomain.com', + expectedUri = '/invitation/recipient/' + alice + '/sender/' + bob; + + beforeEach(function() { + restDaoStub = sinon.createStubInstance(RestDAO); + invitationDao = new InvitationDAO(restDaoStub); + }); + + describe('initialization', function() { + it('should wire up correctly', function() { + expect(invitationDao._restDao).to.equal(restDaoStub); + expect(invitationDao.invite).to.exist; + expect(InvitationDAO.INVITE_MISSING).to.equal(1); + expect(InvitationDAO.INVITE_PENDING).to.equal(2); + expect(InvitationDAO.INVITE_SUCCESS).to.equal(4); + }); + }); + + describe('invite', function() { + it('should invite the recipient', function(done) { + restDaoStub.put.yieldsAsync(null, undefined, 201); + + invitationDao.invite(alice, bob, function(err, status) { + expect(err).to.not.exist; + expect(status).to.equal(InvitationDAO.INVITE_SUCCESS); + expect(restDaoStub.put.calledWith(null, expectedUri)).to.be.true; + done(); + }); + }); + + it('should point out already invited recipient', function(done) { + restDaoStub.put.yieldsAsync(null, undefined, 304); + + invitationDao.invite(alice, bob, function(err, status) { + expect(err).to.not.exist; + expect(status).to.equal(InvitationDAO.INVITE_PENDING); + done(); + }); + }); + + it('should not work for http error', function(done) { + restDaoStub.put.yieldsAsync({ + errMsg: 'jawollja.' + }); + + invitationDao.invite(alice, bob, function(err, status) { + expect(err).to.exist; + expect(status).to.not.exist; + done(); + }); + }); + + it('should not work for unexpected response', function(done) { + restDaoStub.put.yieldsAsync(null, undefined, 1337); + + invitationDao.invite(alice, bob, function(err, status) { + expect(err).to.exist; + expect(status).to.not.exist; + done(); + }); + }); + }); + + describe('check', function() { + it('should return pending invite', function(done) { + restDaoStub.get.yieldsAsync(null, undefined, 200); + + invitationDao.check(alice, bob, function(err, status) { + expect(err).to.not.exist; + expect(status).to.equal(InvitationDAO.INVITE_PENDING); + expect(restDaoStub.get.calledWith(null, expectedUri)).to.be.true; + done(); + }); + }); + + it('should return missing invite', function(done) { + restDaoStub.get.yieldsAsync({ + code: 404 + }); + + invitationDao.check(alice, bob, function(err, status) { + expect(err).to.not.exist; + expect(status).to.equal(InvitationDAO.INVITE_MISSING); + done(); + }); + }); + + it('should not work for http error', function(done) { + restDaoStub.get.yieldsAsync({ + code: 1337, + errMsg: 'jawollja.' + }); + + invitationDao.check(alice, bob, function(err, status) { + expect(err).to.exist; + expect(status).to.not.exist; + done(); + }); + }); + + it('should not work for unexpected response', function(done) { + restDaoStub.get.yieldsAsync(null, undefined, 1337); + + invitationDao.check(alice, bob, function(err, status) { + expect(err).to.exist; + expect(status).to.not.exist; + done(); + }); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/new-unit/main.js b/test/new-unit/main.js index 308aa75..1d8a653 100644 --- a/test/new-unit/main.js +++ b/test/new-unit/main.js @@ -48,7 +48,8 @@ function startTests() { 'test/new-unit/navigation-ctrl-test', 'test/new-unit/mail-list-ctrl-test', 'test/new-unit/write-ctrl-test', - 'test/new-unit/outbox-bo-test' + 'test/new-unit/outbox-bo-test', + 'test/new-unit/invitation-dao-test' ], function() { //Tests loaded, run tests mocha.run(); diff --git a/test/new-unit/rest-dao-test.js b/test/new-unit/rest-dao-test.js index de461ff..f84a707 100644 --- a/test/new-unit/rest-dao-test.js +++ b/test/new-unit/rest-dao-test.js @@ -10,14 +10,13 @@ define(function(require) { var restDao; beforeEach(function() { - sinon.stub($, 'ajax').yieldsTo('success', { - foo: 'bar' - }); restDao = new RestDAO(); }); afterEach(function() { - $.ajax.restore(); + if (typeof $.ajax.callCount !== 'undefined') { + $.ajax.restore(); + } }); describe('contructor', function() { @@ -40,73 +39,81 @@ define(function(require) { describe('get', function() { it('should work with json as default type', function(done) { - $.ajax.restore(); var spy = sinon.stub($, 'ajax').yieldsTo('success', { foo: 'bar' + }, 'success', { + status: 200 }); restDao.get({ uri: '/asdf', type: 'json' - }, function(err, data) { + }, function(err, data, status) { expect(err).to.not.exist; expect(data.foo).to.equal('bar'); expect(spy.calledWith(sinon.match(function(request) { return request.headers.Accept === 'application/json' && request.dataType === 'json'; }))).to.be.true; + expect(status).to.equal(200); done(); }); }); it('should work with json', function(done) { - $.ajax.restore(); var spy = sinon.stub($, 'ajax').yieldsTo('success', { foo: 'bar' + }, 'success', { + status: 200 }); restDao.get({ uri: '/asdf', type: 'json' - }, function(err, data) { + }, function(err, data, status) { expect(err).to.not.exist; expect(data.foo).to.equal('bar'); expect(spy.calledWith(sinon.match(function(request) { return request.headers.Accept === 'application/json' && request.dataType === 'json'; }))).to.be.true; + expect(status).to.equal(200); done(); }); }); it('should work with plain text', function(done) { - $.ajax.restore(); - var spy = sinon.stub($, 'ajax').yieldsTo('success', 'foobar!'); + var spy = sinon.stub($, 'ajax').yieldsTo('success', 'foobar!', 'success', { + status: 200 + }); restDao.get({ uri: '/asdf', type: 'text' - }, function(err, data) { + }, function(err, data, status) { expect(err).to.not.exist; expect(data).to.equal('foobar!'); expect(spy.calledWith(sinon.match(function(request) { return request.headers.Accept === 'text/plain' && request.dataType === 'text'; }))).to.be.true; + expect(status).to.equal(200); done(); }); }); it('should work with xml', function(done) { - $.ajax.restore(); - var spy = sinon.stub($, 'ajax').yieldsTo('success', 'bar'); + var spy = sinon.stub($, 'ajax').yieldsTo('success', 'bar', 'success', { + status: 200 + }); restDao.get({ uri: '/asdf', type: 'xml' - }, function(err, data) { + }, function(err, data, status) { expect(err).to.not.exist; expect(data).to.equal('bar'); // that's probably not right, but in the unit test, it is :) expect(spy.calledWith(sinon.match(function(request) { return request.headers.Accept === 'application/xml' && request.dataType === 'xml'; }))).to.be.true; + expect(status).to.equal(200); done(); }); }); @@ -133,7 +140,6 @@ define(function(require) { }); it('should fail for server error', function(done) { - $.ajax.restore(); sinon.stub($, 'ajax').yieldsTo('error', { status: 500 }, { @@ -153,7 +159,6 @@ define(function(require) { describe('put', function() { it('should fail', function(done) { - $.ajax.restore(); sinon.stub($, 'ajax').yieldsTo('error', { status: 500 }, { @@ -168,8 +173,15 @@ define(function(require) { }); it('should work', function(done) { - restDao.put('/asdf', {}, function(err) { + var spy = sinon.stub($, 'ajax').yieldsTo('success', undefined, 'success', { + status: 201 + }); + + restDao.put('/asdf', {}, function(err, res, status) { expect(err).to.not.exist; + expect(res).to.not.exist; + expect(spy.callCount).to.equal(1); + expect(status).to.equal(201); done(); }); }); @@ -177,7 +189,6 @@ define(function(require) { describe('remove', function() { it('should fail', function(done) { - $.ajax.restore(); sinon.stub($, 'ajax').yieldsTo('error', { status: 500 }, { @@ -192,8 +203,14 @@ define(function(require) { }); it('should work', function(done) { - restDao.remove('/asdf', function(err) { + var spy = sinon.stub($, 'ajax').yieldsTo('success', undefined, 'success', { + status: 204 + }); + restDao.remove('/asdf', function(err, res, status) { expect(err).to.not.exist; + expect(res).to.not.exist; + expect(spy.callCount).to.equal(1); + expect(status).to.equal(204); done(); }); });