All Downloads are FREE. Search and download functionalities are using the official Maven repository.

goog.labs.net.webchannel.webchannelbase_test.js Maven / Gradle / Ivy

Go to download

The Google Closure Library is a collection of JavaScript code designed for use with the Google Closure JavaScript Compiler. This non-official distribution was prepared by the ClojureScript team at http://clojure.org/

There is a newer version: 0.0-20230227-c7c0a541
Show newest version
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// 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 Unit tests for goog.labs.net.webChannel.WebChannelBase.
 * @suppress {accessControls} Private methods are accessed for test purposes.
 *
 */


goog.provide('goog.labs.net.webChannel.webChannelBaseTest');

goog.require('goog.Timer');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.functions');
goog.require('goog.json');
goog.require('goog.labs.net.webChannel.ChannelRequest');
goog.require('goog.labs.net.webChannel.ForwardChannelRequestPool');
goog.require('goog.labs.net.webChannel.WebChannelBase');
goog.require('goog.labs.net.webChannel.WebChannelBaseTransport');
goog.require('goog.labs.net.webChannel.WebChannelDebug');
goog.require('goog.labs.net.webChannel.Wire');
goog.require('goog.labs.net.webChannel.netUtils');
goog.require('goog.labs.net.webChannel.requestStats');
goog.require('goog.labs.net.webChannel.requestStats.Stat');
goog.require('goog.structs.Map');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.asserts');
goog.require('goog.testing.jsunit');

goog.setTestOnly('goog.labs.net.webChannel.webChannelBaseTest');


/**
 * Delay between a network failure and the next network request.
 */
var RETRY_TIME = 1000;


/**
 * A really long time - used to make sure no more timeouts will fire.
 */
var ALL_DAY_MS = 1000 * 60 * 60 * 24;

var stubs = new goog.testing.PropertyReplacer();

var channel;
var deliveredMaps;
var handler;
var mockClock;
var gotError;
var numStatEvents;
var lastStatEvent;
var numTimingEvents;
var lastPostSize;
var lastPostRtt;
var lastPostRetryCount;

// Set to true to see the channel debug output in the browser window.
var debug = false;
// Debug message to print out when debug is true.
var debugMessage = '';

