// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(robliao,vadimt): Determine the granularity of testing to perform. /** * Test fixture for background.js. * @constructor * @extends {testing.Test} */ function GoogleNowBackgroundUnitTest () { testing.Test.call(this); } GoogleNowBackgroundUnitTest.prototype = { __proto__: testing.Test.prototype, /** @override */ extraLibraries: [ 'common_test_util.js', 'background_test_util.js', 'background.js' ] }; var TEST_NAME = 'GoogleNowBackgroundUnitTest'; /** * Tasks Conflict Test */ TEST_F(TEST_NAME, 'AreTasksConflicting', function() { function testTaskPair(newTaskName, scheduledTaskName, expected) { assertTrue(areTasksConflicting(newTaskName, scheduledTaskName) == expected, '(' + newTaskName + ', ' + scheduledTaskName + ')'); } testTaskPair(UPDATE_CARDS_TASK_NAME, UPDATE_CARDS_TASK_NAME, true); testTaskPair(UPDATE_CARDS_TASK_NAME, DISMISS_CARD_TASK_NAME, false); testTaskPair(UPDATE_CARDS_TASK_NAME, RETRY_DISMISS_TASK_NAME, false); testTaskPair(UPDATE_CARDS_TASK_NAME, STATE_CHANGED_TASK_NAME, false); testTaskPair(DISMISS_CARD_TASK_NAME, UPDATE_CARDS_TASK_NAME, false); testTaskPair(DISMISS_CARD_TASK_NAME, DISMISS_CARD_TASK_NAME, false); testTaskPair(DISMISS_CARD_TASK_NAME, RETRY_DISMISS_TASK_NAME, false); testTaskPair(DISMISS_CARD_TASK_NAME, STATE_CHANGED_TASK_NAME, false); testTaskPair(RETRY_DISMISS_TASK_NAME, UPDATE_CARDS_TASK_NAME, true); testTaskPair(RETRY_DISMISS_TASK_NAME, DISMISS_CARD_TASK_NAME, true); testTaskPair(RETRY_DISMISS_TASK_NAME, RETRY_DISMISS_TASK_NAME, true); testTaskPair(RETRY_DISMISS_TASK_NAME, STATE_CHANGED_TASK_NAME, false); testTaskPair(STATE_CHANGED_TASK_NAME, UPDATE_CARDS_TASK_NAME, false); testTaskPair(STATE_CHANGED_TASK_NAME, DISMISS_CARD_TASK_NAME, false); testTaskPair(STATE_CHANGED_TASK_NAME, RETRY_DISMISS_TASK_NAME, false); testTaskPair(STATE_CHANGED_TASK_NAME, STATE_CHANGED_TASK_NAME, false); }); /** * Server Request Tests */ TEST_F(TEST_NAME, '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(TEST_NAME, 'AuthServerRequestForbidden', function() { this.makeAndRegisterMockApis(['authenticationManager.removeToken']); this.mockApis.expects(once()).authenticationManager_removeToken(ANYTHING); expectServerRequests(this, 403, ''); var thenCalled = false; var catchCalled = false; requestFromServer('GET', 'test/target').then(function(request) { thenCalled = true; }).catch(function(request) { // The promise is rejected on HTTP failures. catchCalled = true; assertTrue(request.status === 403); }); assertFalse(thenCalled); assertTrue(catchCalled); }); TEST_F(TEST_NAME, 'AuthServerRequestNoAuth', function() { this.makeAndRegisterMockApis(['authenticationManager.removeToken']); this.mockApis.expects(once()).authenticationManager_removeToken(ANYTHING); expectServerRequests(this, 401, ''); var thenCalled = false; var catchCalled = false; requestFromServer('GET', 'test/target').then(function(request) { thenCalled = true; }).catch(function(request) { // The promise is rejected on HTTP failures. catchCalled = true; assertTrue(request.status === 401); }); assertFalse(thenCalled); assertTrue(catchCalled); }); 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(TEST_NAME, '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); }) /** * requestNotificationGroupsFromServer Tests */ TEST_F(TEST_NAME, 'RequestNotificationGroupsFromServerEmpty', function() { this.makeAndRegisterMockGlobals([ 'shouldShowExplanatoryCard', 'recordEvent', 'requestFromServer' ]); this.mockGlobals.expects(once()).shouldShowExplanatoryCard() .will(returnValue(false)); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.REQUEST_FOR_CARDS_TOTAL); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.REQUEST_FOR_CARDS_SUCCESS); var requestFromServerArgs = new SaveMockArguments(); this.mockGlobals.expects(once()).requestFromServer( requestFromServerArgs.match(eq('GET')), requestFromServerArgs.match(ANYTHING)) .will(returnValue( Promise.resolve({status: 200, responseText: "{}"}))); var thenCalled = false; var catchCalled = false; requestNotificationGroupsFromServer([]).then(function() { thenCalled = true; }).catch(function() { catchCalled = true; }); assertTrue(thenCalled); assertFalse(catchCalled); var pathAndQuery = requestFromServerArgs.arguments[1]; var query = pathAndQuery.split('?')[1]; assertTrue(query.search('timeZoneOffsetMs') >= 0); assertTrue(query.search('uiLocale') >= 0); assertFalse(query.search('cardExplanation') >= 0); }); TEST_F(TEST_NAME, 'RequestNotificationGroupsFromServerWithGroups', function() { this.makeAndRegisterMockGlobals([ 'shouldShowExplanatoryCard', 'recordEvent', 'requestFromServer' ]); this.mockGlobals.expects(once()).shouldShowExplanatoryCard() .will(returnValue(false)); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.REQUEST_FOR_CARDS_TOTAL); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.REQUEST_FOR_CARDS_SUCCESS); var requestFromServerArgs = new SaveMockArguments(); this.mockGlobals.expects(once()).requestFromServer( requestFromServerArgs.match(eq('GET')), requestFromServerArgs.match(ANYTHING)) .will(returnValue( Promise.resolve({status: 200, responseText: "{}"}))); var thenCalled = false; var catchCalled = false; requestNotificationGroupsFromServer(['A', 'B', 'C']).then(function() { thenCalled = true; }).catch(function() { catchCalled = true; }); assertTrue(thenCalled); assertFalse(catchCalled); var pathAndQuery = requestFromServerArgs.arguments[1]; var query = pathAndQuery.split('?')[1]; assertTrue(query.search('timeZoneOffsetMs') >= 0); assertTrue(query.search('uiLocale') >= 0); assertFalse(query.search('cardExplanation') >= 0); assertTrue(query.search('requestTypes=A') >= 0); assertTrue(query.search('requestTypes=B') >= 0); assertTrue(query.search('requestTypes=C') >= 0); }); TEST_F(TEST_NAME, 'RequestNotificationGroupsFromServerExplanatory', function() { this.makeAndRegisterMockGlobals([ 'shouldShowExplanatoryCard', 'recordEvent', 'requestFromServer' ]); this.mockGlobals.expects(once()).shouldShowExplanatoryCard() .will(returnValue(true)); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.REQUEST_FOR_CARDS_TOTAL); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.REQUEST_FOR_CARDS_SUCCESS); var requestFromServerArgs = new SaveMockArguments(); this.mockGlobals.expects(once()).requestFromServer( requestFromServerArgs.match(eq('GET')), requestFromServerArgs.match(ANYTHING)) .will(returnValue( Promise.resolve({status: 200, responseText: "{}"}))); var thenCalled = false; var catchCalled = false; requestNotificationGroupsFromServer([]).then(function() { thenCalled = true; }).catch(function() { catchCalled = true; }); assertTrue(thenCalled); assertFalse(catchCalled); var pathAndQuery = requestFromServerArgs.arguments[1]; var query = pathAndQuery.split('?')[1]; assertTrue(query.search('timeZoneOffsetMs') >= 0); assertTrue(query.search('uiLocale') >= 0); assertTrue(query.search('cardExplanation=true') >= 0); }); TEST_F(TEST_NAME, 'RequestNotificationGroupsFromServerFailure', function() { this.makeAndRegisterMockGlobals([ 'shouldShowExplanatoryCard', 'recordEvent', 'requestFromServer' ]); this.mockGlobals.expects(once()).shouldShowExplanatoryCard() .will(returnValue(false)); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.REQUEST_FOR_CARDS_TOTAL); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.REQUEST_FOR_CARDS_SUCCESS); var requestFromServerArgs = new SaveMockArguments(); this.mockGlobals.expects(once()).requestFromServer( requestFromServerArgs.match(eq('GET')), requestFromServerArgs.match(ANYTHING)) .will(returnValue( Promise.reject({status: 401}))); var thenCalled = false; var catchCalled = false; requestNotificationGroupsFromServer([]).then(function() { thenCalled = true; }).catch(function() { catchCalled = true; }); assertFalse(thenCalled); assertTrue(catchCalled); }); /** * requestAndUpdateOptIn Tests */ TEST_F(TEST_NAME, 'RequestAndUpdateOptInOptedIn', function() { this.makeAndRegisterMockApis([ 'chrome.storage.local.set', 'requestFromServer' ]); this.mockApis.expects(once()).requestFromServer('GET', 'settings/optin') .will(returnValue(Promise.resolve({ status: 200, responseText: '{"value": true}'}))); this.mockApis.expects(once()) .chrome_storage_local_set(eqJSON({googleNowEnabled: true})); var thenCalled = false; var catchCalled = false; requestAndUpdateOptedIn().then(function(optedIn) { thenCalled = true; assertTrue(optedIn); }).catch(function() { catchCalled = true; }); assertTrue(thenCalled); assertFalse(catchCalled); }); TEST_F(TEST_NAME, 'RequestAndUpdateOptInOptedOut', function() { this.makeAndRegisterMockApis([ 'chrome.storage.local.set', 'requestFromServer' ]); this.mockApis.expects(once()).requestFromServer('GET', 'settings/optin') .will(returnValue(Promise.resolve({ status: 200, responseText: '{"value": false}'}))); this.mockApis.expects(once()) .chrome_storage_local_set(eqJSON({googleNowEnabled: false})); var thenCalled = false; var catchCalled = false; requestAndUpdateOptedIn().then(function(optedIn) { thenCalled = true; assertFalse(optedIn); }).catch(function() { catchCalled = true; }); assertTrue(thenCalled); assertFalse(catchCalled); }); TEST_F(TEST_NAME, 'RequestAndUpdateOptInFailure', function() { this.makeAndRegisterMockApis([ 'chrome.storage.local.set', 'requestFromServer' ]); this.mockApis.expects(once()).requestFromServer('GET', 'settings/optin') .will(returnValue(Promise.reject({status: 404}))); this.mockApis.expects(never()).chrome_storage_local_set(); var thenCalled = false; var catchCalled = false; requestAndUpdateOptedIn().then(function() { thenCalled = true; }).catch(function() { catchCalled = true; }); assertFalse(thenCalled); assertTrue(catchCalled); }); /** * pollOptedInNoImmediateRecheck Tests */ TEST_F(TEST_NAME, 'pollOptedInNoImmediateRecheckOptedIn', function() { this.makeAndRegisterMockApis([ 'requestAndUpdateOptedIn', 'instrumented.metricsPrivate.getVariationParams', 'optInPollAttempts.start' ]); this.mockApis.expects(once()).requestAndUpdateOptedIn() .will(returnValue(Promise.resolve(true))); this.mockApis.expects(never()) .instrumented_metricsPrivate_getVariationParams(); this.mockApis.expects(never()).optInPollAttempts_start(); pollOptedInNoImmediateRecheck(); }); TEST_F(TEST_NAME, 'pollOptedInNoImmediateRecheckOptedOut', function() { this.makeAndRegisterMockApis([ 'requestAndUpdateOptedIn', 'instrumented.metricsPrivate.getVariationParams', 'optInPollAttempts.start' ]); this.mockApis.expects(once()).requestAndUpdateOptedIn() .will(returnValue(Promise.resolve(false))); var getVariationParamsSavedArgs = new SaveMockArguments(); this.mockApis.expects(once()) .instrumented_metricsPrivate_getVariationParams( getVariationParamsSavedArgs.match(eq('GoogleNow')), getVariationParamsSavedArgs.match(ANYTHING)) .will(invokeCallback(getVariationParamsSavedArgs, 1, {})); this.mockApis.expects(once()) .optInPollAttempts_start(DEFAULT_OPTIN_CHECK_PERIOD_SECONDS); pollOptedInNoImmediateRecheck(); }); TEST_F(TEST_NAME, 'pollOptedInNoImmediateRecheckFailure', function() { this.makeAndRegisterMockApis([ 'requestAndUpdateOptedIn', 'instrumented.metricsPrivate.getVariationParams', 'optInPollAttempts.start' ]); this.mockApis.expects(once()).requestAndUpdateOptedIn() .will(returnValue(Promise.reject())); var getVariationParamsSavedArgs = new SaveMockArguments(); this.mockApis.expects(once()) .instrumented_metricsPrivate_getVariationParams( getVariationParamsSavedArgs.match(eq('GoogleNow')), getVariationParamsSavedArgs.match(ANYTHING)) .will(invokeCallback(getVariationParamsSavedArgs, 1, {})); this.mockApis.expects(once()) .optInPollAttempts_start(DEFAULT_OPTIN_CHECK_PERIOD_SECONDS); pollOptedInNoImmediateRecheck(); }); /** * getGroupsToRequest Tests */ TEST_F(TEST_NAME, 'GetGroupsToRequestNone', function() { this.makeAndRegisterMockApis([ 'fillFromChromeLocalStorage', 'Date.now' ]); this.mockApis.expects(once()) .fillFromChromeLocalStorage(eqJSON({notificationGroups: {}})) .will(returnValue(Promise.resolve({notificationGroups: {}}))); this.mockApis.expects(once()).Date_now().will(returnValue(20)); getGroupsToRequest().then(function(groupsToRequest) { assertTrue(JSON.stringify(groupsToRequest) === '[]'); }); }); TEST_F(TEST_NAME, 'GetGroupsToRequestWithGroups', function() { this.makeAndRegisterMockApis([ 'fillFromChromeLocalStorage', 'Date.now' ]); this.mockApis.expects(once()) .fillFromChromeLocalStorage(eqJSON({notificationGroups: {}})) .will(returnValue(Promise.resolve({notificationGroups: { TIME18: {nextPollTime: 18}, TIME19: {nextPollTime: 19}, TIME20: {nextPollTime: 20}, TIME21: {nextPollTime: 21}, TIME22: {nextPollTime: 22}, TIMEUNDEF: {} }}))); this.mockApis.expects(once()).Date_now().will(returnValue(20)); getGroupsToRequest().then(function(groupsToRequest) { assertTrue(groupsToRequest.length == 3); assertTrue(groupsToRequest.indexOf('TIME18') >= 0); assertTrue(groupsToRequest.indexOf('TIME19') >= 0); assertTrue(groupsToRequest.indexOf('TIME20') >= 0); }); }); /** * combineGroup Tests */ TEST_F(TEST_NAME, 'CombineGroup', function() { // Tests combineGroup function. Verifies that both notifications with and // without show time are handled correctly and that cards are correctly // added to existing cards with same ID or start a new combined card. // Setup and expectations. var combinedCards = { 'EXISTING CARD': [1] }; var receivedNotificationNoShowTime = { chromeNotificationId: 'EXISTING CARD', trigger: {hideTimeSec: 1} }; var receivedNotificationWithShowTime = { chromeNotificationId: 'NEW CARD', trigger: {showTimeSec: 2, hideTimeSec: 3} } var storedGroup = { cardsTimestamp: 10000, cards: [ receivedNotificationNoShowTime, receivedNotificationWithShowTime ] }; // Invoking the tested function. combineGroup(combinedCards, storedGroup); // Check the output value. var expectedCombinedCards = { 'EXISTING CARD': [ 1, { receivedNotification: receivedNotificationNoShowTime, hideTime: 11000 } ], 'NEW CARD': [ { receivedNotification: receivedNotificationWithShowTime, showTime: 12000, hideTime: 13000 } ] }; assertEquals( JSON.stringify(expectedCombinedCards), JSON.stringify(combinedCards)); }); /** * Mocks global functions and APIs that initialize() depends upon. * @param {Test} fixture Test fixture. */ function mockInitializeDependencies(fixture) { fixture.makeAndRegisterMockGlobals([ 'pollOptedInNoImmediateRecheck', 'recordEvent', 'removeAllCards', 'setBackgroundEnable', 'startPollingCards', 'stopPollingCards' ]); fixture.makeAndRegisterMockApis([ 'authenticationManager.isSignedIn', 'chrome.storage.local.remove', 'fillFromChromeLocalStorage', 'instrumented.metricsPrivate.getVariationParams', 'instrumented.notifications.getAll', 'instrumented.notifications.getPermissionLevel', 'instrumented.webstorePrivate.getBrowserLogin', 'optInPollAttempts.isRunning', 'optInPollAttempts.stop', 'tasks.add', 'updateCardsAttempts.isRunning', 'updateCardsAttempts.stop' ]); } /** * Sets up the test to expect the state machine calls and send * the specified state machine state. Currently used to test initialize(). * Note that this CAN NOT be used if any of the methods below are called * outside of this context with the same argument matchers. * expects() calls cannot be chained with the same argument matchers. * @param {object} fixture Test fixture. * @param {string} testIdentityToken getAuthToken callback token. * @param {object} testExperimentVariationParams Response of * metricsPrivate.getVariationParams. * @param {string} testExperimentVariationParams Response of * notifications.getPermissionLevel. * @param {boolean} testGoogleNowEnabled True if the user is opted in to Google * Now. */ function expectStateMachineCalls( fixture, testIdentityToken, testExperimentVariationParams, testNotificationPermissionLevel, testGoogleNowEnabled) { fixture.mockApis.expects(once()). authenticationManager_isSignedIn(). will(returnValue(new Promise(function(resolve) { resolve(!!testIdentityToken); }))); var getVariationParamsSavedArgs = new SaveMockArguments(); fixture.mockApis.expects(once()). instrumented_metricsPrivate_getVariationParams( getVariationParamsSavedArgs.match(ANYTHING), getVariationParamsSavedArgs.match(ANYTHING)). will(invokeCallback( getVariationParamsSavedArgs, 1, testExperimentVariationParams)); var notificationGetPermissionLevelSavedArgs = new SaveMockArguments(); fixture.mockApis.expects(once()). instrumented_notifications_getPermissionLevel( notificationGetPermissionLevelSavedArgs.match(ANYTHING)). will(invokeCallback( notificationGetPermissionLevelSavedArgs, 0, testNotificationPermissionLevel)) expectChromeLocalStorageGet( fixture, {googleNowEnabled: false}, {googleNowEnabled: testGoogleNowEnabled}); var updateCardsAttemptsIsRunningSavedArgs = new SaveMockArguments(); fixture.mockApis.expects(once()). updateCardsAttempts_isRunning( updateCardsAttemptsIsRunningSavedArgs.match(ANYTHING)). will( invokeCallback( updateCardsAttemptsIsRunningSavedArgs, 0, undefined)); var optInPollAttemptsIsRunningSavedArgs = new SaveMockArguments(); fixture.mockApis.expects(once()). optInPollAttempts_isRunning( optInPollAttemptsIsRunningSavedArgs.match(ANYTHING)). will( invokeCallback( optInPollAttemptsIsRunningSavedArgs, 0, undefined)); } /** * Sets up the test to expect the initialization calls that * initialize() invokes. * Note that this CAN NOT be used if any of the methods below are called * outside of this context with the same argument matchers. * expects() calls cannot be chained with the same argument matchers. */ function expectInitialization(fixture) { var tasksAddSavedArgs = new SaveMockArguments(); fixture.mockApis.expects(once()). tasks_add( tasksAddSavedArgs.match(ANYTHING), tasksAddSavedArgs.match(ANYTHING)). will(invokeCallback(tasksAddSavedArgs, 1, function() {})); // The ordering here between stubs and expects is important. // We only care about the EXTENSION_START event. The other events are covered // by the NoCards tests below. Reversing the calls will cause all recordEvent // calls to be unexpected. fixture.mockGlobals.stubs().recordEvent(ANYTHING); fixture.mockGlobals. expects(once()).recordEvent(GoogleNowEvent.EXTENSION_START); } TEST_F(TEST_NAME,'Initialize_SignedOut', function() { // Tests the case when getAuthToken fails most likely because the user is // not signed in. In this case, the function should quietly exit after // finding out that getAuthToken fails. // Setup and expectations. var testIdentityToken = undefined; var testExperimentVariationParams = {}; var testNotificationPermissionLevel = 'denied'; var testGoogleNowEnabled = undefined; mockInitializeDependencies(this); expectInitialization(this); expectStateMachineCalls( this, testIdentityToken, testExperimentVariationParams, testNotificationPermissionLevel, testGoogleNowEnabled); this.mockGlobals.expects(once()).setBackgroundEnable(false); this.mockGlobals.expects(never()).startPollingCards(); this.mockGlobals.expects(once()).stopPollingCards(); this.mockGlobals.expects(once()).removeAllCards(); this.mockGlobals.expects(never()).pollOptedInNoImmediateRecheck(); this.mockApis.expects(once()).optInPollAttempts_stop(); // Invoking the tested function. initialize(); }); TEST_F(TEST_NAME,'Initialize_NotificationDisabled', function() { // Tests the case when Google Now is disabled in the notifications center. // Setup and expectations. var testIdentityToken = 'some identity token'; var testExperimentVariationParams = {}; var testNotificationPermissionLevel = 'denied'; var testGoogleNowEnabled = undefined; mockInitializeDependencies(this); expectInitialization(this); expectStateMachineCalls( this, testIdentityToken, testExperimentVariationParams, testNotificationPermissionLevel, testGoogleNowEnabled); this.mockGlobals.expects(once()).setBackgroundEnable(false); this.mockGlobals.expects(never()).startPollingCards(); this.mockGlobals.expects(once()).stopPollingCards(); this.mockGlobals.expects(once()).removeAllCards(); this.mockGlobals.expects(never()).pollOptedInNoImmediateRecheck(); this.mockApis.expects(once()).optInPollAttempts_stop(); // Invoking the tested function. initialize(); }); TEST_F(TEST_NAME, 'Initialize_NoBackground', function() { // Tests when the no background variation is received. // Setup and expectations. var testIdentityToken = 'some identity token'; var testExperimentVariationParams = {canEnableBackground: 'false'}; var testNotificationPermissionLevel = 'granted'; var testGoogleNowEnabled = true; mockInitializeDependencies(this); expectInitialization(this); expectStateMachineCalls( this, testIdentityToken, testExperimentVariationParams, testNotificationPermissionLevel, testGoogleNowEnabled); this.mockGlobals.expects(once()).setBackgroundEnable(false); this.mockGlobals.expects(once()).startPollingCards(); this.mockGlobals.expects(never()).stopPollingCards(); this.mockGlobals.expects(never()).removeAllCards(); this.mockGlobals.expects(never()).pollOptedInNoImmediateRecheck(); this.mockApis.expects(once()).optInPollAttempts_stop(); // Invoking the tested function. initialize(); }); TEST_F(TEST_NAME, 'Initialize_GoogleNowDisabled', function() { // Tests when the user has Google Now disabled. // Setup and expectations. var testIdentityToken = 'some identity token'; var testExperimentVariationParams = {}; var testNotificationPermissionLevel = 'granted'; var testGoogleNowEnabled = false; mockInitializeDependencies(this); expectInitialization(this); expectStateMachineCalls( this, testIdentityToken, testExperimentVariationParams, testNotificationPermissionLevel, testGoogleNowEnabled); this.mockGlobals.expects(once()).setBackgroundEnable(false); this.mockGlobals.expects(never()).startPollingCards(); this.mockGlobals.expects(once()).stopPollingCards(); this.mockGlobals.expects(once()).removeAllCards(); this.mockGlobals.expects(once()).pollOptedInNoImmediateRecheck(); this.mockApis.expects(never()).optInPollAttempts_stop(); // Invoking the tested function. initialize(); }); TEST_F(TEST_NAME, 'Initialize_RunGoogleNow', function() { // Tests if Google Now will invoke startPollingCards when all // of the required state is fulfilled. // Setup and expectations. var testIdentityToken = 'some identity token'; var testExperimentVariationParams = {}; var testNotificationPermissionLevel = 'granted'; var testGoogleNowEnabled = true; mockInitializeDependencies(this); expectInitialization(this); expectStateMachineCalls( this, testIdentityToken, testExperimentVariationParams, testNotificationPermissionLevel, testGoogleNowEnabled); this.mockGlobals.expects(once()).setBackgroundEnable(true); this.mockGlobals.expects(once()).startPollingCards(); this.mockGlobals.expects(never()).stopPollingCards(); this.mockGlobals.expects(never()).removeAllCards(); this.mockGlobals.expects(never()).pollOptedInNoImmediateRecheck(); this.mockApis.expects(once()).optInPollAttempts_stop(); // Invoking the tested function. initialize(); }); /** * No Cards Event Recording Tests */ TEST_F(TEST_NAME, 'NoCardsSignedOut', function() { var signedIn = false; var notificationEnabled = false; var googleNowEnabled = false; this.makeAndRegisterMockGlobals([ 'recordEvent', 'removeAllCards', 'setBackgroundEnable', 'setShouldPollCards', 'setShouldPollOptInStatus']); this.mockGlobals.stubs().removeAllCards(); this.mockGlobals.stubs().setBackgroundEnable(ANYTHING); this.mockGlobals.stubs().setShouldPollCards(ANYTHING); this.mockGlobals.stubs().setShouldPollOptInStatus(ANYTHING); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.STOPPED); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.SIGNED_OUT); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.NOTIFICATION_DISABLED); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.GOOGLE_NOW_DISABLED); updateRunningState(signedIn, true, notificationEnabled, googleNowEnabled); }); TEST_F(TEST_NAME, 'NoCardsNotificationsDisabled', function() { var signedIn = true; var notificationEnabled = false; var googleNowEnabled = false; this.makeAndRegisterMockGlobals([ 'recordEvent', 'removeAllCards', 'setBackgroundEnable', 'setShouldPollCards', 'setShouldPollOptInStatus']); this.mockGlobals.stubs().removeAllCards(); this.mockGlobals.stubs().setBackgroundEnable(ANYTHING); this.mockGlobals.stubs().setShouldPollCards(ANYTHING); this.mockGlobals.stubs().setShouldPollOptInStatus(ANYTHING); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.STOPPED); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.SIGNED_OUT); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.NOTIFICATION_DISABLED); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.GOOGLE_NOW_DISABLED); updateRunningState(signedIn, true, notificationEnabled, googleNowEnabled); }); TEST_F(TEST_NAME, 'NoCardsGoogleNowDisabled', function() { var signedIn = true; var notificationEnabled = true; var googleNowEnabled = false; this.makeAndRegisterMockGlobals([ 'recordEvent', 'removeAllCards', 'setBackgroundEnable', 'setShouldPollCards', 'setShouldPollOptInStatus']); this.mockGlobals.stubs().removeAllCards(); this.mockGlobals.stubs().setBackgroundEnable(ANYTHING); this.mockGlobals.stubs().setShouldPollCards(ANYTHING); this.mockGlobals.stubs().setShouldPollOptInStatus(ANYTHING); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.STOPPED); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.SIGNED_OUT); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.NOTIFICATION_DISABLED); this.mockGlobals.expects(once()).recordEvent( GoogleNowEvent.GOOGLE_NOW_DISABLED); updateRunningState(signedIn, true, notificationEnabled, googleNowEnabled); }); TEST_F(TEST_NAME, 'NoCardsEverythingEnabled', function() { var signedIn = true; var notificationEnabled = true; var googleNowEnabled = true; this.makeAndRegisterMockGlobals([ 'recordEvent', 'removeAllCards', 'setBackgroundEnable', 'setShouldPollCards', 'setShouldPollOptInStatus']); this.mockGlobals.stubs().removeAllCards(); this.mockGlobals.stubs().setBackgroundEnable(ANYTHING); this.mockGlobals.stubs().setShouldPollCards(ANYTHING); this.mockGlobals.stubs().setShouldPollOptInStatus(ANYTHING); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.STOPPED); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.SIGNED_OUT); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.NOTIFICATION_DISABLED); this.mockGlobals.expects(never()).recordEvent( GoogleNowEvent.GOOGLE_NOW_DISABLED); updateRunningState(signedIn, true, notificationEnabled, googleNowEnabled); }); /** * Mocks global functions and APIs that onNotificationClicked() depends upon. * @param {Test} fixture Test fixture. */ function mockOnNotificationClickedDependencies(fixture) { fixture.makeAndRegisterMockApis([ 'chrome.windows.create', 'chrome.windows.update', 'fillFromChromeLocalStorage', 'instrumented.tabs.create']); } TEST_F(TEST_NAME, 'OnNotificationClicked_NoData', function() { // Tests the case when there is no data associated with notification id. // In this case, the function should do nothing. // Setup and expectations. var testNotificationId = 'TEST_ID'; var testNotificationDataRequest = {notificationsData: {}}; var testNotificationData = {notificationsData: {}}; mockOnNotificationClickedDependencies(this); this.makeMockLocalFunctions(['selector']); expectChromeLocalStorageGet( this, testNotificationDataRequest, testNotificationData); // Invoking the tested function. onNotificationClicked( testNotificationId, this.mockLocalFunctions.functions().selector); }); TEST_F(TEST_NAME, 'OnNotificationClicked_ActionUrlsUndefined', function() { // Tests the case when the data associated with notification id is // 'undefined'. // In this case, the function should do nothing. // Setup and expectations. var testActionUrls = undefined; var testNotificationId = 'TEST_ID'; var testNotificationDataRequest = {notificationsData: {}}; var testNotificationData = { notificationsData: {'TEST_ID': {actionUrls: testActionUrls}} }; mockOnNotificationClickedDependencies(this); this.makeMockLocalFunctions(['selector']); expectChromeLocalStorageGet( this, testNotificationDataRequest, testNotificationData); this.mockLocalFunctions.expects(once()) .selector(eqJSON( testNotificationData.notificationsData[testNotificationId])) .will(returnValue(undefined)); // Invoking the tested function. onNotificationClicked( testNotificationId, this.mockLocalFunctions.functions().selector); }); TEST_F(TEST_NAME, 'OnNotificationClicked_TabCreateSuccess', function() { // Tests the selected URL is OK and crome.tabs.create suceeds. // Setup and expectations. var testActionUrls = {testField: 'TEST VALUE'}; var testNotificationId = 'TEST_ID'; var testNotificationDataRequest = {notificationsData: {}}; var testNotificationData = { notificationsData: {'TEST_ID': {actionUrls: testActionUrls}} }; var testActionUrl = 'http://testurl.com'; var testCreatedTab = {windowId: 239}; mockOnNotificationClickedDependencies(this); this.makeMockLocalFunctions(['selector']); expectChromeLocalStorageGet( this, testNotificationDataRequest, testNotificationData); this.mockLocalFunctions.expects(once()) .selector(eqJSON( testNotificationData.notificationsData[testNotificationId])) .will(returnValue(testActionUrl)); var chromeTabsCreateSavedArgs = new SaveMockArguments(); this.mockApis.expects(once()). instrumented_tabs_create( chromeTabsCreateSavedArgs.match(eqJSON({url: testActionUrl})), chromeTabsCreateSavedArgs.match(ANYTHING)). will(invokeCallback(chromeTabsCreateSavedArgs, 1, testCreatedTab)); this.mockApis.expects(once()).chrome_windows_update( testCreatedTab.windowId, eqJSON({focused: true})); // Invoking the tested function. onNotificationClicked( testNotificationId, this.mockLocalFunctions.functions().selector); }); TEST_F(TEST_NAME, 'OnNotificationClicked_TabCreateFail', function() { // Tests the selected URL is OK and crome.tabs.create fails. // In this case, the function should invoke chrome.windows.create as a // second attempt. // Setup and expectations. var testActionUrls = {testField: 'TEST VALUE'}; var testNotificationId = 'TEST_ID'; var testNotificationDataRequest = {notificationsData: {}}; var testNotificationData = { notificationsData: {'TEST_ID': {actionUrls: testActionUrls}} }; var testActionUrl = 'http://testurl.com'; var testCreatedTab = undefined; // chrome.tabs.create fails mockOnNotificationClickedDependencies(this); this.makeMockLocalFunctions(['selector']); expectChromeLocalStorageGet( this, testNotificationDataRequest, testNotificationData); this.mockLocalFunctions.expects(once()) .selector(eqJSON( testNotificationData.notificationsData[testNotificationId])) .will(returnValue(testActionUrl)); var chromeTabsCreateSavedArgs = new SaveMockArguments(); this.mockApis.expects(once()). instrumented_tabs_create( chromeTabsCreateSavedArgs.match(eqJSON({url: testActionUrl})), chromeTabsCreateSavedArgs.match(ANYTHING)). will(invokeCallback(chromeTabsCreateSavedArgs, 1, testCreatedTab)); this.mockApis.expects(once()).chrome_windows_create( eqJSON({url: testActionUrl, focused: true})); // Invoking the tested function. onNotificationClicked( testNotificationId, this.mockLocalFunctions.functions().selector); }); TEST_F(TEST_NAME, 'ShowNotificationGroups', function() { // Tests showNotificationGroups function. Checks that the function properly // deletes the card that didn't get an update, updates existing card and // creates a new card that previously didn't exist. // Setup and expectations. var existingNotifications = { 'SHOULD BE DELETED': 'SOMETHING', 'SHOULD BE KEPT': 'SOMETHING' }; var keptCard = { chromeNotificationId: 'SHOULD BE KEPT', trigger: {showTimeSec: 0, hideTimeSec: 0} }; var keptNotification = { receivedNotification: keptCard, showTime: 0, hideTime: 0 }; var newCard = { chromeNotificationId: 'NEW CARD', trigger: {showTimeSec: 0, hideTimeSec: 0} }; var newNotification = { receivedNotification: newCard, showTime: 0, hideTime: 0 }; var notificationGroups = { 'TEST GROUP 1': {cards: [keptCard], cardsTimestamp: 0}, 'TEST GROUP 2': {cards: [newCard], cardsTimestamp: 0} }; var fakeOnCardShownFunction = 'FAKE ON CARD SHOWN FUNCTION'; var expectedUpdatedNotifications = { 'SHOULD BE KEPT': 'KEPT CARD NOTIFICATION DATA', 'NEW CARD': 'NEW CARD NOTIFICATION DATA' }; this.makeAndRegisterMockApis([ 'cardSet.update', 'chrome.storage.local.set', 'instrumented.notifications.getAll' ]); this.makeMockLocalFunctions([ 'onSuccess' ]); var notificationsGetAllSavedArgs = new SaveMockArguments(); this.mockApis.expects(once()). instrumented_notifications_getAll( notificationsGetAllSavedArgs.match(ANYTHING)). will(invokeCallback( notificationsGetAllSavedArgs, 0, existingNotifications)); this.mockApis.expects(once()). cardSet_update( 'SHOULD BE KEPT', eqJSON([keptNotification]), eqJSON(notificationGroups), fakeOnCardShownFunction). will(returnValue('KEPT CARD NOTIFICATION DATA')); this.mockApis.expects(once()). cardSet_update( 'NEW CARD', eqJSON([newNotification]), eqJSON(notificationGroups), fakeOnCardShownFunction). will(returnValue('NEW CARD NOTIFICATION DATA')); this.mockApis.expects(once()). cardSet_update( 'SHOULD BE DELETED', [], eqJSON(notificationGroups), fakeOnCardShownFunction). will(returnValue(undefined)); this.mockApis.expects(once()). chrome_storage_local_set( eqJSON({notificationsData: expectedUpdatedNotifications})); this.mockLocalFunctions.expects(once()). onSuccess(undefined); // Invoking the tested function. showNotificationGroups(notificationGroups, fakeOnCardShownFunction) .then(this.mockLocalFunctions.functions().onSuccess); }); TEST_F(TEST_NAME, 'ProcessServerResponse', function() { // Tests processServerResponse function. // Setup and expectations. Date.now = function() { return 3000000; }; // GROUP1 was requested and contains cards c4 and c5. For c5, there is a // non-expired dismissal, so it will be ignored. // GROUP2 was not requested, but is contained in server response to // indicate that the group still exists. Stored group GROUP2 won't change. // GROUP3 is stored, but is not present in server's response, which means // it doesn't exist anymore. This group will be deleted. // GROUP4 doesn't contain cards, but it was requested. This is treated as // if it had an empty array of cards. Cards in the stored group will be // replaced with an empty array. // GROUP5 doesn't have next poll time, and it will be stored without next // poll time. var serverResponse = { groups: { GROUP1: {requested: true, nextPollSeconds: 46}, GROUP2: {requested: false}, GROUP4: {requested: true, nextPollSeconds: 45}, GROUP5: {requested: true} }, notifications: [ {notificationId: 'c4', groupName: 'GROUP1'}, {notificationId: 'c5', groupName: 'GROUP1'} ] }; var recentDismissals = { c4: 1800000, // expired dismissal c5: 1800001 // non-expired dismissal }; var storedGroups = { GROUP2: { cards: [{notificationId: 'c2'}], cardsTimestamp: 239, nextPollTime: 10000 }, GROUP3: { cards: [{notificationId: 'c3'}], cardsTimestamp: 240, nextPollTime: 10001 }, GROUP4: { cards: [{notificationId: 'c6'}], cardsTimestamp: 241, nextPollTime: 10002 } }; var expectedUpdatedGroups = { GROUP1: { cards: [{notificationId: 'c4', groupName: 'GROUP1'}], cardsTimestamp: 3000000, nextPollTime: 3046000 }, GROUP2: { cards: [{notificationId: 'c2'}], cardsTimestamp: 239, nextPollTime: 10000 }, GROUP4: { cards: [], cardsTimestamp: 3000000, nextPollTime: 3045000 }, GROUP5: { cards: [], cardsTimestamp: 3000000 } }; var expectedUpdatedRecentDismissals = { c5: 1800001 }; this.makeAndRegisterMockGlobals([ 'scheduleNextCardsPoll' ]); this.makeAndRegisterMockApis([ 'fillFromChromeLocalStorage', ]); expectChromeLocalStorageGet( this, { notificationGroups: {}, recentDismissals: {} }, { notificationGroups: storedGroups, recentDismissals: recentDismissals }); this.mockGlobals.expects(once()) .scheduleNextCardsPoll(eqJSON(expectedUpdatedGroups)); // Invoking the tested function. processServerResponse(serverResponse); }); TEST_F(TEST_NAME, 'ProcessServerResponseGoogleNowDisabled', function() { // Tests processServerResponse function for the case when the response // indicates that Google Now is disabled. // Setup and expectations. var serverResponse = { googleNowDisabled: true, groups: {} }; this.makeAndRegisterMockGlobals([ 'scheduleNextCardsPoll' ]); this.makeAndRegisterMockApis([ 'chrome.storage.local.set', 'fillFromChromeLocalStorage' ]); this.mockApis.expects(once()). chrome_storage_local_set(eqJSON({googleNowEnabled: false})); this.mockGlobals.expects(never()).scheduleNextCardsPoll(); // Invoking the tested function. processServerResponse(serverResponse); });