Index: remoting/webapp/crd/js/host_controller_unittest.js |
diff --git a/remoting/webapp/crd/js/host_controller_unittest.js b/remoting/webapp/crd/js/host_controller_unittest.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..cda53ba005641f04dbd3f5a8e73e064fbe0c8571 |
--- /dev/null |
+++ b/remoting/webapp/crd/js/host_controller_unittest.js |
@@ -0,0 +1,413 @@ |
+/** |
+ * @fileoverview |
+ * Unit tests for host_controller.js. |
+ */ |
+ |
+(function() { |
+ |
+'use strict'; |
+ |
+/** @type {sinon.Mock} */ |
+var hostListMock = null; |
+ |
+/** @type {sinon.TestStub} */ |
+var generateUuidStub; |
+ |
+/** @type {remoting.MockHostDaemonFacade} */ |
+var mockHostDaemonFacade; |
+ |
+/** @type {sinon.TestStub} */ |
+var hostDaemonFacadeCtorStub; |
+ |
+var FAKE_HOST_PIN = '<FAKE_HOST_PIN>'; |
+var FAKE_USER_EMAIL = '<FAKE_USER_EMAIL>'; |
+var FAKE_USER_NAME = '<FAKE_USER_NAME>'; |
+var FAKE_UUID = '0bad0bad-0bad-0bad-0bad-0bad0bad0bad'; |
+var FAKE_DAEMON_VERSION = '1.2.3.4'; |
+var FAKE_HOST_NAME = '<FAKE_HOST_NAME>'; |
+var FAKE_PUBLIC_KEY = '<FAKE_PUBLIC_KEY>'; |
+var FAKE_PRIVATE_KEY = '<FAKE_PRIVATE_KEY>'; |
+var FAKE_AUTH_CODE = '<FAKE_AUTH_CODE>'; |
+var FAKE_REFRESH_TOKEN = '<FAKE_REFRESH_TOKEN>'; |
+var FAKE_PIN_HASH = '<FAKE_PIN_HASH>'; |
+var FAKE_HOST_CLIENT_ID = '<FAKE_HOST_CLIENT_ID>'; |
+var FAKE_CLIENT_BASE_JID = '<FAKE_CLIENT_BASE_JID>'; |
+ |
+/** @type {sinon.Spy|Function} */ |
+var getCredentialsFromAuthCodeSpy; |
+ |
+/** @type {sinon.Spy|Function} */ |
+var getPinHashSpy; |
+ |
+/** @type {sinon.Spy|Function} */ |
+var startDaemonSpy; |
+ |
+/** @type {sinon.Spy} */ |
+var unregisterHostByIdSpy; |
+ |
+/** @type {sinon.Spy} */ |
+var onLocalHostStartedSpy; |
+ |
+QUnit.module('host_controller', { |
+ beforeEach: function(/** QUnit.Assert */ assert) { |
+ chromeMocks.activate(['identity', 'runtime']); |
+ chromeMocks.identity.mock$setToken('my_token'); |
+ remoting.identity = new remoting.Identity(); |
+ remoting.MockXhr.activate(); |
+ base.debug.assert(remoting.oauth2 === null); |
+ remoting.oauth2 = new remoting.OAuth2(); |
+ base.debug.assert(remoting.hostList === null); |
+ remoting.hostList = /** @type {remoting.HostList} */ |
+ (Object.create(remoting.HostList.prototype)); |
+ |
+ // When the HostList's unregisterHostById method is called, make |
+ // sure the argument is correct. |
+ unregisterHostByIdSpy = |
+ sinon.stub(remoting.hostList, 'unregisterHostById', function( |
+ /** string */ hostId) { |
+ assert.equal(hostId, FAKE_UUID); |
+ }); |
+ |
+ // When the HostList's onLocalHostStarted method is called, make |
+ // sure the arguments are correct. |
+ onLocalHostStartedSpy = |
+ sinon.stub( |
+ remoting.hostList, 'onLocalHostStarted', function( |
+ /** string */ hostName, |
+ /** string */ newHostId, |
+ /** string */ publicKey) { |
+ assert.equal(hostName, FAKE_HOST_NAME); |
+ assert.equal(newHostId, FAKE_UUID); |
+ assert.equal(publicKey, FAKE_PUBLIC_KEY); |
+ }); |
+ |
+ hostDaemonFacadeCtorStub = sinon.stub(remoting, 'HostDaemonFacade'); |
+ mockHostDaemonFacade = new remoting.MockHostDaemonFacade(); |
+ hostDaemonFacadeCtorStub.returns(mockHostDaemonFacade); |
+ generateUuidStub = sinon.stub(base, 'generateUuid'); |
+ generateUuidStub.returns(FAKE_UUID); |
+ getCredentialsFromAuthCodeSpy = sinon.spy( |
+ mockHostDaemonFacade, 'getCredentialsFromAuthCode'); |
+ getPinHashSpy = sinon.spy(mockHostDaemonFacade, 'getPinHash'); |
+ startDaemonSpy = sinon.spy(mockHostDaemonFacade, 'startDaemon'); |
+ |
+ // Set up successful responses from mockHostDaemonFacade. |
+ // Individual tests override these values to create errors. |
+ mockHostDaemonFacade.features = |
+ [remoting.HostController.Feature.OAUTH_CLIENT]; |
+ mockHostDaemonFacade.daemonVersion = FAKE_DAEMON_VERSION; |
+ mockHostDaemonFacade.hostName = FAKE_HOST_NAME; |
+ mockHostDaemonFacade.privateKey = FAKE_PRIVATE_KEY; |
+ mockHostDaemonFacade.publicKey = FAKE_PUBLIC_KEY; |
+ mockHostDaemonFacade.hostClientId = FAKE_HOST_CLIENT_ID; |
+ mockHostDaemonFacade.userEmail = FAKE_USER_EMAIL; |
+ mockHostDaemonFacade.refreshToken = FAKE_REFRESH_TOKEN; |
+ mockHostDaemonFacade.pinHash = FAKE_PIN_HASH; |
+ mockHostDaemonFacade.startDaemonResult = |
+ remoting.HostController.AsyncResult.OK; |
+ |
+ sinon.stub(remoting.identity, 'getEmail').returns( |
+ Promise.resolve(FAKE_USER_EMAIL)); |
+ sinon.stub(remoting.oauth2, 'getRefreshToken').returns( |
+ FAKE_REFRESH_TOKEN); |
+ }, |
+ |
+ afterEach: function() { |
+ getCredentialsFromAuthCodeSpy.restore(); |
+ generateUuidStub.restore(); |
+ hostDaemonFacadeCtorStub.restore(); |
+ remoting.hostList = null; |
+ remoting.oauth2 = null; |
+ remoting.MockXhr.restore(); |
+ chromeMocks.restore(); |
+ remoting.identity = null; |
+ } |
+}); |
+ |
+/** |
+ * Install |
Jamie
2015/04/08 00:16:15
The comment doesn't seem to have any relation to t
John Williams
2015/04/08 20:20:09
Must've typed errant ^K while I was thinking about
|
+ * @param {QUnit.Assert} assert |
+ * @param {boolean} withAuthCode |
+ */ |
+function queueRegistryResponse(assert, withAuthCode) { |
+ var responseJson = { |
+ data: { |
+ authorizationCode: FAKE_AUTH_CODE |
+ } |
+ }; |
+ if (!withAuthCode) { |
+ delete responseJson.data.authorizationCode; |
+ } |
+ |
+ remoting.MockXhr.setResponseFor( |
+ 'POST', 'DIRECTORY_API_BASE_URL/@me/hosts', |
+ function(/** remoting.MockXhr */ xhr) { |
+ assert.deepEqual( |
+ xhr.params.jsonContent, |
+ { data: { |
+ hostId: FAKE_UUID, |
+ hostName: FAKE_HOST_NAME, |
+ publicKey: FAKE_PUBLIC_KEY |
+ } }); |
+ xhr.setTextResponse(200, JSON.stringify(responseJson)); |
+ }); |
+} |
+ |
+/** |
+ * @param {remoting.HostController} controller |
+ */ |
+function mockGetClientBaseJid(controller) { |
+ sinon.stub(controller, 'getClientBaseJid_'). |
+ callsArgWith(0, FAKE_CLIENT_BASE_JID); |
+}; |
+ |
+// Check that hasFeature returns false for missing features. |
+QUnit.test('hasFeature returns false', function(assert) { |
+ var controller = new remoting.HostController(); |
+ return new Promise(function(resolve, reject) { |
+ controller.hasFeature( |
+ remoting.HostController.Feature.PAIRING_REGISTRY, |
+ resolve); |
+ }).then(function(/** boolean */ result) { |
+ assert.equal(result, false); |
+ }); |
+}); |
+ |
+// Check that hasFeature returns true for supported features. |
+QUnit.test('hasFeature returns true', function(assert) { |
+ var controller = new remoting.HostController(); |
+ return new Promise(function(resolve, reject) { |
+ controller.hasFeature( |
+ remoting.HostController.Feature.OAUTH_CLIENT, |
+ resolve); |
+ }).then(function(/** boolean */ result) { |
+ assert.equal(result, true); |
+ }); |
+}); |
+ |
+// Check what happens when the HostDaemonFacade's getHostName method |
+// fails. |
+QUnit.test('start with getHostName failure', function(assert) { |
+ mockHostDaemonFacade.hostName = null; |
+ var controller = new remoting.HostController(); |
Jamie
2015/04/08 00:16:14
Is the code from here on common to all tests? Can
John Williams
2015/04/08 20:20:09
Done.
Jamie
2015/04/08 22:29:29
IIRC, the controller used to be global and you opt
John Williams
2015/04/08 23:19:55
Right, I did. I misunderstood the comment, but I
Jamie
2015/04/08 23:28:42
Acknowledged.
|
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, true, function() { |
+ reject('test failed'); |
+ }, function(/** remoting.Error */ e) { |
+ assert.equal(e.getDetail(), 'getHostName'); |
+ assert.equal(getCredentialsFromAuthCodeSpy.callCount, 0); |
Jamie
2015/04/08 00:16:14
Should it be a test failure if this was called? We
John Williams
2015/04/08 20:20:09
Good point. Changed.
|
+ assert.equal(unregisterHostByIdSpy.callCount, 0); |
+ assert.equal(onLocalHostStartedSpy.callCount, 0); |
+ assert.equal(startDaemonSpy.callCount, 0); |
+ resolve(null); |
+ }); |
+ }); |
+}); |
+ |
+// Check what happens when the HostDaemonFacade's generateKeyPair |
+// method fails. |
+QUnit.test('start with generateKeyPair failure', function(assert) { |
+ mockHostDaemonFacade.publicKey = null; |
+ mockHostDaemonFacade.privateKey = null; |
+ var controller = new remoting.HostController(); |
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, true, function() { |
+ reject('test failed'); |
+ }, function(/** remoting.Error */ e) { |
+ assert.equal(e.getDetail(), 'generateKeyPair'); |
+ assert.equal(getCredentialsFromAuthCodeSpy.callCount, 0); |
+ assert.equal(unregisterHostByIdSpy.callCount, 0); |
+ assert.equal(onLocalHostStartedSpy.callCount, 0); |
+ assert.equal(startDaemonSpy.callCount, 0); |
+ resolve(null); |
+ }); |
+ }); |
+}); |
+ |
+// Check what happens when the HostDaemonFacade's getHostClientId |
+// method fails. |
+QUnit.test('start with getHostClientId failure', function(assert) { |
+ var controller = new remoting.HostController(); |
+ mockHostDaemonFacade.hostClientId = null; |
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, true, function() { |
+ reject('test failed'); |
+ }, function(/** remoting.Error */ e) { |
+ assert.equal(e.getDetail(), 'getHostClientId'); |
+ assert.equal(getCredentialsFromAuthCodeSpy.callCount, 0); |
+ assert.equal(unregisterHostByIdSpy.callCount, 0); |
+ assert.equal(onLocalHostStartedSpy.callCount, 0); |
+ assert.equal(startDaemonSpy.callCount, 0); |
+ resolve(null); |
+ }); |
+ }); |
+}); |
+ |
+// Check what happens when the registry returns an HTTP when we try to |
+// register a host. |
+QUnit.test('start with host registration failure', function(assert) { |
+ var controller = new remoting.HostController(); |
+ remoting.MockXhr.setEmptyResponseFor( |
+ 'POST', 'DIRECTORY_API_BASE_URL/@me/hosts', 500); |
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, true, function() { |
+ reject('test failed'); |
+ }, function(/** remoting.Error */ e) { |
+ assert.equal(e.getTag(), remoting.Error.Tag.REGISTRATION_FAILED); |
+ assert.equal(getCredentialsFromAuthCodeSpy.callCount, 0); |
+ assert.equal(unregisterHostByIdSpy.callCount, 0); |
+ assert.equal(onLocalHostStartedSpy.callCount, 0); |
+ assert.equal(startDaemonSpy.callCount, 0); |
+ resolve(null); |
+ }); |
+ }); |
+}); |
+ |
+// Check what happens when the HostDaemonFacade's |
+// getCredentialsFromAuthCode method fails. |
+QUnit.test('start with getCredentialsFromAuthCode failure', function(assert) { |
+ var controller = new remoting.HostController(); |
+ mockHostDaemonFacade.useEmail = null; |
+ mockHostDaemonFacade.refreshToken = null; |
+ queueRegistryResponse(assert, true); |
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, true, function() { |
+ reject('test failed'); |
+ }, function(/** remoting.Error */ e) { |
+ assert.equal(e.getDetail(), 'getCredentialsFromAuthCode'); |
+ assert.equal(getCredentialsFromAuthCodeSpy.callCount, 1); |
+ assert.equal(unregisterHostByIdSpy.callCount, 0); |
+ assert.equal(onLocalHostStartedSpy.callCount, 0); |
+ assert.equal(startDaemonSpy.callCount, 0); |
+ resolve(null); |
+ }); |
+ }); |
+}); |
+ |
+// Check what happens when the HostDaemonFacade's getPinHash method |
+// fails, and verify that getPinHash is called when the registry |
+// does't return an auth code. |
+QUnit.test('start with getRefreshToken+getPinHash failure', function(assert) { |
+ var controller = new remoting.HostController(); |
+ mockHostDaemonFacade.pinHash = null; |
+ queueRegistryResponse(assert, false); |
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, true, function() { |
+ reject('test failed'); |
+ }, function(/** remoting.Error */ e) { |
+ assert.equal(e.getDetail(), 'getPinHash'); |
+ assert.equal(getCredentialsFromAuthCodeSpy.callCount, 0); |
+ assert.equal(unregisterHostByIdSpy.callCount, 0); |
+ assert.equal(onLocalHostStartedSpy.callCount, 0); |
+ assert.equal(startDaemonSpy.callCount, 0); |
+ resolve(null); |
+ }); |
+ }); |
+}); |
+ |
+// Check what happens when the HostController's getClientBaseJid_ |
+// method fails. |
+QUnit.test('start with getClientBaseJid_ failure', function(assert) { |
+ var controller = new remoting.HostController(); |
+ queueRegistryResponse(assert, true); |
+ sinon.stub(controller, 'getClientBaseJid_'). |
+ callsArgWith(1, remoting.Error.unexpected('getClientBaseJid_')); |
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, true, function() { |
+ reject('test failed'); |
+ }, function(/** remoting.Error */ e) { |
+ assert.equal(e.getDetail(), 'getClientBaseJid_'); |
+ assert.equal(unregisterHostByIdSpy.callCount, 1); |
+ resolve(null); |
+ }); |
+ }); |
+}); |
+ |
+// Check what happens when the HostDaemonFacade's startDaemon method |
+// fails and calls its onError argument. |
+QUnit.test('start with startDaemon failure', function(assert) { |
+ var controller = new remoting.HostController(); |
+ queueRegistryResponse(assert, true); |
+ mockGetClientBaseJid(controller); |
+ mockHostDaemonFacade.startDaemonResult = null; |
Jamie
2015/04/08 00:16:14
Why not FAILED/FAILED_DIRECTORY?
John Williams
2015/04/08 20:20:09
Added this as a separate case, but there are two d
Jamie
2015/04/08 22:29:29
Yuck. That sounds like something worth cleaning up
John Williams
2015/04/08 23:19:55
SGTM. When I work on legacy code I usually end up
Jamie
2015/04/08 23:28:42
Acknowledged.
|
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, true, function() { |
+ reject('test failed'); |
+ }, function(/** remoting.Error */ e) { |
+ assert.equal(e.getDetail(), 'startDaemon'); |
+ assert.equal(unregisterHostByIdSpy.callCount, 1); |
+ assert.equal(onLocalHostStartedSpy.callCount, 0); |
+ resolve(null); |
+ }); |
+ }); |
+}); |
+ |
+// Check what happens when the HostDaemonFacade's startDaemon method |
+// calls is onDone method with an async error code. |
+QUnit.test('start with startDaemon cancelled', function(assert) { |
+ var controller = new remoting.HostController(); |
+ queueRegistryResponse(assert, true); |
+ mockGetClientBaseJid(controller); |
+ mockHostDaemonFacade.startDaemonResult = |
+ remoting.HostController.AsyncResult.CANCELLED; |
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, true, function() { |
+ reject('test failed'); |
+ }, function(/** remoting.Error */ e) { |
+ assert.equal(e.getTag(), remoting.Error.Tag.CANCELLED); |
+ assert.equal(unregisterHostByIdSpy.callCount, 1); |
+ assert.equal(onLocalHostStartedSpy.callCount, 0); |
+ resolve(null); |
+ }); |
+ }); |
+}); |
+ |
+// Check what happens when the entire host registration process |
+// succeeds. |
+[false, true].forEach(function(/** boolean */ consent) { |
+ QUnit.test('start succeeds with consent=' + consent, function(assert) { |
+ var controller = new remoting.HostController(); |
+ queueRegistryResponse(assert, true); |
+ mockGetClientBaseJid(controller); |
+ return new Promise(function(resolve, reject) { |
+ controller.start(FAKE_HOST_PIN, consent, function() { |
+ assert.equal(getCredentialsFromAuthCodeSpy.callCount, 1); |
+ assert.deepEqual( |
+ getCredentialsFromAuthCodeSpy.args[0][0], |
+ FAKE_AUTH_CODE); |
+ assert.equal(getPinHashSpy.callCount, 1); |
+ assert.deepEqual( |
+ getPinHashSpy.args[0].slice(0, 2), |
+ [FAKE_UUID, FAKE_HOST_PIN]); |
+ |
+ assert.equal(unregisterHostByIdSpy.callCount, 0); |
+ assert.equal(onLocalHostStartedSpy.callCount, 1); |
+ assert.equal(startDaemonSpy.callCount, 1); |
+ assert.deepEqual( |
+ startDaemonSpy.args[0].slice(0, 2), |
+ [{ |
+ xmpp_login: FAKE_USER_EMAIL, |
+ oauth_refresh_token: FAKE_REFRESH_TOKEN, |
+ host_owner: FAKE_CLIENT_BASE_JID, |
+ host_owner_email: FAKE_USER_EMAIL, |
+ host_id: FAKE_UUID, |
+ host_name: FAKE_HOST_NAME, |
+ host_secret_hash: FAKE_PIN_HASH, |
+ private_key: FAKE_PRIVATE_KEY |
+ }, consent]); |
+ resolve(null); |
+ }, reject); |
+ }); |
+ }); |
+}); |
+ |
+// TODO(jrw): Add tests for |stop| method. |
+// TODO(jrw): Add tests for |updatePin| method. |
+// TODO(jrw): Add tests for |getLocalHostState| method. |
+// TODO(jrw): Add tests for |getLocalHostId| method. |
+// TODO(jrw): Add tests for |getPairedClients| method. |
+// TODO(jrw): Add tests for |deletePairedClient| method. |
+// TODO(jrw): Add tests for |clearPairedClients| method. |
+// TODO(jrw): Make sure getClientBaseJid_ method is tested. |
+ |
+})(); |