/********************************************************************************
 * Copyright (C) 2018 TypeFox and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/ import { enableJSDOM } from '../browser/test/jsdom' ; let disableJSDOM = enableJSDOM ( ) ; import { FrontendApplicationConfigProvider } from './frontend-application-config-provider' ; import { ApplicationProps } from '@theia/application-package/lib/application-props' ; FrontendApplicationConfigProvider . set ( { ... ApplicationProps . DEFAULT . frontend . config } ) ; import { expect } from 'chai' ; import { ConnectionStatus , ConnectionStatusOptions , FrontendConnectionStatusService , PingService } from './connection-status-service' ; import { MockConnectionStatusService } from './test/mock-connection-status-service' ; import * as sinon from 'sinon' ; import { Container } from 'inversify' ; import { WebSocketConnectionProvider } from './messaging/ws-connection-provider' ; import { ILogger , Emitter , Loggable } from '../common' ; disableJSDOM ( ) ; describe ( 'connection-status' , function ( ) : void { let connectionStatusService ; before ( ( ) => { disableJSDOM = enableJSDOM ( ) ; } ) ; after ( ( ) => { disableJSDOM ( ) ; } ) ; beforeEach ( ( ) => { connectionStatusService = new MockConnectionStatusService ( ) ; } ) ; afterEach ( ( ) => { if ( connectionStatusService !== undefined ) { connectionStatusService . dispose ( ) ; } } ) ; it ( 'should go from online to offline if the connection is down' , async ( ) => { expect ( connectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . ONLINE ) ; connectionStatusService . alive = false ; await pause ( ) ; expect ( connectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . OFFLINE ) ; } ) ; it ( 'should go from offline to online if the connection is re-established' , async ( ) => { expect ( connectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . ONLINE ) ; connectionStatusService . alive = false ; await pause ( ) ; expect ( connectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . OFFLINE ) ; connectionStatusService . alive = true ; await pause ( ) ; expect ( connectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . ONLINE ) ; } ) ; } ) ; describe ( 'frontend-connection-status' , function ( ) : void { const OFFLINE_TIMEOUT = 10 ; let testContainer ; const mockSocketOpenedEmitter = new Emitter ( ) ; const mockSocketClosedEmitter = new Emitter ( ) ; const mockIncomingMessageActivityEmitter = new Emitter ( ) ; before ( ( ) => { disableJSDOM = enableJSDOM ( ) ; } ) ; after ( ( ) => { disableJSDOM ( ) ; } ) ; let timer ; let pingSpy ; beforeEach ( ( ) => { const mockWebSocketConnectionProvider = sinon . createStubInstance ( WebSocketConnectionProvider ) ; const mockPingService = < PingService > { ping ( ) { return Promise . resolve ( undefined ) ; } } ; const mockILogger = < ILogger > { error ( loggable ) { return Promise . resolve ( undefined ) ; } } ; testContainer = new Container ( ) ; testContainer . bind ( FrontendConnectionStatusService ) . toSelf ( ) . inSingletonScope ( ) ; testContainer . bind ( PingService ) . toConstantValue ( mockPingService ) ; testContainer . bind ( ILogger ) . toConstantValue ( mockILogger ) ; testContainer . bind ( ConnectionStatusOptions ) . toConstantValue ( { offlineTimeout : OFFLINE_TIMEOUT } ) ; testContainer . bind ( WebSocketConnectionProvider ) . toConstantValue ( mockWebSocketConnectionProvider ) ; sinon . stub ( mockWebSocketConnectionProvider , 'onSocketDidOpen' ) . value ( mockSocketOpenedEmitter . event ) ; sinon . stub ( mockWebSocketConnectionProvider , 'onSocketDidClose' ) . value ( mockSocketClosedEmitter . event ) ; sinon . stub ( mockWebSocketConnectionProvider , 'onIncomingMessageActivity' ) . value ( mockIncomingMessageActivityEmitter . event ) ; timer = sinon . useFakeTimers ( ) ; pingSpy = sinon . spy ( mockPingService , 'ping' ) ; } ) ; afterEach ( ( ) => { pingSpy . restore ( ) ; timer . restore ( ) ; testContainer . unbindAll ( ) ; } ) ; it ( 'should switch status to offline on websocket close' , ( ) => { const frontendConnectionStatusService = testContainer . get < FrontendConnectionStatusService > ( FrontendConnectionStatusService ) ; frontendConnectionStatusService [ 'init' ] ( ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . ONLINE ) ; mockSocketClosedEmitter . fire ( undefined ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . OFFLINE ) ; } ) ; it ( 'should switch status to online on websocket established' , ( ) => { const frontendConnectionStatusService = testContainer . get < FrontendConnectionStatusService > ( FrontendConnectionStatusService ) ; frontendConnectionStatusService [ 'init' ] ( ) ; mockSocketClosedEmitter . fire ( undefined ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . OFFLINE ) ; mockSocketOpenedEmitter . fire ( undefined ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . ONLINE ) ; } ) ; it ( 'should switch status to online on any websocket activity' , ( ) => { const frontendConnectionStatusService = testContainer . get < FrontendConnectionStatusService > ( FrontendConnectionStatusService ) ; frontendConnectionStatusService [ 'init' ] ( ) ; mockSocketClosedEmitter . fire ( undefined ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . OFFLINE ) ; mockIncomingMessageActivityEmitter . fire ( undefined ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . ONLINE ) ; } ) ; it ( 'should perform ping request after socket activity' , ( ) => { const frontendConnectionStatusService = testContainer . get < FrontendConnectionStatusService > ( FrontendConnectionStatusService ) ; frontendConnectionStatusService [ 'init' ] ( ) ; mockIncomingMessageActivityEmitter . fire ( undefined ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . ONLINE ) ; sinon . assert . notCalled ( pingSpy ) ; timer . tick ( OFFLINE_TIMEOUT ) ; sinon . assert . calledOnce ( pingSpy ) ; } ) ; it ( 'should not perform ping request before desired timeout' , ( ) => { const frontendConnectionStatusService = testContainer . get < FrontendConnectionStatusService > ( FrontendConnectionStatusService ) ; frontendConnectionStatusService [ 'init' ] ( ) ; mockIncomingMessageActivityEmitter . fire ( undefined ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . ONLINE ) ; sinon . assert . notCalled ( pingSpy ) ; timer . tick ( OFFLINE_TIMEOUT - 1 ) ; sinon . assert . notCalled ( pingSpy ) ; } ) ; it ( 'should switch to offline mode if ping request was rejected' , ( ) => { const pingService = testContainer . get < PingService > ( PingService ) ; pingSpy . restore ( ) ; const stub = sinon . stub ( pingService , 'ping' ) . onFirstCall ( ) . throws ( 'failed to make a ping request' ) ; const frontendConnectionStatusService = testContainer . get < FrontendConnectionStatusService > ( FrontendConnectionStatusService ) ; frontendConnectionStatusService [ 'init' ] ( ) ; mockIncomingMessageActivityEmitter . fire ( undefined ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . ONLINE ) ; timer . tick ( OFFLINE_TIMEOUT ) ; sinon . assert . calledOnce ( stub ) ; expect ( frontendConnectionStatusService . currentStatus ) . to . be . equal ( ConnectionStatus . OFFLINE ) ; } ) ; } ) ; function pause ( time = 1 ) { return new Promise ( resolve => setTimeout ( resolve , time ) ) ; }