function debugToWindow(message) {
  if (debug) {
    debugMessage += message + '
'; goog.dom.getElement('debug').innerHTML = debugMessage; } } /** * Stubs goog.labs.net.webChannel.netUtils to always time out. It maintains the * contract given by goog.labs.net.webChannel.netUtils.testNetwork, but always * times out (calling callback(false)). * * stubNetUtils should be called in tests that require it before * a call to testNetwork happens. It is reset at tearDown. */ function stubNetUtils() { stubs.set( goog.labs.net.webChannel.netUtils, 'testLoadImage', function(url, timeout, callback) { goog.Timer.callOnce(goog.partial(callback, false), timeout); }); } /** * Stubs goog.labs.net.webChannel.ForwardChannelRequestPool.isSpdyEnabled_ * to manage the max pool size for the forward channel. * * @param {boolean} spdyEnabled Whether SPDY is enabled for the test. */ function stubSpdyCheck(spdyEnabled) { stubs.set( goog.labs.net.webChannel.ForwardChannelRequestPool, 'isSpdyEnabled_', function() { return spdyEnabled; }); } /** * Mock ChannelRequest. * @constructor * @struct * @final */ var MockChannelRequest = function( channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId) { this.channel_ = channel; this.channelDebug_ = channelDebug; this.sessionId_ = opt_sessionId; this.requestId_ = opt_requestId; this.successful_ = true; this.lastError_ = null; this.lastStatusCode_ = 200; // For debugging, keep track of whether this is a back or forward channel. this.isBack = !!(opt_requestId == 'rpc'); this.isForward = !this.isBack; }; MockChannelRequest.prototype.postData_ = null; MockChannelRequest.prototype.requestStartTime_ = null; MockChannelRequest.prototype.setExtraHeaders = function(extraHeaders) {}; MockChannelRequest.prototype.setTimeout = function(timeout) {}; MockChannelRequest.prototype.setReadyStateChangeThrottle = function(throttle) { }; MockChannelRequest.prototype.xmlHttpPost = function( uri, postData, decodeChunks) { this.channelDebug_.debug( '---> POST: ' + uri + ', ' + postData + ', ' + decodeChunks); this.postData_ = postData; this.requestStartTime_ = goog.now(); }; MockChannelRequest.prototype.xmlHttpGet = function( uri, decodeChunks, opt_noClose) { this.channelDebug_.debug( '<--- GET: ' + uri + ', ' + decodeChunks + ', ' + opt_noClose); this.requestStartTime_ = goog.now(); }; MockChannelRequest.prototype.sendCloseRequest = function(uri) { this.requestStartTime_ = goog.now(); }; MockChannelRequest.prototype.cancel = function() { this.successful_ = false; }; MockChannelRequest.prototype.getSuccess = function() { return this.successful_; }; MockChannelRequest.prototype.getLastError = function() { return this.lastError_; }; MockChannelRequest.prototype.getLastStatusCode = function() { return this.lastStatusCode_; }; MockChannelRequest.prototype.getSessionId = function() { return this.sessionId_; }; MockChannelRequest.prototype.getRequestId = function() { return this.requestId_; }; MockChannelRequest.prototype.getPostData = function() { return this.postData_; }; MockChannelRequest.prototype.getRequestStartTime = function() { return this.requestStartTime_; }; MockChannelRequest.prototype.getXhr = function() { return null; }; function shouldRunTests() { return goog.labs.net.webChannel.ChannelRequest.supportsXhrStreaming(); } /** * @suppress {invalidCasts} The cast from MockChannelRequest to * ChannelRequest is invalid and will not compile. */ function setUpPage() { // Use our MockChannelRequests instead of the real ones. goog.labs.net.webChannel.ChannelRequest.createChannelRequest = function( channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId) { return /** @type {!goog.labs.net.webChannel.ChannelRequest} */ ( new MockChannelRequest( channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId)); }; // Mock out the stat notification code. goog.labs.net.webChannel.requestStats.notifyStatEvent = function(stat) { numStatEvents++; lastStatEvent = stat; }; goog.labs.net.webChannel.requestStats.notifyTimingEvent = function( size, rtt, retries) { numTimingEvents++; lastPostSize = size; lastPostRtt = rtt; lastPostRetryCount = retries; }; } function setUp() { numTimingEvents = 0; lastPostSize = null; lastPostRtt = null; lastPostRetryCount = null; mockClock = new goog.testing.MockClock(true); channel = new goog.labs.net.webChannel.WebChannelBase('1'); gotError = false; handler = new goog.labs.net.webChannel.WebChannelBase.Handler(); handler.channelOpened = function() {}; handler.channelError = function(channel, error) { gotError = true; }; handler.channelSuccess = function(channel, maps) { deliveredMaps = goog.array.clone(maps); }; /** * @suppress {checkTypes} The callback function type declaration is skipped. */ handler.channelClosed = function( channel, opt_pendingMaps, opt_undeliveredMaps) { // Mock out the handler, and let it set a formatted user readable string // of the undelivered maps which we can use when verifying our assertions. if (opt_pendingMaps) { handler.pendingMapsString = formatArrayOfMaps(opt_pendingMaps); } if (opt_undeliveredMaps) { handler.undeliveredMapsString = formatArrayOfMaps(opt_undeliveredMaps); } }; handler.channelHandleMultipleArrays = function() {}; handler.channelHandleArray = function() {}; channel.setHandler(handler); // Provide a predictable retry time for testing. channel.getRetryTime_ = function(retryCount) { return RETRY_TIME; }; var channelDebug = new goog.labs.net.webChannel.WebChannelDebug(); channelDebug.debug = function(message) { debugToWindow(message); }; channel.setChannelDebug(channelDebug); numStatEvents = 0; lastStatEvent = null; } function tearDown() { mockClock.dispose(); stubs.reset(); debugToWindow('
'); } function getSingleForwardRequest() { var pool = channel.forwardChannelRequestPool_; if (!pool.hasPendingRequest()) { return null; } return pool.request_ || pool.requestPool_.getValues()[0]; } /** * Helper function to return a formatted string representing an array of maps. */ function formatArrayOfMaps(arrayOfMaps) { var result = []; for (var i = 0; i < arrayOfMaps.length; i++) { var map = arrayOfMaps[i]; var keys = map.map.getKeys(); for (var j = 0; j < keys.length; j++) { var tmp = keys[j] + ':' + map.map.get(keys[j]) + (map.context ? ':' + map.context : ''); result.push(tmp); } } return result.join(', '); } function testFormatArrayOfMaps() { // This function is used in a non-trivial test, so let's verify that it works. var map1 = new goog.structs.Map(); map1.set('k1', 'v1'); map1.set('k2', 'v2'); var map2 = new goog.structs.Map(); map2.set('k3', 'v3'); var map3 = new goog.structs.Map(); map3.set('k4', 'v4'); map3.set('k5', 'v5'); map3.set('k6', 'v6'); // One map. var a = []; a.push(new goog.labs.net.webChannel.Wire.QueuedMap(0, map1)); assertEquals('k1:v1, k2:v2', formatArrayOfMaps(a)); // Many maps. var b = []; b.push(new goog.labs.net.webChannel.Wire.QueuedMap(0, map1)); b.push(new goog.labs.net.webChannel.Wire.QueuedMap(0, map2)); b.push(new goog.labs.net.webChannel.Wire.QueuedMap(0, map3)); assertEquals( 'k1:v1, k2:v2, k3:v3, k4:v4, k5:v5, k6:v6', formatArrayOfMaps(b)); // One map with a context. var c = []; c.push( new goog.labs.net.webChannel.Wire.QueuedMap(0, map1, new String('c1'))); assertEquals('k1:v1:c1, k2:v2:c1', formatArrayOfMaps(c)); } /** * @param {number=} opt_serverVersion * @param {string=} opt_hostPrefix * @param {string=} opt_uriPrefix * @param {boolean=} opt_spdyEnabled */ function connectForwardChannel( opt_serverVersion, opt_hostPrefix, opt_uriPrefix, opt_spdyEnabled) { stubSpdyCheck(!!opt_spdyEnabled); var uriPrefix = opt_uriPrefix || ''; channel.connect(uriPrefix + '/test', uriPrefix + '/bind', null); mockClock.tick(0); completeTestConnection(); completeForwardChannel(opt_serverVersion, opt_hostPrefix); } /** * @param {number=} opt_serverVersion * @param {string=} opt_hostPrefix * @param {string=} opt_uriPrefix * @param {boolean=} opt_spdyEnabled */ function connect( opt_serverVersion, opt_hostPrefix, opt_uriPrefix, opt_spdyEnabled) { connectForwardChannel( opt_serverVersion, opt_hostPrefix, opt_uriPrefix, opt_spdyEnabled); completeBackChannel(); } function disconnect() { channel.disconnect(); mockClock.tick(0); } function completeTestConnection() { completeForwardTestConnection(); completeBackTestConnection(); assertEquals( goog.labs.net.webChannel.WebChannelBase.State.OPENING, channel.getState()); } function completeForwardTestConnection() { channel.connectionTest_.onRequestData( channel.connectionTest_.request_, '["b"]'); channel.connectionTest_.onRequestComplete(channel.connectionTest_.request_); mockClock.tick(0); } function completeBackTestConnection() { channel.connectionTest_.onRequestData( channel.connectionTest_.request_, '11111'); mockClock.tick(0); } /** * @param {number=} opt_serverVersion * @param {string=} opt_hostPrefix */ function completeForwardChannel(opt_serverVersion, opt_hostPrefix) { var responseData = '[[0,["c","1234567890ABCDEF",' + (opt_hostPrefix ? '"' + opt_hostPrefix + '"' : 'null') + (opt_serverVersion ? ',' + opt_serverVersion : '') + ']]]'; channel.onRequestData(getSingleForwardRequest(), responseData); channel.onRequestComplete(getSingleForwardRequest()); mockClock.tick(0); } function completeBackChannel() { channel.onRequestData(channel.backChannelRequest_, '[[1,["foo"]]]'); channel.onRequestComplete(channel.backChannelRequest_); mockClock.tick(0); } function responseDone() { channel.onRequestData(getSingleForwardRequest(), '[1,0,0]'); // mock data channel.onRequestComplete(getSingleForwardRequest()); mockClock.tick(0); } /** * * @param {number=} opt_lastArrayIdSentFromServer * @param {number=} opt_outstandingDataSize */ function responseNoBackchannel( opt_lastArrayIdSentFromServer, opt_outstandingDataSize) { var responseData = goog.json.serialize( [0, opt_lastArrayIdSentFromServer, opt_outstandingDataSize]); channel.onRequestData(getSingleForwardRequest(), responseData); channel.onRequestComplete(getSingleForwardRequest()); mockClock.tick(0); } function response(lastArrayIdSentFromServer, outstandingDataSize) { var responseData = goog.json.serialize([1, lastArrayIdSentFromServer, outstandingDataSize]); channel.onRequestData(getSingleForwardRequest(), responseData); channel.onRequestComplete(getSingleForwardRequest()); mockClock.tick(0); } function receive(data) { channel.onRequestData(channel.backChannelRequest_, '[[1,' + data + ']]'); channel.onRequestComplete(channel.backChannelRequest_); mockClock.tick(0); } function responseTimeout() { getSingleForwardRequest().lastError_ = goog.labs.net.webChannel.ChannelRequest.Error.TIMEOUT; getSingleForwardRequest().successful_ = false; channel.onRequestComplete(getSingleForwardRequest()); mockClock.tick(0); } /** * @param {number=} opt_statusCode */ function responseRequestFailed(opt_statusCode) { getSingleForwardRequest().lastError_ = goog.labs.net.webChannel.ChannelRequest.Error.STATUS; getSingleForwardRequest().lastStatusCode_ = opt_statusCode || 503; getSingleForwardRequest().successful_ = false; channel.onRequestComplete(getSingleForwardRequest()); mockClock.tick(0); } function responseUnknownSessionId() { getSingleForwardRequest().lastError_ = goog.labs.net.webChannel.ChannelRequest.Error.UNKNOWN_SESSION_ID; getSingleForwardRequest().successful_ = false; channel.onRequestComplete(getSingleForwardRequest()); mockClock.tick(0); } /** * @param {string} key * @param {string} value * @param {string=} opt_context */ function sendMap(key, value, opt_context) { var map = new goog.structs.Map(); map.set(key, value); channel.sendMap(map, opt_context); mockClock.tick(0); } function hasForwardChannel() { return !!getSingleForwardRequest(); } function hasBackChannel() { return !!channel.backChannelRequest_; } function hasDeadBackChannelTimer() { return goog.isDefAndNotNull(channel.deadBackChannelTimerId_); } function assertHasForwardChannel() { assertTrue('Forward channel missing.', hasForwardChannel()); } function assertHasBackChannel() { assertTrue('Back channel missing.', hasBackChannel()); } function testConnect() { connect(); assertEquals( goog.labs.net.webChannel.WebChannelBase.State.OPENED, channel.getState()); // If the server specifies no version, the client assumes the latest version assertEquals( goog.labs.net.webChannel.Wire.LATEST_CHANNEL_VERSION, channel.channelVersion_); assertFalse(channel.isBuffered()); } function testConnect_backChannelEstablished() { connect(); assertHasBackChannel(); } function testConnect_withServerHostPrefix() { connect(undefined, 'serverHostPrefix'); assertEquals('serverHostPrefix', channel.hostPrefix_); } function testConnect_withClientHostPrefix() { handler.correctHostPrefix = function(hostPrefix) { return 'clientHostPrefix'; }; connect(); assertEquals('clientHostPrefix', channel.hostPrefix_); } function testConnect_overrideServerHostPrefix() { handler.correctHostPrefix = function(hostPrefix) { return 'clientHostPrefix'; }; connect(undefined, 'serverHostPrefix'); assertEquals('clientHostPrefix', channel.hostPrefix_); } function testConnect_withServerVersion() { connect(8); assertEquals(8, channel.channelVersion_); } function testConnect_notOkToMakeRequestForTest() { handler.okToMakeRequest = goog.functions.constant( goog.labs.net.webChannel.WebChannelBase.Error.NETWORK); channel.connect('/test', '/bind', null); mockClock.tick(0); assertEquals( goog.labs.net.webChannel.WebChannelBase.State.CLOSED, channel.getState()); } function testConnect_notOkToMakeRequestForBind() { channel.connect('/test', '/bind', null); mockClock.tick(0); completeTestConnection(); handler.okToMakeRequest = goog.functions.constant( goog.labs.net.webChannel.WebChannelBase.Error.NETWORK); completeForwardChannel(); assertEquals( goog.labs.net.webChannel.WebChannelBase.State.CLOSED, channel.getState()); } function testSendMap() { connect(); sendMapOnce(); } function testSendMapWithSpdyEnabled() { connect(undefined, undefined, undefined, true); sendMapOnce(); } function sendMapOnce() { assertEquals(1, numTimingEvents); sendMap('foo', 'bar'); responseDone(); assertEquals(2, numTimingEvents); assertEquals('foo:bar', formatArrayOfMaps(deliveredMaps)); } function testSendMap_twice() { connect(); sendMapTwice(); } function testSendMap_twiceWithSpdyEnabled() { connect(undefined, undefined, undefined, true); sendMapTwice(); } function sendMapTwice() { sendMap('foo1', 'bar1'); responseDone(); assertEquals('foo1:bar1', formatArrayOfMaps(deliveredMaps)); sendMap('foo2', 'bar2'); responseDone(); assertEquals('foo2:bar2', formatArrayOfMaps(deliveredMaps)); } function testSendMap_andReceive() { connect(); sendMap('foo', 'bar'); responseDone(); receive('["the server reply"]'); } function testReceive() { connect(); receive('["message from server"]'); assertHasBackChannel(); } function testReceive_twice() { connect(); receive('["message one from server"]'); receive('["message two from server"]'); assertHasBackChannel(); } function testReceive_andSendMap() { connect(); receive('["the server reply"]'); sendMap('foo', 'bar'); responseDone(); assertHasBackChannel(); } function testBackChannelRemainsEstablished_afterSingleSendMap() { connect(); sendMap('foo', 'bar'); responseDone(); receive('["ack"]'); assertHasBackChannel(); } function testBackChannelRemainsEstablished_afterDoubleSendMap() { connect(); sendMap('foo1', 'bar1'); sendMap('foo2', 'bar2'); responseDone(); receive('["ack"]'); // This assertion would fail prior to CL 13302660. assertHasBackChannel(); } function testTimingEvent() { connect(); assertEquals(1, numTimingEvents); sendMap('', ''); assertEquals(1, numTimingEvents); mockClock.tick(20); var expSize = getSingleForwardRequest().getPostData().length; responseDone(); assertEquals(2, numTimingEvents); assertEquals(expSize, lastPostSize); assertEquals(20, lastPostRtt); assertEquals(0, lastPostRetryCount); sendMap('abcdefg', '123456'); expSize = getSingleForwardRequest().getPostData().length; responseTimeout(); assertEquals(2, numTimingEvents); mockClock.tick(RETRY_TIME + 1); responseDone(); assertEquals(3, numTimingEvents); assertEquals(expSize, lastPostSize); assertEquals(1, lastPostRetryCount); assertEquals(1, lastPostRtt); } /** * Make sure that dropping the forward channel retry limit below the retry count * reports an error, and prevents another request from firing. */ function testSetFailFastWhileWaitingForRetry() { stubNetUtils(); connect(); setFailFastWhileWaitingForRetry(); } function testSetFailFastWhileWaitingForRetryWithSpdyEnabled() { stubNetUtils(); connect(undefined, undefined, undefined, true); setFailFastWhileWaitingForRetry(); } function setFailFastWhileWaitingForRetry() { assertEquals(1, numTimingEvents); sendMap('foo', 'bar'); assertNull(channel.forwardChannelTimerId_); assertNotNull(getSingleForwardRequest()); assertEquals(0, channel.forwardChannelRetryCount_); // Watchdog timeout. responseTimeout(); assertNotNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(1, channel.forwardChannelRetryCount_); // Almost finish the between-retry timeout. mockClock.tick(RETRY_TIME - 1); assertNotNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(1, channel.forwardChannelRetryCount_); // Setting max retries to 0 should cancel the timer and raise an error. channel.setFailFast(true); assertNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(1, channel.forwardChannelRetryCount_); assertTrue(gotError); assertEquals(0, deliveredMaps.length); // We get the error immediately before starting to ping google.com. // Simulate that timing out. We should get a network error in addition to the // initial failure. gotError = false; mockClock.tick(goog.labs.net.webChannel.netUtils.NETWORK_TIMEOUT); assertTrue('No error after network ping timed out.', gotError); // Make sure no more retry timers are firing. mockClock.tick(ALL_DAY_MS); assertNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(1, channel.forwardChannelRetryCount_); assertEquals(1, numTimingEvents); } /** * Make sure that dropping the forward channel retry limit below the retry count * reports an error, and prevents another request from firing. */ function testSetFailFastWhileRetryXhrIsInFlight() { stubNetUtils(); connect(); setFailFastWhileRetryXhrIsInFlight(); } function testSetFailFastWhileRetryXhrIsInFlightWithSpdyEnabled() { stubNetUtils(); connect(undefined, undefined, undefined, true); setFailFastWhileRetryXhrIsInFlight(); } function setFailFastWhileRetryXhrIsInFlight() { assertEquals(1, numTimingEvents); sendMap('foo', 'bar'); assertNull(channel.forwardChannelTimerId_); assertNotNull(getSingleForwardRequest()); assertEquals(0, channel.forwardChannelRetryCount_); // Watchdog timeout. responseTimeout(); assertNotNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(1, channel.forwardChannelRetryCount_); // Wait for the between-retry timeout. mockClock.tick(RETRY_TIME); assertNull(channel.forwardChannelTimerId_); assertNotNull(getSingleForwardRequest()); assertEquals(1, channel.forwardChannelRetryCount_); // Simulate a second watchdog timeout. responseTimeout(); assertNotNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(2, channel.forwardChannelRetryCount_); // Wait for another between-retry timeout. mockClock.tick(RETRY_TIME); // Now the third req is in flight. assertNull(channel.forwardChannelTimerId_); assertNotNull(getSingleForwardRequest()); assertEquals(2, channel.forwardChannelRetryCount_); // Set fail fast, killing the request channel.setFailFast(true); assertNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(2, channel.forwardChannelRetryCount_); assertTrue(gotError); // We get the error immediately before starting to ping google.com. // Simulate that timing out. We should get a network error in addition to the gotError = false; mockClock.tick(goog.labs.net.webChannel.netUtils.NETWORK_TIMEOUT); assertTrue('No error after network ping timed out.', gotError); // Make sure no more retry timers are firing. mockClock.tick(ALL_DAY_MS); assertNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(2, channel.forwardChannelRetryCount_); assertEquals(1, numTimingEvents); } /** * Makes sure that setting fail fast while not retrying doesn't cause a failure. */ function testSetFailFastAtRetryCount() { stubNetUtils(); connect(); assertEquals(1, numTimingEvents); sendMap('foo', 'bar'); assertNull(channel.forwardChannelTimerId_); assertNotNull(getSingleForwardRequest()); assertEquals(0, channel.forwardChannelRetryCount_); // Set fail fast. channel.setFailFast(true); // Request should still be alive. assertNull(channel.forwardChannelTimerId_); assertNotNull(getSingleForwardRequest()); assertEquals(0, channel.forwardChannelRetryCount_); // Watchdog timeout. Now we should get an error. responseTimeout(); assertNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(0, channel.forwardChannelRetryCount_); assertTrue(gotError); // We get the error immediately before starting to ping google.com. // Simulate that timing out. We should get a network error in addition to the // initial failure. gotError = false; mockClock.tick(goog.labs.net.webChannel.netUtils.NETWORK_TIMEOUT); assertTrue('No error after network ping timed out.', gotError); // Make sure no more retry timers are firing. mockClock.tick(ALL_DAY_MS); assertNull(channel.forwardChannelTimerId_); assertNull(getSingleForwardRequest()); assertEquals(0, channel.forwardChannelRetryCount_); assertEquals(1, numTimingEvents); } function testRequestFailedClosesChannel() { stubNetUtils(); connect(); requestFailedClosesChannel(); } function testRequestFailedClosesChannelWithSpdyEnabled() { stubNetUtils(); connect(undefined, undefined, undefined, true); requestFailedClosesChannel(); } function requestFailedClosesChannel() { assertEquals(1, numTimingEvents); sendMap('foo', 'bar'); responseRequestFailed(); assertEquals( 'Should be closed immediately after request failed.', goog.labs.net.webChannel.WebChannelBase.State.CLOSED, channel.getState()); mockClock.tick(goog.labs.net.webChannel.netUtils.NETWORK_TIMEOUT); assertEquals( 'Should remain closed after the ping timeout.', goog.labs.net.webChannel.WebChannelBase.State.CLOSED, channel.getState()); assertEquals(1, numTimingEvents); } function testStatEventReportedOnlyOnce() { stubNetUtils(); connect(); sendMap('foo', 'bar'); numStatEvents = 0; lastStatEvent = null; responseUnknownSessionId(); assertEquals(1, numStatEvents); assertEquals( goog.labs.net.webChannel.requestStats.Stat.ERROR_OTHER, lastStatEvent); numStatEvents = 0; mockClock.tick(goog.labs.net.webChannel.netUtils.NETWORK_TIMEOUT); assertEquals('No new stat events should be reported.', 0, numStatEvents); } function testStatEventReportedOnlyOnce_onNetworkUp() { stubNetUtils(); connect(); sendMap('foo', 'bar'); numStatEvents = 0; lastStatEvent = null; responseRequestFailed(); assertEquals( 'No stat event should be reported before we know the reason.', 0, numStatEvents); // Let the ping time out. mockClock.tick(goog.labs.net.webChannel.netUtils.NETWORK_TIMEOUT); // Assert we report the correct stat event. assertEquals(1, numStatEvents); assertEquals( goog.labs.net.webChannel.requestStats.Stat.ERROR_NETWORK, lastStatEvent); } function testStatEventReportedOnlyOnce_onNetworkDown() { stubNetUtils(); connect(); sendMap('foo', 'bar'); numStatEvents = 0; lastStatEvent = null; responseRequestFailed(); assertEquals( 'No stat event should be reported before we know the reason.', 0, numStatEvents); // Wait half the ping timeout period, and then fake the network being up. mockClock.tick(goog.labs.net.webChannel.netUtils.NETWORK_TIMEOUT / 2); channel.testNetworkCallback_(true); // Assert we report the correct stat event. assertEquals(1, numStatEvents); assertEquals( goog.labs.net.webChannel.requestStats.Stat.ERROR_OTHER, lastStatEvent); } function testOutgoingMapsAwaitsResponse() { connect(); outgoingMapsAwaitsResponse(); } function testOutgoingMapsAwaitsResponseWithSpdyEnabled() { connect(undefined, undefined, undefined, true); outgoingMapsAwaitsResponse(); } function outgoingMapsAwaitsResponse() { assertEquals(0, channel.outgoingMaps_.length); sendMap('foo1', 'bar'); assertEquals(0, channel.outgoingMaps_.length); sendMap('foo2', 'bar'); assertEquals(1, channel.outgoingMaps_.length); sendMap('foo3', 'bar'); assertEquals(2, channel.outgoingMaps_.length); sendMap('foo4', 'bar'); assertEquals(3, channel.outgoingMaps_.length); responseDone(); // Now the forward channel request is completed and a new started, so all maps // are dequeued from the array of outgoing maps into this new forward request. assertEquals(0, channel.outgoingMaps_.length); } function testUndeliveredMaps_doesNotNotifyWhenSuccessful() { /** * @suppress {checkTypes} The callback function type declaration is skipped. */ handler.channelClosed = function( channel, opt_pendingMaps, opt_undeliveredMaps) { if (opt_pendingMaps || opt_undeliveredMaps) { fail('No pending or undelivered maps should be reported.'); } }; connect(); sendMap('foo1', 'bar1'); responseDone(); sendMap('foo2', 'bar2'); responseDone(); disconnect(); } function testUndeliveredMaps_doesNotNotifyIfNothingWasSent() { /** * @suppress {checkTypes} The callback function type declaration is skipped. */ handler.channelClosed = function( channel, opt_pendingMaps, opt_undeliveredMaps) { if (opt_pendingMaps || opt_undeliveredMaps) { fail('No pending or undelivered maps should be reported.'); } }; connect(); mockClock.tick(ALL_DAY_MS); disconnect(); } function testUndeliveredMaps_clearsPendingMapsAfterNotifying() { connect(); sendMap('foo1', 'bar1'); sendMap('foo2', 'bar2'); sendMap('foo3', 'bar3'); assertEquals(1, channel.pendingMaps_.length); assertEquals(2, channel.outgoingMaps_.length); disconnect(); assertEquals(0, channel.pendingMaps_.length); assertEquals(0, channel.outgoingMaps_.length); } function testUndeliveredMaps_notifiesWithContext() { connect(); // First send two messages that succeed. sendMap('foo1', 'bar1', 'context1'); responseDone(); sendMap('foo2', 'bar2', 'context2'); responseDone(); // Pretend the server hangs and no longer responds. sendMap('foo3', 'bar3', 'context3'); sendMap('foo4', 'bar4', 'context4'); sendMap('foo5', 'bar5', 'context5'); // Give up. disconnect(); // Assert that we are informed of any undelivered messages; both about // #3 that was sent but which we don't know if the server received, and // #4 and #5 which remain in the outgoing maps and have not yet been sent. assertEquals('foo3:bar3:context3', handler.pendingMapsString); assertEquals( 'foo4:bar4:context4, foo5:bar5:context5', handler.undeliveredMapsString); } function testUndeliveredMaps_serviceUnavailable() { // Send a few maps, and let one fail. connect(); sendMap('foo1', 'bar1'); responseDone(); sendMap('foo2', 'bar2'); responseRequestFailed(); // After a failure, the channel should be closed. disconnect(); assertEquals('foo2:bar2', handler.pendingMapsString); assertEquals('', handler.undeliveredMapsString); } function testUndeliveredMaps_onPingTimeout() { stubNetUtils(); connect(); // Send a message. sendMap('foo1', 'bar1'); // Fake REQUEST_FAILED, triggering a ping to check the network. responseRequestFailed(); // Let the ping time out, unsuccessfully. mockClock.tick(goog.labs.net.webChannel.netUtils.NETWORK_TIMEOUT); // Assert channel is closed. assertEquals( goog.labs.net.webChannel.WebChannelBase.State.CLOSED, channel.getState()); // Assert that the handler is notified about the undelivered messages. assertEquals('foo1:bar1', handler.pendingMapsString); assertEquals('', handler.undeliveredMapsString); } function testResponseNoBackchannelPostNotBeforeBackchannel() { connect(8); sendMap('foo1', 'bar1'); mockClock.tick(10); assertFalse( channel.backChannelRequest_.getRequestStartTime() < getSingleForwardRequest().getRequestStartTime()); responseNoBackchannel(); assertNotEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_MISSING, lastStatEvent); } function testResponseNoBackchannel() { connect(8); sendMap('foo1', 'bar1'); response(-1, 0); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE + 1); sendMap('foo2', 'bar2'); assertTrue( channel.backChannelRequest_.getRequestStartTime() + goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE < getSingleForwardRequest().getRequestStartTime()); responseNoBackchannel(); assertEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_MISSING, lastStatEvent); } function testResponseNoBackchannelWithNoBackchannel() { connect(8); sendMap('foo1', 'bar1'); assertNull(channel.backChannelTimerId_); channel.backChannelRequest_.cancel(); channel.backChannelRequest_ = null; responseNoBackchannel(); assertEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_MISSING, lastStatEvent); } function testResponseNoBackchannelWithStartTimer() { connect(8); sendMap('foo1', 'bar1'); channel.backChannelRequest_.cancel(); channel.backChannelRequest_ = null; channel.backChannelTimerId_ = 123; responseNoBackchannel(); assertNotEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_MISSING, lastStatEvent); } function testResponseWithNoArraySent() { connect(8); sendMap('foo1', 'bar1'); // Send a response as if the server hasn't sent down an array. response(-1, 0); // POST response with an array ID lower than our last received is OK. assertEquals(1, channel.lastArrayId_); assertEquals(-1, channel.lastPostResponseArrayId_); } function testResponseWithArraysMissing() { connect(8); sendMap('foo1', 'bar1'); assertEquals(-1, channel.lastPostResponseArrayId_); // Send a response as if the server has sent down seven arrays. response(7, 111); assertEquals(1, channel.lastArrayId_); assertEquals(7, channel.lastPostResponseArrayId_); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE * 2); assertEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_DEAD, lastStatEvent); } function testMultipleResponsesWithArraysMissing() { connect(8); sendMap('foo1', 'bar1'); assertEquals(-1, channel.lastPostResponseArrayId_); // Send a response as if the server has sent down seven arrays. response(7, 111); assertEquals(1, channel.lastArrayId_); assertEquals(7, channel.lastPostResponseArrayId_); sendMap('foo2', 'bar2'); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE); response(8, 119); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE); // The original timer should still fire. assertEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_DEAD, lastStatEvent); } function testOnlyRetryOnceBasedOnResponse() { connect(8); sendMap('foo1', 'bar1'); assertEquals(-1, channel.lastPostResponseArrayId_); // Send a response as if the server has sent down seven arrays. response(7, 111); assertEquals(1, channel.lastArrayId_); assertEquals(7, channel.lastPostResponseArrayId_); assertTrue(hasDeadBackChannelTimer()); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE * 2); assertEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_DEAD, lastStatEvent); assertEquals(1, channel.backChannelRetryCount_); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE); sendMap('foo2', 'bar2'); assertFalse(hasDeadBackChannelTimer()); response(8, 119); assertFalse(hasDeadBackChannelTimer()); } function testResponseWithArraysMissingAndLiveChannel() { connect(8); sendMap('foo1', 'bar1'); assertEquals(-1, channel.lastPostResponseArrayId_); // Send a response as if the server has sent down seven arrays. response(7, 111); assertEquals(1, channel.lastArrayId_); assertEquals(7, channel.lastPostResponseArrayId_); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE); assertTrue(hasDeadBackChannelTimer()); receive('["ack"]'); assertFalse(hasDeadBackChannelTimer()); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE); assertNotEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_DEAD, lastStatEvent); } function testResponseWithBigOutstandingData() { connect(8); sendMap('foo1', 'bar1'); assertEquals(-1, channel.lastPostResponseArrayId_); // Send a response as if the server has sent down seven arrays and 50kbytes. response(7, 50000); assertEquals(1, channel.lastArrayId_); assertEquals(7, channel.lastPostResponseArrayId_); assertFalse(hasDeadBackChannelTimer()); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE * 2); assertNotEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_DEAD, lastStatEvent); } function testResponseInBufferedMode() { connect(8); channel.useChunked_ = false; sendMap('foo1', 'bar1'); assertEquals(-1, channel.lastPostResponseArrayId_); response(7, 111); assertEquals(1, channel.lastArrayId_); assertEquals(7, channel.lastPostResponseArrayId_); assertFalse(hasDeadBackChannelTimer()); mockClock.tick(goog.labs.net.webChannel.WebChannelBase.RTT_ESTIMATE * 2); assertNotEquals( goog.labs.net.webChannel.requestStats.Stat.BACKCHANNEL_DEAD, lastStatEvent); } function testResponseWithGarbage() { connect(8); sendMap('foo1', 'bar1'); channel.onRequestData(getSingleForwardRequest(), 'garbage'); assertEquals( goog.labs.net.webChannel.WebChannelBase.State.CLOSED, channel.getState()); } function testResponseWithGarbageInArray() { connect(8); sendMap('foo1', 'bar1'); channel.onRequestData(getSingleForwardRequest(), '["garbage"]'); assertEquals( goog.labs.net.webChannel.WebChannelBase.State.CLOSED, channel.getState()); } function testResponseWithEvilData() { connect(8); sendMap('foo1', 'bar1'); channel.onRequestData( getSingleForwardRequest(), 'foo=