UNPKG

quodolores

Version:

Monorepo for the Firebase JavaScript SDK

1,702 lines (1,597 loc) 314 kB
/** * @license * Copyright 2017 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @fileoverview Tests for rpchandler.js. */ goog.provide('fireauth.RpcHandlerTest'); goog.require('fireauth.AuthError'); goog.require('fireauth.AuthErrorWithCredential'); goog.require('fireauth.AuthProvider'); goog.require('fireauth.FacebookAuthProvider'); goog.require('fireauth.GoogleAuthProvider'); goog.require('fireauth.OAuthCredential'); goog.require('fireauth.OAuthProvider'); goog.require('fireauth.RpcHandler'); goog.require('fireauth.authenum.Error'); goog.require('fireauth.common.testHelper'); goog.require('fireauth.constants'); goog.require('fireauth.util'); goog.require('goog.Promise'); goog.require('goog.json'); goog.require('goog.net.CorsXmlHttpFactory'); goog.require('goog.net.EventType'); goog.require('goog.net.FetchXmlHttpFactory'); goog.require('goog.net.XhrIo'); goog.require('goog.net.XhrLike'); goog.require('goog.object'); goog.require('goog.testing.AsyncTestCase'); goog.require('goog.testing.MockClock'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.recordFunction'); goog.setTestOnly('fireauth.RpcHandlerTest'); var ignoreArgument; var gapi = gapi || {}; var stubs = new goog.testing.PropertyReplacer(); var rpcHandler = null; var expectedResponse = { 'resp1': 'val1', 'resp2': 'val2' }; // STS token server response. var expectedStsTokenResponse = { 'access_token': 'accessToken', 'refresh_token': 'refreshToken', 'expires_in': '3600' }; // Token response with expiresIn. var tokenResponseWithExpiresIn = { 'idToken': 'accessToken', 'refreshToken': 'refreshToken', 'expiresIn': '3600' }; // New token response without expiresIn. var tokenResponse = { 'idToken': 'accessToken', 'refreshToken': 'refreshToken' }; var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); var CURRENT_URL = 'http://www.example.com:8080/foo.htm'; var clock; var mockControl; var delay = 30000; var identityPlatformEndpoint = fireauth.constants.Endpoint.PRODUCTION.identityPlatformEndpoint; var now = new Date(); var pendingCredResponse; var pendingCredResponseWithAdditionalInfo; function setUp() { stubs.replace( fireauth.util, 'supportsCors', function() {return true;}); stubs.replace( goog.net.XhrIo.prototype, 'send', goog.testing.recordFunction()); stubs.replace( goog.net.XhrIo.prototype, 'listen', goog.testing.recordFunction()); stubs.replace( goog.net.XhrIo.prototype, 'listenOnce', goog.testing.recordFunction()); stubs.replace( goog.net.XhrIo.prototype, 'setTimeoutInterval', goog.testing.recordFunction()); stubs.replace(fireauth.util, 'getCurrentUrl', function() { return CURRENT_URL; }); rpcHandler = new fireauth.RpcHandler('apiKey'); ignoreArgument = goog.testing.mockmatchers.ignoreArgument; mockControl = new goog.testing.MockControl(); mockControl.$resetAll(); pendingCredResponse = { 'mfaInfo': { 'mfaEnrollmentId': 'ENROLLMENT_UID1', 'enrolledAt': now.toISOString(), 'phoneInfo': '+16505551234' }, 'mfaPendingCredential': 'PENDING_CREDENTIAL' }; pendingCredResponseWithAdditionalInfo = goog.object.clone(pendingCredResponse); goog.object.extend(pendingCredResponseWithAdditionalInfo, { // Credential returned. 'providerId': 'google.com', 'oauthAccessToken': 'googleAccessToken', 'oauthIdToken': 'googleIdToken', 'oauthExpireIn': 3600, // Additional user info data. 'rawUserInfo': '{"kind":"plus#person","displayName":"John Doe",' + '"name":{"givenName":"John","familyName":"Doe"}}' }); } /** * @param {string} url The URL to make a request to. * @param {string} method The HTTP send method. * @param {?ArrayBuffer|?ArrayBufferView|?Blob|?Document|?FormData|string} * data The request content. * @param {?Object} headers The request content headers. * @param {number} timeout The request timeout. * @param {?Object} response The response to return. */ function assertSendXhrAndRunCallback( url, method, data, headers, timeout, response) { stubs.replace( fireauth.RpcHandler.prototype, 'sendXhr_', function(actualUrl, callback, actualMethod, actualData, actualHeaders, actualTimeout) { assertEquals(url, actualUrl); assertEquals(method, actualMethod); assertEquals(data, actualData); assertObjectEquals(headers, actualHeaders); assertEquals(timeout, actualTimeout); callback(response); }); } /** * Asserts that server errors are handled correctly. * @param {function() : !goog.Promise} methodToTest The method that we are * testing, which returns a Promise that we expect to reject with an error. * @param {!Object<string, string>} errorMap A map from server errors to the * errors we expect from the method under test. * @param {string} url The expected URL to which a request is made. * @param {!Object<string, string>} body The expected body of the request. */ function assertServerErrorsAreHandled(methodToTest, errorMap, url, body) { errorMap = goog.object.clone(errorMap); asyncTestCase.waitForSignals(goog.object.getKeys(errorMap).length); var promise = goog.Promise.resolve(); goog.object.forEach(errorMap, function(expectedError, serverErrorCode) { promise = promise.then(function() { assertSendXhrAndRunCallback( url, 'POST', goog.json.serialize(body), fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, delay, { 'error': { 'message': serverErrorCode } }); return methodToTest().thenCatch(function(error) { fireauth.common.testHelper.assertErrorEquals( new fireauth.AuthError(expectedError), error); asyncTestCase.signal(); }); }); }); } function tearDown() { pendingCredResponse = null; pendingCredResponseWithAdditionalInfo = null; stubs.reset(); rpcHandler = null; fireauth.RpcHandler.loadGApi_ = null; goog.dispose(clock); try { mockControl.$verifyAll(); } finally { mockControl.$tearDown(); } delete goog.global['self']; } function testGetApiKey() { assertEquals('apiKey', rpcHandler.getApiKey()); } function testUpdateGetTenantId() { assertNull(rpcHandler.getTenantId()); rpcHandler.updateTenantId('123456789012'); assertEquals('123456789012', rpcHandler.getTenantId()); rpcHandler.updateTenantId(null); assertNull(rpcHandler.getTenantId()); } function testRpcHandler_XMLHttpRequest_notSupported() { stubs.replace( fireauth.RpcHandler, 'getXMLHttpRequest', function() {return undefined;}); var expectedError = new fireauth.AuthError( fireauth.authenum.Error.INTERNAL_ERROR, 'The XMLHttpRequest compatibility library was not found.'); var error = assertThrows(function() { new fireauth.RpcHandler('apiKey'); }); fireauth.common.testHelper.assertErrorEquals(expectedError, error); } function testRpcHandler_XMLHttpRequest_worker() { // Test worker environment that FetchXmlHttpFactory is used in initialization // of goog.net.XhrIo. // Install mock clock. clock = new goog.testing.MockClock(true); // Simulates global self in a worker environment. goog.global['self'] = {}; var xhrInstance = mockControl.createStrictMock(goog.net.XhrLike); var createInstance = mockControl.createMethodMock( goog.net.FetchXmlHttpFactory.prototype, 'createInstance'); stubs.reset(); // Simulate worker environment. stubs.replace( fireauth.util, 'isWorker', function() {return true;}); // Simulate fetch, Request and Headers API supported. stubs.replace( fireauth.util, 'isFetchSupported', function() {return true;}); // No XMLHttpRequest available. stubs.replace( fireauth.RpcHandler, 'getXMLHttpRequest', function() {return undefined;}); // Confirm RPC handler calls XHR instance from FetchXmlHttpFactory XHR. createInstance().$returns(xhrInstance); xhrInstance.open(ignoreArgument, ignoreArgument, ignoreArgument).$once(); xhrInstance.setRequestHeader(ignoreArgument, ignoreArgument).$once(); xhrInstance.send(ignoreArgument).$once(); xhrInstance.abort().$once(); asyncTestCase.waitForSignals(1); mockControl.$replayAll(); rpcHandler = new fireauth.RpcHandler('apiKey'); // Simulate RPC and then timeout. rpcHandler.fetchProvidersForIdentifier('user@example.com') .thenCatch(function(error) { asyncTestCase.signal(); }); // Timeout XHR request. clock.tick(delay * 2); } function testRpcHandler_XMLHttpRequest_worker_fetchNotSupported() { // Test worker environment where fetch, Headers and Request are not supported. // Simulates global self in a worker environment. goog.global['self'] = {}; var expectedError = new fireauth.AuthError( fireauth.authenum.Error.OPERATION_NOT_SUPPORTED, 'fetch, Headers and Request native APIs or equivalent Polyfills ' + 'must be available to support HTTP requests from a Worker environment.'); stubs.reset(); // Simulate worker environment. stubs.replace( fireauth.util, 'isWorker', function() {return true;}); // Simulate fetch, Request and Headers API not supported. stubs.replace( fireauth.util, 'isFetchSupported', function() {return false;}); // No XMLHttpRequest available. stubs.replace( fireauth.RpcHandler, 'getXMLHttpRequest', function() {return undefined;}); asyncTestCase.waitForSignals(1); rpcHandler = new fireauth.RpcHandler('apiKey'); // Simulate RPC and then expected error thrown. rpcHandler.fetchProvidersForIdentifier('user@example.com') .thenCatch(function(actualError) { fireauth.common.testHelper.assertErrorEquals( expectedError, actualError); asyncTestCase.signal(); }); } function testRpcHandler_XMLHttpRequest_corsBrowser() { // Test CORS browser environment that CorsXmlHttpFactory is used in // initialization of goog.net.XhrIo. // Install mock clock. clock = new goog.testing.MockClock(true); var xhrInstance = mockControl.createStrictMock(goog.net.XhrLike); var createInstance = mockControl.createMethodMock( goog.net.CorsXmlHttpFactory.prototype, 'createInstance'); stubs.reset(); // Non-worker environment. stubs.replace( fireauth.util, 'isWorker', function() {return false;}); // CORS supporting browser. stubs.replace( fireauth.util, 'supportsCors', function() {return true;}); // Non-native environment. stubs.replace( fireauth.util, 'isNativeEnvironment', function() {return false;}); // Confirm RPC handler calls XHR instance from CorsXmlHttpFactory XHR. createInstance().$returns(xhrInstance); xhrInstance.open(ignoreArgument, ignoreArgument, ignoreArgument).$once(); xhrInstance.setRequestHeader(ignoreArgument, ignoreArgument).$once(); xhrInstance.send(ignoreArgument).$once(); xhrInstance.abort().$once(); asyncTestCase.waitForSignals(1); mockControl.$replayAll(); rpcHandler = new fireauth.RpcHandler('apiKey'); // Simulate RPC and then timeout. rpcHandler.fetchProvidersForIdentifier('user@example.com') .thenCatch(function(error) { asyncTestCase.signal(); }); // Timeout XHR request. clock.tick(delay * 2); } function testRpcHandler_XMLHttpRequest_reactNative() { // Test react-native environment that built-in XMLHttpRequest is used in // xhrFactory. // Install mock clock. clock = new goog.testing.MockClock(true); var xhrInstance = mockControl.createStrictMock(goog.net.XhrLike); var xhrConstructor = mockControl.createConstructorMock( goog.net, 'XhrLike'); stubs.reset(); // CORS supporting environment. stubs.replace( fireauth.util, 'supportsCors', function() {return true;}); // Return native XMLHttpRequest.. stubs.replace( fireauth.RpcHandler, 'getXMLHttpRequest', function() {return xhrConstructor;}); // React-native environment. stubs.replace( fireauth.util, 'isNativeEnvironment', function() {return true;}); stubs.replace( fireauth.util, 'getEnvironment', function() {return fireauth.util.Env.REACT_NATIVE;}); // Confirm RPC handler calls XHR instance from factory XHR. xhrConstructor().$returns(xhrInstance); xhrInstance.open(ignoreArgument, ignoreArgument, ignoreArgument).$once(); xhrInstance.setRequestHeader(ignoreArgument, ignoreArgument).$once(); xhrInstance.send(ignoreArgument).$once(); xhrInstance.abort().$once(); asyncTestCase.waitForSignals(1); mockControl.$replayAll(); rpcHandler = new fireauth.RpcHandler('apiKey'); // Simulate RPC and then timeout. rpcHandler.fetchProvidersForIdentifier('user@example.com') .thenCatch(function(error) { asyncTestCase.signal(); }); // Timeout XHR request. clock.tick(delay * 2); } function testRpcHandler_XMLHttpRequest_node() { // Test node environment that Node.js implementation is used in xhrfactory. // Install mock clock. clock = new goog.testing.MockClock(true); var xhrInstance = mockControl.createStrictMock(goog.net.XhrLike); var xhrConstructor = mockControl.createConstructorMock( goog.net, 'XhrLike'); stubs.reset(); stubs.replace( fireauth.util, 'supportsCors', function() {return true;}); // Return mock XHR constructor. In a Node.js environment the polyfill library // would be used. stubs.replace( fireauth.RpcHandler, 'getXMLHttpRequest', function() {return xhrConstructor;}); // Node.js environment. stubs.replace( fireauth.util, 'getEnvironment', function() {return fireauth.util.Env.NODE;}); // Confirm RPC handler calls XHR instance from factory XHR. xhrConstructor().$returns(xhrInstance); xhrInstance.open(ignoreArgument, ignoreArgument, ignoreArgument).$once(); xhrInstance.setRequestHeader(ignoreArgument, ignoreArgument).$once(); xhrInstance.send(ignoreArgument).$once(); xhrInstance.abort().$once(); asyncTestCase.waitForSignals(1); mockControl.$replayAll(); rpcHandler = new fireauth.RpcHandler('apiKey'); // Simulate RPC and then timeout. rpcHandler.fetchProvidersForIdentifier('user@example.com') .thenCatch(function(error) { asyncTestCase.signal(); }); // Timeout XHR request. clock.tick(delay * 2); } /** * Asserts and applies the goog.net.XhrIo send call. * @param {string} url The expected XHR URL. * @param {string} method The XHR expected HTTP method. * @param {?ArrayBuffer|?ArrayBufferView|?Blob|?Document|?FormData|string=} data * The expected request data. * @param {?Object|undefined} headers The expected HTTP headers. * @param {number} timeout The expected timeout. * @param {?Object|undefined} resp The expected response to return. */ function assertXhrIoAndRunCallback(url, method, data, headers, timeout, resp) { // Confirm correct parameters passed to goog.net.XhrIo.send. assertEquals( 1, goog.net.XhrIo.prototype.send.getCallCount()); assertEquals( url, goog.net.XhrIo.prototype.send.getLastCall().getArgument(0)); assertEquals( method, goog.net.XhrIo.prototype.send.getLastCall().getArgument(1)); assertEquals( data, goog.net.XhrIo.prototype.send.getLastCall().getArgument(2)); assertObjectEquals( headers, goog.net.XhrIo.prototype.send.getLastCall().getArgument(3)); assertEquals( 1, goog.net.XhrIo.prototype.setTimeoutInterval.getCallCount()); assertEquals( timeout, goog.net.XhrIo.prototype.setTimeoutInterval.getLastCall().getArgument(0)); // Get on complete callback. var callback = goog.net.XhrIo.prototype.listen.getLastCall().getArgument(1); // Returned expected response. var self = { // Return the response text. getResponseText: function() { return goog.json.serialize(resp); } }; // Run on complete callback, pass self as this to return expected response. callback.apply(self); } function testSendXhr_post() { rpcHandler = new fireauth.RpcHandler('apiKey'); // Check response is passed to provided callback. var responseRecorded = null; var func = function(response) { responseRecorded = response; }; var data = 'key1=value1&key2=value2'; var headers = { 'Content-Type': 'application/json' }; // Send XHR with test parameters. rpcHandler.sendXhr_( 'url1', func, 'POST', data, headers, 5000); // Confirm correct parameters passed and run on complete. assertXhrIoAndRunCallback( 'url1', 'POST', data, headers, 5000, expectedResponse); // Confirm callback called with expected response. assertObjectEquals(expectedResponse, responseRecorded); } /** * Tests client version being correctly sent with requests to Firebase Auth * server. */ function testSendFirebaseBackendRequest_clientVersion() { var clientVersion = 'Chrome/JsCore/3.0.0'; // Simulate clock. clock = new goog.testing.MockClock(); clock.install(); clock.tick(50); // Pass client version in constructor. var rpcHandler = new fireauth.RpcHandler( 'apiKey', null, clientVersion); var expectedDomains = [ 'domain.com', 'www.mydomain.com' ]; var serverResponse = { 'authorizedDomains': [ 'domain.com', 'www.mydomain.com' ] }; // The client version should be passed to header. var expectedHeaders = { 'Content-Type': 'application/json', 'X-Client-Version': clientVersion }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + 'getProjectConfig?key=apiKey&cb=50', 'GET', undefined, expectedHeaders, delay, serverResponse); rpcHandler.getAuthorizedDomains().then(function(domains) { assertArrayEquals(expectedDomains, domains); asyncTestCase.signal(); }); } function testSendFirebaseBackendRequest_timeout() { // Test network timeout error for Firebase backend request. var actualError; // Allow xhrIo requests. stubs.reset(); // Simulate CORS support. stubs.replace( fireauth.util, 'supportsCors', function() {return true;}); // Expected timeout error. var timeoutError = new fireauth.AuthError( fireauth.authenum.Error.NETWORK_REQUEST_FAILED); // Install mock clock. clock = new goog.testing.MockClock(true); rpcHandler = new fireauth.RpcHandler('apiKey'); // Send request for backend API. rpcHandler.fetchProvidersForIdentifier('user@example.com') .thenCatch(function(error) { // Record error. actualError = error; }); // Timeout XHR request. clock.tick(delay * 2); // Timeout error should have been returned. fireauth.common.testHelper.assertErrorEquals(timeoutError, actualError); } function testSendFirebaseBackendRequest_offline_falseAlert() { // Install mock clock. clock = new goog.testing.MockClock(true); var expectedResponse = [ 'google.com', 'myauthprovider.com' ]; var serverResponse = { 'kind': 'identitytoolkit#CreateAuthUriResponse', 'authUri': 'https://accounts.google.com/o/oauth2/auth?foo=bar', 'providerId': 'google.com', 'allProviders': [ 'google.com', 'myauthprovider.com' ], 'registered': true, 'forExistingProvider': true, 'sessionId': 'MY_SESSION_ID' }; var identifier = 'MY_ID'; stubs.reset(); // Simulate browser supports CORS. stubs.replace( fireauth.util, 'supportsCors', function() {return true;}); // Simulate expected URL returned for current URL. stubs.replace( fireauth.util, 'getCurrentUrl', function() { return CURRENT_URL; }); // Simulate false alert navigator.onLine. stubs.replace( fireauth.util, 'isOnline', function() {return false;}); // Overwrite XHR IO send to simulate a 4999ms delay before the response. stubs.replace( goog.net.XhrIo.prototype, 'send', function(url, httpMethod, data, headers) { assertEquals( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + 'createAuthUri?key=apiKey', url); assertEquals('POST', httpMethod); assertEquals(goog.json.serialize(request), data); assertObjectEquals( fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, headers); clock.tick(4999); this.dispatchEvent(goog.net.EventType.COMPLETE); }); // Simulate expected response returned. stubs.replace( goog.net.XhrIo.prototype, 'getResponseText', function() { return JSON.stringify(serverResponse); }); asyncTestCase.waitForSignals(1); var request = { 'identifier': identifier, 'continueUri': CURRENT_URL }; rpcHandler.fetchProvidersForIdentifier(identifier) .then(function(response) { assertArrayEquals(expectedResponse, response); asyncTestCase.signal(); }); } function testSendFirebaseBackendRequest_offline_slowResponse() { // Install mock clock. clock = new goog.testing.MockClock(true); var serverResponse = { 'kind': 'identitytoolkit#CreateAuthUriResponse', 'authUri': 'https://accounts.google.com/o/oauth2/auth?foo=bar', 'providerId': 'google.com', 'allProviders': [ 'google.com', 'myauthprovider.com' ], 'registered': true, 'forExistingProvider': true, 'sessionId': 'MY_SESSION_ID' }; var identifier = 'MY_ID'; stubs.reset(); // Simulate browser supports CORS. stubs.replace( fireauth.util, 'supportsCors', function() {return true;}); // Simulate expected URL returned for current URL. stubs.replace( fireauth.util, 'getCurrentUrl', function() { return CURRENT_URL; }); // Simulate false alert navigator.onLine. stubs.replace( fireauth.util, 'isOnline', function() {return false;}); // Overwrite XHR IO send to simulate a 5000mx delay before the response. stubs.replace( goog.net.XhrIo.prototype, 'send', function(url, httpMethod, data, headers) { assertEquals( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/' + 'createAuthUri?key=apiKey', url); assertEquals('POST', httpMethod); assertEquals(goog.json.serialize(request), data); assertObjectEquals( fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, headers); clock.tick(5000); this.dispatchEvent(goog.net.EventType.COMPLETE); }); // Simulate expected response returned. stubs.replace( goog.net.XhrIo.prototype, 'getResponseText', function() { return JSON.stringify(serverResponse); }); asyncTestCase.waitForSignals(1); var request = { 'identifier': identifier, 'continueUri': CURRENT_URL }; // Expected timeout error even though the request was eventually returned. var timeoutError = new fireauth.AuthError( fireauth.authenum.Error.NETWORK_REQUEST_FAILED); rpcHandler.fetchProvidersForIdentifier(identifier) .thenCatch(function(actualError) { // Timeout error should have been returned. fireauth.common.testHelper.assertErrorEquals(timeoutError, actualError); asyncTestCase.signal(); }); } function testSendFirebaseBackendRequest_offline() { // Test network timeout error for offline Firebase backend request. asyncTestCase.waitForSignals(1); // Allow xhrIo requests. stubs.reset(); // Simulate app offline. stubs.replace( fireauth.util, 'isOnline', function() {return false;}); // Install mock clock. clock = new goog.testing.MockClock(true); // Expected timeout error. var timeoutError = new fireauth.AuthError( fireauth.authenum.Error.NETWORK_REQUEST_FAILED); rpcHandler = new fireauth.RpcHandler('apiKey'); // Send request for backend API. rpcHandler.fetchProvidersForIdentifier('user@example.com') .thenCatch(function(error) { // Timeout error event without any wait (no tick in mockclock). fireauth.common.testHelper.assertErrorEquals(timeoutError, error); asyncTestCase.signal(); }); // Simulate short timeout when navigator.onLine is false. clock.tick(5000); } function testSendStsTokenBackendRequest_timeout() { // Test network timeout error for STS token backend request. var actualError; // Allow xhrIo requests. stubs.reset(); // Simulate CORS support. stubs.replace( fireauth.util, 'supportsCors', function() {return true;}); // Expected timeout error. var timeoutError = new fireauth.AuthError( fireauth.authenum.Error.NETWORK_REQUEST_FAILED); // Install mock clock. clock = new goog.testing.MockClock(true); rpcHandler = new fireauth.RpcHandler('apiKey'); // Send request for backend API. rpcHandler.requestStsToken({ 'grant_type': 'authorization_code', 'code': 'idToken' }).thenCatch(function(error) { // Record error. actualError = error; }); // Timeout XHR request. clock.tick(delay * 2); // Timeout error should have been returned. fireauth.common.testHelper.assertErrorEquals(timeoutError, actualError); } function testSendStsTokenBackendRequest_offline() { // Test network timeout error for offline STS token backend request. asyncTestCase.waitForSignals(1); // Allow xhrIo requests. stubs.reset(); // Simulate app offline. stubs.replace( fireauth.util, 'isOnline', function() {return false;}); // Install mock clock. clock = new goog.testing.MockClock(true); // Expected timeout error. var timeoutError = new fireauth.AuthError( fireauth.authenum.Error.NETWORK_REQUEST_FAILED); rpcHandler = new fireauth.RpcHandler('apiKey'); // Send request for backend API. rpcHandler.requestStsToken({ 'grant_type': 'authorization_code', 'code': 'idToken' }).thenCatch(function(error) { // Timeout error event without any wait (no tick in mockclock). fireauth.common.testHelper.assertErrorEquals(timeoutError, error); asyncTestCase.signal(); }); // Simulate short timeout when navigator.onLine is false. clock.tick(5000); } function testSendXhr_corsUnsupported() { var expectedResponse = { 'key1': 'value1', 'key2': 'value2' }; var recordedToken = 'token'; gapi.auth = gapi.auth || {}; gapi.client = gapi.client || {}; stubs.reset(); // Simulate GApi loaded. stubs.set( gapi.auth, 'getToken', function() { return recordedToken; }); stubs.set( gapi.auth, 'setToken', function(token) { recordedToken = token; }); stubs.set( gapi.client, 'request', function(request) { assertEquals('none', request['authType']); assertEquals('url1', request['path']); assertEquals('GET', request['method']); assertEquals(data, request['body']); assertObjectEquals(headers, request['headers']); request['callback'](expectedResponse); asyncTestCase.signal(); }); stubs.set( gapi.client, 'setApiKey', function(apiKey) { assertEquals('apiKey', apiKey); asyncTestCase.signal(); }); // Simulate browser that does not support CORS. stubs.replace( fireauth.util, 'supportsCors', function() {return false;}); var func = function(response) { assertObjectEquals(expectedResponse, response); // Verify token updated. assertEquals('token', gapi.auth.getToken()); asyncTestCase.signal(); }; var data = 'key1=value1&key2=value2'; var headers = { 'Content-Type': 'application/json' }; asyncTestCase.waitForSignals(3); // Simulate GApi dependencies loaded. fireauth.RpcHandler.loadGApi_ = goog.Promise.resolve(); rpcHandler.sendXhr_( 'url1', func, 'GET', data, headers, 5000); } function testSendXhr_corsUnsupported_error() { var expectedResponse = { 'error': { 'message': fireauth.RpcHandler.ServerError.CORS_UNSUPPORTED } }; // Simulate browser that does not support CORS. stubs.replace( fireauth.util, 'supportsCors', function() {return false;}); var func = function(response) { assertObjectEquals(expectedResponse, response); asyncTestCase.signal(); }; var data = 'key1=value1&key2=value2'; var headers = { 'Content-Type': 'application/json' }; asyncTestCase.waitForSignals(1); fireauth.RpcHandler.loadGApi_ = goog.Promise.reject(); rpcHandler.sendXhr_( 'url1', func, 'GET', data, headers, 5000); } function testSendSecureTokenBackendRequest_clientVersion() { var clientVersion = 'Chrome/JsCore/3.0.0'; // The client version should be passed to header. var expectedHeaders = { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Client-Version': clientVersion }; // Pass client version in constructor. var rpcHandler = new fireauth.RpcHandler( 'apiKey', null, clientVersion); asyncTestCase.waitForSignals(1); // Confirm correct parameters passed and run on complete. assertSendXhrAndRunCallback( 'https://securetoken.googleapis.com/v1/token?key=apiKey', 'POST', 'grant_type=authorization_code&code=idToken', expectedHeaders, delay, expectedStsTokenResponse); // Send STS token request, default config will be used. rpcHandler.requestStsToken( { 'grant_type': 'authorization_code', 'code': 'idToken' }).then(function(response) { assertObjectEquals( expectedStsTokenResponse, response); asyncTestCase.signal(); }); } function testRequestStsToken_updateClientVersion() { asyncTestCase.waitForSignals(1); // Confirm correct parameters passed and run on complete. assertSendXhrAndRunCallback( 'https://securetoken.googleapis.com/v1/token?key=apiKey', 'POST', 'grant_type=authorization_code&code=idToken', { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Client-Version': 'Chrome/JsCore/3.0.0/FirebaseCore-web' }, delay, expectedStsTokenResponse); // Update client version. rpcHandler.updateClientVersion('Chrome/JsCore/3.0.0/FirebaseCore-web'); // Send STS token request. rpcHandler.requestStsToken( { 'grant_type': 'authorization_code', 'code': 'idToken' }).then(function(response) { assertObjectEquals( expectedStsTokenResponse, response); asyncTestCase.signal(); }); } function testRequestStsToken_removeClientVersion() { asyncTestCase.waitForSignals(1); // Confirm correct parameters passed and run on complete. assertSendXhrAndRunCallback( 'https://securetoken.googleapis.com/v1/token?key=apiKey', 'POST', 'grant_type=authorization_code&code=idToken', { 'Content-Type': 'application/x-www-form-urlencoded' }, delay, expectedStsTokenResponse); // Remove client version. rpcHandler.updateClientVersion(null); // Send STS token request. rpcHandler.requestStsToken( { 'grant_type': 'authorization_code', 'code': 'idToken' }).then(function(response) { assertObjectEquals( expectedStsTokenResponse, response); asyncTestCase.signal(); }); } function testRequestStsToken_default() { asyncTestCase.waitForSignals(1); // Confirm correct parameters passed and run on complete. assertSendXhrAndRunCallback( 'https://securetoken.googleapis.com/v1/token?key=apiKey', 'POST', 'grant_type=authorization_code&code=idToken', fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_, delay, expectedStsTokenResponse); // Send STS token request, default config will be used. rpcHandler.requestStsToken( { 'grant_type': 'authorization_code', 'code': 'idToken' }).then(function(response) { assertObjectEquals( expectedStsTokenResponse, response); asyncTestCase.signal(); }); } function testRequestStsToken_custom() { asyncTestCase.waitForSignals(1); // Reinitialize RPC handler using custom config. rpcHandler = new fireauth.RpcHandler( 'apiKey', { 'secureTokenEndpoint': 'http://localhost/token', 'secureTokenTimeout': new fireauth.util.Delay(5000, 5000), 'secureTokenHeaders': {'Content-Type': 'application/json'} }); // Confirm correct parameters passed and run on complete. assertSendXhrAndRunCallback( 'http://localhost/token?key=apiKey', 'POST', 'grant_type=authorization_code&code=idToken', { 'Content-Type': 'application/json' }, 5000, expectedStsTokenResponse); // Send STS token request, custom config will be used. rpcHandler.requestStsToken( { 'grant_type': 'authorization_code', 'code': 'idToken' }).then(function(response) { assertObjectEquals( expectedStsTokenResponse, response); asyncTestCase.signal(); }); } function testRequestStsToken_invalidRequest() { asyncTestCase.waitForSignals(1); // Reinitialize RPC handler. rpcHandler = new fireauth.RpcHandler( 'apiKey'); // Send STS token request, no XHR, invalid request. rpcHandler.requestStsToken( { 'invalid': 'authorization_code', 'code': 'idToken' }).then( function(response) {}, function(error) { fireauth.common.testHelper.assertErrorEquals( new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR), error); asyncTestCase.signal(); }); } function testRequestStsToken_unknownServerResponse() { var serverResponse = {'error': 'INTERNAL_SERVER_ERROR'}; asyncTestCase.waitForSignals(1); // Confirm correct parameters passed and run on complete. assertSendXhrAndRunCallback( 'https://securetoken.googleapis.com/v1/token?key=apiKey', 'POST', 'grant_type=authorization_code&code=idToken', fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_, delay, serverResponse); // Send STS token request, default config will be used. rpcHandler.requestStsToken( { 'grant_type': 'authorization_code', 'code': 'idToken' }).then( function(response) {}, function(error) { fireauth.common.testHelper.assertErrorEquals( new fireauth.AuthError(fireauth.authenum.Error.INTERNAL_ERROR, goog.json.serialize(serverResponse)), error); asyncTestCase.signal(); }); } function testRequestStsToken_specificErrorResponse() { // Server error response when token is expired. var serverResponse = { "error": { "code": 400, "message": "TOKEN_EXPIRED", "status": "INVALID_ARGUMENT" } }; asyncTestCase.waitForSignals(1); // Confirm correct parameters passed and run on complete. assertSendXhrAndRunCallback( 'https://securetoken.googleapis.com/v1/token?key=apiKey', 'POST', 'grant_type=authorization_code&code=idToken', fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_, delay, serverResponse); // Send STS token request, default config will be used. rpcHandler.requestStsToken( { 'grant_type': 'authorization_code', 'code': 'idToken' }).then( function(response) {}, function(error) { fireauth.common.testHelper.assertErrorEquals( new fireauth.AuthError(fireauth.authenum.Error.TOKEN_EXPIRED), error); asyncTestCase.signal(); }); } function testRequestStsToken_emulator() { asyncTestCase.waitForSignals(1); // Confirm correct parameters passed and run on complete. assertSendXhrAndRunCallback( 'http://emulator.test.domain:1234/securetoken.googleapis.com/' + 'v1/token?key=apiKey', 'POST', 'grant_type=authorization_code&code=idToken', fireauth.RpcHandler.DEFAULT_SECURE_TOKEN_HEADERS_, delay, expectedStsTokenResponse); // Set an emulator config. rpcHandler.updateEmulatorConfig( { url: 'http://emulator.test.domain:1234' }); // Send STS token request, emulator config will be used. rpcHandler .requestStsToken({ 'grant_type': 'authorization_code', 'code': 'idToken' }) .then(function (response) { assertObjectEquals(expectedStsTokenResponse, response); asyncTestCase.signal(); }); } function testRequestFirebaseEndpoint_success() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + '=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, delay, expectedResponse); rpcHandler.requestFirebaseEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestFirebaseEndpoint_emulator() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'http://emulator.test.domain:1234/www.googleapis.com/identitytoolkit' + '/v3/relyingparty/method1?key=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, delay, expectedResponse); // Set an emulator config. rpcHandler.updateEmulatorConfig( { url: 'http://emulator.test.domain:1234' }); rpcHandler .requestFirebaseEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }) .then(function (response) { assertObjectEquals(expectedResponse, response); asyncTestCase.signal(); }); } function testRequestIdentityPlatformEndpoint_success() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( identityPlatformEndpoint + 'method1?key=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, delay, expectedResponse); rpcHandler.requestIdentityPlatformEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestIdentityPlatformEndpoint_emulator() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'http://emulator.test.domain:1234/identitytoolkit.googleapis.com' + '/v2/method1?key=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, delay, expectedResponse); // Set an emulator config. rpcHandler.updateEmulatorConfig( { url: 'http://emulator.test.domain:1234' }); rpcHandler .requestIdentityPlatformEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }) .then(function (response) { assertObjectEquals(expectedResponse, response); asyncTestCase.signal(); }); } function testRequestFirebaseEndpoint_updateClientVersion() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + '=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json', 'X-Client-Version': 'Chrome/JsCore/3.0.0/FirebaseCore-web' }, delay, expectedResponse); // Update client version. rpcHandler.updateClientVersion('Chrome/JsCore/3.0.0/FirebaseCore-web'); rpcHandler.requestFirebaseEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestIdentityPlatformEndpoint_updateClientVersion() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( identityPlatformEndpoint + 'method1?key=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json', 'X-Client-Version': 'Chrome/JsCore/3.0.0/FirebaseCore-web' }, delay, expectedResponse); // Update client version. rpcHandler.updateClientVersion('Chrome/JsCore/3.0.0/FirebaseCore-web'); rpcHandler.requestIdentityPlatformEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestFirebaseEndpoint_removeClientVersion() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + '=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json' }, delay, expectedResponse); // Remove client version. rpcHandler.updateClientVersion(null); rpcHandler.requestFirebaseEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestIdentityPlatformEndpoint_removeClientVersion() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( identityPlatformEndpoint + 'method1?key=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json' }, delay, expectedResponse); // Remove client version. rpcHandler.updateClientVersion(null); rpcHandler.requestIdentityPlatformEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestFirebaseEndpoint_setCustomLocaleHeader_success() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + '=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json', 'X-Firebase-Locale': 'fr' }, delay, expectedResponse); // Set French as custom Firebase locale header. rpcHandler.updateCustomLocaleHeader('fr'); rpcHandler.requestFirebaseEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestIdentityPlatformEndpoint_setCustomLocaleHeader_success() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( identityPlatformEndpoint + 'method1?key=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json', 'X-Firebase-Locale': 'fr' }, delay, expectedResponse); // Set French as custom Firebase locale header. rpcHandler.updateCustomLocaleHeader('fr'); rpcHandler.requestIdentityPlatformEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestFirebaseEndpoint_updateCustomLocaleHeader_success() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + '=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json', 'X-Firebase-Locale': 'de' }, delay, expectedResponse); // Set French as custom Firebase locale header. rpcHandler.updateCustomLocaleHeader('fr'); // Change to German. rpcHandler.updateCustomLocaleHeader('de'); rpcHandler.requestFirebaseEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestIdPlatformEndpoint_updateCustomLocaleHeader_success() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( identityPlatformEndpoint + 'method1?key=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json', 'X-Firebase-Locale': 'de' }, delay, expectedResponse); // Set French as custom Firebase locale header. rpcHandler.updateCustomLocaleHeader('fr'); // Change to German. rpcHandler.updateCustomLocaleHeader('de'); rpcHandler.requestIdentityPlatformEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestFirebaseEndpoint_removeCustomLocaleHeader_success() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + '=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json' }, delay, expectedResponse); // Set French as custom Firebase locale header. rpcHandler.updateCustomLocaleHeader('fr'); // Remove custom locale header. rpcHandler.updateCustomLocaleHeader(null); rpcHandler.requestFirebaseEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestIdPlatformEndpoint_removeCustomLocaleHeader_success() { var expectedResponse = { 'status': 'success' }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( identityPlatformEndpoint + 'method1?key=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), { 'Content-Type': 'application/json' }, delay, expectedResponse); // Set French as custom Firebase locale header. rpcHandler.updateCustomLocaleHeader('fr'); // Remove custom locale header. rpcHandler.updateCustomLocaleHeader(null); rpcHandler.requestIdentityPlatformEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(function(response) { assertObjectEquals( expectedResponse, response); asyncTestCase.signal(); }); } function testRequestFirebaseEndpoint_error() { // Error case. var errorResponse = { 'error': { 'message': 'ERROR_CODE' } }; asyncTestCase.waitForSignals(1); assertSendXhrAndRunCallback( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/method1?key' + '=apiKey', 'POST', goog.json.serialize({ 'key1': 'value1', 'key2': 'value2' }), fireauth.RpcHandler.DEFAULT_FIREBASE_HEADERS_, delay, errorResponse); rpcHandler.requestFirebaseEndpoint( 'method1', 'POST', { 'key1': 'value1', 'key2': 'value2' }).then(