From 8e9809cf5cf3333c3105e7924fa3f91ff31a5f46 Mon Sep 17 00:00:00 2001 From: "robliao@chromium.org" Date: Thu, 20 Mar 2014 07:01:18 +0000 Subject: [PATCH] Server Request Tests and Mock Promise Enhancements Added some server request tests and improved the mock promise to handle these tests. The Mock Promise now better simulates the full extent of promises. BUG=164227 Review URL: https://codereview.chromium.org/202293003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@258239 0039d316-1c4b-4281-b951-d872f2087c98 --- .../google_now/background_unittest.gtestjs | 106 +++++++++++++++++++++ .../resources/google_now/common_test_util.js | 83 ++++++++++++---- 2 files changed, 168 insertions(+), 21 deletions(-) diff --git a/chrome/browser/resources/google_now/background_unittest.gtestjs b/chrome/browser/resources/google_now/background_unittest.gtestjs index d9cbb07d13e0..14d8e58429e1 100644 --- a/chrome/browser/resources/google_now/background_unittest.gtestjs +++ b/chrome/browser/resources/google_now/background_unittest.gtestjs @@ -52,6 +52,112 @@ TEST_F('GoogleNowBackgroundUnitTest', 'AreTasksConflicting', function() { }); /** + * Server Request Tests + */ +TEST_F('GoogleNowBackgroundUnitTest', 'AuthServerRequestSuccess', function() { + expectServerRequests(this, 200, '{}'); + var callbackCalled = false; + requestFromServer('GET', 'test/target').then(function(request) { + callbackCalled = true; + assertTrue(request.status === 200); + assertTrue(request.responseText === '{}'); + }); + assertTrue(callbackCalled); +}); + +TEST_F('GoogleNowBackgroundUnitTest', 'AuthServerRequestForbidden', function() { + this.makeAndRegisterMockApis(['authenticationManager.removeToken']); + this.mockApis.expects(once()).authenticationManager_removeToken(ANYTHING); + + expectServerRequests(this, 403, ''); + + var callbackCalled = false; + requestFromServer('GET', 'test/target').then(function(request) { + // The promise is resolved even on HTTP failures. + callbackCalled = true; + assertTrue(request.status === 403); + }); + assertTrue(callbackCalled); +}); + +TEST_F('GoogleNowBackgroundUnitTest', 'AuthServerRequestNoAuth', function() { + this.makeAndRegisterMockApis(['authenticationManager.removeToken']); + this.mockApis.expects(once()).authenticationManager_removeToken(ANYTHING); + + expectServerRequests(this, 401, ''); + + var callbackCalled = false; + requestFromServer('GET', 'test/target').then(function(request) { + // The promise is resolved even on HTTP failures. + callbackCalled = true; + assertTrue(request.status === 401); + }); + assertTrue(callbackCalled); +}); + +function expectServerRequests(fixture, httpStatus, responseText) { + fixture.makeAndRegisterMockApis([ + 'authenticationManager.getAuthToken', + 'buildServerRequest' + ]); + + function XMLHttpRequest() {} + + XMLHttpRequest.prototype = { + addEventListener: function(type, listener, wantsUntrusted) {}, + setRequestHeader: function(header, value) {}, + send: function() {} + } + + fixture.mockApis.expects(once()).authenticationManager_getAuthToken() + .will(returnValue(Promise.resolve('token'))); + + var mockXMLHttpRequest = mock(XMLHttpRequest); + var mockXMLHttpRequestProxy = mockXMLHttpRequest.proxy(); + fixture.mockApis.expects(once()) + .buildServerRequest(ANYTHING, ANYTHING, ANYTHING) + .will(returnValue(mockXMLHttpRequestProxy)); + + mockXMLHttpRequest.expects(once()) + .setRequestHeader('Authorization', 'Bearer token'); + + var loadEndSavedArgs = new SaveMockArguments(); + mockXMLHttpRequest.expects(once()) + .addEventListener( + loadEndSavedArgs.match(eq('loadend')), + loadEndSavedArgs.match(ANYTHING), + loadEndSavedArgs.match(eq(false))); + + mockXMLHttpRequestProxy.status = httpStatus; + mockXMLHttpRequestProxy.response = responseText; + mockXMLHttpRequestProxy.responseText = responseText; + + mockXMLHttpRequest.expects(once()).send() + .will(invokeCallback(loadEndSavedArgs, 1, mockXMLHttpRequestProxy)); +} + +TEST_F('GoogleNowBackgroundUnitTest', 'AuthServerRequestNoToken', function() { + this.makeAndRegisterMockApis([ + 'authenticationManager.getAuthToken', + 'buildServerRequest' + ]); + + this.mockApis.expects(once()).authenticationManager_getAuthToken() + .will(returnValue(Promise.reject())); + this.mockApis.expects(never()).buildServerRequest() + + var thenCalled = false; + var catchCalled = false; + requestFromServer('GET', 'test/target').then(function(request) { + thenCalled = true; + }).catch(function() { + catchCalled = true; + }); + assertFalse(thenCalled); + assertTrue(catchCalled); +}) + +/** * Mocks global functions and APIs that initialize() depends upon. * @param {Test} fixture Test fixture. */ diff --git a/chrome/browser/resources/google_now/common_test_util.js b/chrome/browser/resources/google_now/common_test_util.js index 66d65f49d9ef..c090c9545bb0 100644 --- a/chrome/browser/resources/google_now/common_test_util.js +++ b/chrome/browser/resources/google_now/common_test_util.js @@ -78,36 +78,70 @@ function getMockHandlerContainer(eventIdentifier) { * As a result, we can't use built-in JS promises since they run asynchronously. * Instead of mocking all possible calls to promises, a skeleton * implementation is provided to get the tests to pass. + * + * This functionality and logic originates from ECMAScript 6's spec of promises. */ var Promise = function() { function PromisePrototypeObject(asyncTask) { - var result; - var resolved = false; - asyncTask( - function(asyncResult) { - result = asyncResult; - resolved = true; - }, - function(asyncFailureResult) { - result = asyncFailureResult; - resolved = false; - }); + function isThenable(value) { + return (typeof value === 'object') && isCallable(value.then); + } - function then(callback) { - if (resolved) { - callback.call(null, result); + function isCallable(value) { + return typeof value === 'function'; + } + + function callResolveRejectFunc(func) { + var funcResult; + var funcResolved = false; + func( + function(resolveResult) { + funcResult = resolveResult; + funcResolved = true; + }, + function(rejectResult) { + funcResult = rejectResult; + funcResolved = false; + }); + return { result: funcResult, resolved: funcResolved }; + } + + function then(onResolve, onReject) { + var resolutionHandler = + isCallable(onResolve) ? onResolve : function() { return result; }; + var rejectionHandler = + isCallable(onReject) ? onReject : function() { return result; }; + var handlerResult = + resolved ? resolutionHandler(result) : rejectionHandler(result); + var promiseResolved = resolved; + if (isThenable(handlerResult)) { + var resolveReject = callResolveRejectFunc(handlerResult.then); + handlerResult = resolveReject.result; + promiseResolved = resolveReject.resolved; + } + + if (promiseResolved) { + return Promise.resolve(handlerResult); + } else { + return Promise.reject(handlerResult); } - return this; } // Promises use the function name "catch" to call back error handlers. // We can't use "catch" since function or variable names cannot use the word // "catch". - function catchFunc(callback) { - if (!resolved) { - callback.call(null, result); - } - return this; + function catchFunc(onRejected) { + return this.then(undefined, onRejected); + } + + var resolveReject = callResolveRejectFunc(asyncTask); + var result = resolveReject.result; + var resolved = resolveReject.resolved; + + if (isThenable(result)) { + var thenResolveReject = callResolveRejectFunc(result.then); + result = thenResolveReject.result; + resolved = thenResolveReject.resolved; } return {then: then, catch: catchFunc, isPromise: true}; @@ -137,12 +171,19 @@ var Promise = function() { return promise; } + function reject(value) { + var promise = new PromisePrototypeObject(function(resolve, reject) { + reject(value); + }); + return promise; + } + PromisePrototypeObject.all = all; PromisePrototypeObject.resolve = resolve; + PromisePrototypeObject.reject = reject; return PromisePrototypeObject; }(); - /** * Sets up the test to expect a Chrome Local Storage call. * @param {Object} fixture Mock JS Test Object. -- 2.11.4.GIT