mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-13 06:38:48 +02:00
1130 lines
36 KiB
JavaScript
1130 lines
36 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
var expect = chai.expect;
|
|
|
|
describe("loop.store.ConversationStore", function () {
|
|
"use strict";
|
|
|
|
var CALL_STATES = loop.store.CALL_STATES;
|
|
var WS_STATES = loop.store.WS_STATES;
|
|
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
|
var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
|
|
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
|
var sharedActions = loop.shared.actions;
|
|
var sharedUtils = loop.shared.utils;
|
|
var sandbox, dispatcher, client, store, fakeSessionData, sdkDriver;
|
|
var contact, fakeMozLoop;
|
|
var connectPromise, resolveConnectPromise, rejectConnectPromise;
|
|
var wsCancelSpy, wsCloseSpy, wsDeclineSpy, wsMediaUpSpy, fakeWebsocket;
|
|
|
|
function checkFailures(done, f) {
|
|
try {
|
|
f();
|
|
done();
|
|
} catch (err) {
|
|
done(err);
|
|
}
|
|
}
|
|
|
|
beforeEach(function() {
|
|
sandbox = sinon.sandbox.create();
|
|
|
|
contact = {
|
|
name: [ "Mr Smith" ],
|
|
email: [{
|
|
type: "home",
|
|
value: "fakeEmail",
|
|
pref: true
|
|
}]
|
|
};
|
|
|
|
fakeMozLoop = {
|
|
getLoopPref: sandbox.stub(),
|
|
addConversationContext: sandbox.stub(),
|
|
calls: {
|
|
setCallInProgress: sandbox.stub(),
|
|
clearCallInProgress: sandbox.stub(),
|
|
blockDirectCaller: sandbox.stub()
|
|
},
|
|
rooms: {
|
|
create: sandbox.stub()
|
|
}
|
|
};
|
|
|
|
dispatcher = new loop.Dispatcher();
|
|
client = {
|
|
setupOutgoingCall: sinon.stub(),
|
|
requestCallUrl: sinon.stub()
|
|
};
|
|
sdkDriver = {
|
|
connectSession: sinon.stub(),
|
|
disconnectSession: sinon.stub(),
|
|
retryPublishWithoutVideo: sinon.stub()
|
|
};
|
|
|
|
wsCancelSpy = sinon.spy();
|
|
wsCloseSpy = sinon.spy();
|
|
wsDeclineSpy = sinon.spy();
|
|
wsMediaUpSpy = sinon.spy();
|
|
|
|
fakeWebsocket = {
|
|
cancel: wsCancelSpy,
|
|
close: wsCloseSpy,
|
|
decline: wsDeclineSpy,
|
|
mediaUp: wsMediaUpSpy
|
|
};
|
|
|
|
store = new loop.store.ConversationStore(dispatcher, {
|
|
client: client,
|
|
mozLoop: fakeMozLoop,
|
|
sdkDriver: sdkDriver
|
|
});
|
|
fakeSessionData = {
|
|
apiKey: "fakeKey",
|
|
callId: "142536",
|
|
sessionId: "321456",
|
|
sessionToken: "341256",
|
|
websocketToken: "543216",
|
|
windowId: "28",
|
|
progressURL: "fakeURL"
|
|
};
|
|
|
|
var dummySocket = {
|
|
close: sinon.spy(),
|
|
send: sinon.spy()
|
|
};
|
|
|
|
connectPromise = new Promise(function(resolve, reject) {
|
|
resolveConnectPromise = resolve;
|
|
rejectConnectPromise = reject;
|
|
});
|
|
|
|
sandbox.stub(loop.CallConnectionWebSocket.prototype,
|
|
"promiseConnect").returns(connectPromise);
|
|
});
|
|
|
|
afterEach(function() {
|
|
sandbox.restore();
|
|
});
|
|
|
|
describe("#initialize", function() {
|
|
it("should throw an error if the client is missing", function() {
|
|
expect(function() {
|
|
new loop.store.ConversationStore(dispatcher, {
|
|
sdkDriver: sdkDriver
|
|
});
|
|
}).to.Throw(/client/);
|
|
});
|
|
|
|
it("should throw an error if the sdkDriver is missing", function() {
|
|
expect(function() {
|
|
new loop.store.ConversationStore(dispatcher, {
|
|
client: client
|
|
});
|
|
}).to.Throw(/sdkDriver/);
|
|
});
|
|
|
|
it("should throw an error if mozLoop is missing", function() {
|
|
expect(function() {
|
|
new loop.store.ConversationStore(dispatcher, {
|
|
sdkDriver: sdkDriver,
|
|
client: client
|
|
});
|
|
}).to.Throw(/mozLoop/);
|
|
});
|
|
});
|
|
|
|
describe("#connectionFailure", function() {
|
|
beforeEach(function() {
|
|
store._websocket = fakeWebsocket;
|
|
store.setStoreState({windowId: "42"});
|
|
});
|
|
|
|
it("should retry publishing if on desktop, and in the videoMuted state", function() {
|
|
store._isDesktop = true;
|
|
|
|
store.connectionFailure(new sharedActions.ConnectionFailure({
|
|
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
|
}));
|
|
|
|
sinon.assert.calledOnce(sdkDriver.retryPublishWithoutVideo);
|
|
});
|
|
|
|
it("should set videoMuted to try when retrying publishing", function() {
|
|
store._isDesktop = true;
|
|
|
|
store.connectionFailure(new sharedActions.ConnectionFailure({
|
|
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
|
}));
|
|
|
|
expect(store.getStoreState().videoMuted).eql(true);
|
|
});
|
|
|
|
it("should disconnect the session", function() {
|
|
store.connectionFailure(
|
|
new sharedActions.ConnectionFailure({reason: "fake"}));
|
|
|
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
|
});
|
|
|
|
it("should ensure the websocket is closed", function() {
|
|
store.connectionFailure(
|
|
new sharedActions.ConnectionFailure({reason: "fake"}));
|
|
|
|
sinon.assert.calledOnce(wsCloseSpy);
|
|
});
|
|
|
|
it("should set the state to 'terminated'", function() {
|
|
store.setStoreState({callState: CALL_STATES.ALERTING});
|
|
|
|
store.connectionFailure(
|
|
new sharedActions.ConnectionFailure({reason: "fake"}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.TERMINATED);
|
|
expect(store.getStoreState("callStateReason")).eql("fake");
|
|
});
|
|
|
|
it("should release mozLoop callsData", function() {
|
|
store.connectionFailure(
|
|
new sharedActions.ConnectionFailure({reason: "fake"}));
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.calls.clearCallInProgress);
|
|
sinon.assert.calledWithExactly(
|
|
fakeMozLoop.calls.clearCallInProgress, "42");
|
|
});
|
|
});
|
|
|
|
describe("#connectionProgress", function() {
|
|
describe("progress: init", function() {
|
|
it("should change the state from 'gather' to 'connecting'", function() {
|
|
store.setStoreState({callState: CALL_STATES.GATHER});
|
|
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.INIT}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.CONNECTING);
|
|
});
|
|
});
|
|
|
|
describe("progress: alerting", function() {
|
|
it("should change the state from 'gather' to 'alerting'", function() {
|
|
store.setStoreState({callState: CALL_STATES.GATHER});
|
|
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.ALERTING);
|
|
});
|
|
|
|
it("should change the state from 'init' to 'alerting'", function() {
|
|
store.setStoreState({callState: CALL_STATES.INIT});
|
|
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.ALERTING);
|
|
});
|
|
});
|
|
|
|
describe("progress: connecting (outgoing calls)", function() {
|
|
beforeEach(function() {
|
|
store.setStoreState({
|
|
callState: CALL_STATES.ALERTING,
|
|
outgoing: true
|
|
});
|
|
});
|
|
|
|
it("should change the state to 'ongoing'", function() {
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.ONGOING);
|
|
});
|
|
|
|
it("should connect the session", function() {
|
|
store.setStoreState(fakeSessionData);
|
|
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
|
|
|
sinon.assert.calledOnce(sdkDriver.connectSession);
|
|
sinon.assert.calledWithExactly(sdkDriver.connectSession, {
|
|
apiKey: "fakeKey",
|
|
sessionId: "321456",
|
|
sessionToken: "341256"
|
|
});
|
|
});
|
|
|
|
it("should call mozLoop.addConversationContext", function() {
|
|
store.setStoreState(fakeSessionData);
|
|
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.addConversationContext);
|
|
sinon.assert.calledWithExactly(fakeMozLoop.addConversationContext,
|
|
"28", "321456", "142536");
|
|
});
|
|
});
|
|
|
|
describe("progress: connecting (incoming calls)", function() {
|
|
beforeEach(function() {
|
|
store.setStoreState({
|
|
callState: CALL_STATES.ALERTING,
|
|
outgoing: false,
|
|
windowId: 42
|
|
});
|
|
|
|
sandbox.stub(console, "error");
|
|
store._websocket = fakeWebsocket;
|
|
});
|
|
|
|
it("should log an error", function() {
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
|
|
|
sinon.assert.calledOnce(console.error);
|
|
});
|
|
|
|
it("should call decline on the websocket", function() {
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
|
|
|
sinon.assert.calledOnce(fakeWebsocket.decline);
|
|
});
|
|
|
|
it("should close the websocket", function() {
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
|
|
|
sinon.assert.calledOnce(fakeWebsocket.close);
|
|
});
|
|
|
|
it("should clear the call in progress for the backend", function() {
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.calls.clearCallInProgress);
|
|
sinon.assert.calledWithExactly(fakeMozLoop.calls.clearCallInProgress, 42);
|
|
});
|
|
|
|
it("should set the call state to CLOSE", function() {
|
|
store.connectionProgress(
|
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.CLOSE);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("#setupWindowData", function() {
|
|
var fakeSetupWindowData;
|
|
|
|
beforeEach(function() {
|
|
store.setStoreState({callState: CALL_STATES.INIT});
|
|
fakeSetupWindowData = {
|
|
windowId: "123456",
|
|
type: "outgoing",
|
|
contact: contact,
|
|
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO
|
|
};
|
|
});
|
|
|
|
it("should set the state to 'gather'", function() {
|
|
dispatcher.dispatch(
|
|
new sharedActions.SetupWindowData(fakeSetupWindowData));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.GATHER);
|
|
});
|
|
|
|
it("should save the basic call information", function() {
|
|
dispatcher.dispatch(
|
|
new sharedActions.SetupWindowData(fakeSetupWindowData));
|
|
|
|
expect(store.getStoreState("windowId")).eql("123456");
|
|
expect(store.getStoreState("outgoing")).eql(true);
|
|
});
|
|
|
|
it("should save the basic information from the mozLoop api", function() {
|
|
dispatcher.dispatch(
|
|
new sharedActions.SetupWindowData(fakeSetupWindowData));
|
|
|
|
expect(store.getStoreState("contact")).eql(contact);
|
|
expect(store.getStoreState("callType"))
|
|
.eql(sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
|
});
|
|
|
|
describe("incoming calls", function() {
|
|
beforeEach(function() {
|
|
store.setStoreState({outgoing: false});
|
|
});
|
|
|
|
it("should initialize the websocket", function() {
|
|
sandbox.stub(loop, "CallConnectionWebSocket").returns({
|
|
promiseConnect: function() { return connectPromise; },
|
|
on: sinon.spy()
|
|
});
|
|
|
|
store.connectCall(
|
|
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
|
|
|
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
|
|
sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
|
|
url: "fakeURL",
|
|
callId: "142536",
|
|
websocketToken: "543216"
|
|
});
|
|
});
|
|
|
|
it("should connect the websocket to the server", function() {
|
|
store.connectCall(
|
|
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
|
|
|
sinon.assert.calledOnce(store._websocket.promiseConnect);
|
|
});
|
|
|
|
describe("WebSocket connection result", function() {
|
|
beforeEach(function() {
|
|
store.connectCall(
|
|
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
|
|
|
sandbox.stub(dispatcher, "dispatch");
|
|
});
|
|
|
|
it("should dispatch a connection progress action on success", function(done) {
|
|
resolveConnectPromise(WS_STATES.INIT);
|
|
|
|
connectPromise.then(function() {
|
|
checkFailures(done, function() {
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("name", "connectionProgress"));
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("wsState", WS_STATES.INIT));
|
|
});
|
|
}, function() {
|
|
done(new Error("Promise should have been resolve, not rejected"));
|
|
});
|
|
});
|
|
|
|
it("should dispatch a connection failure action on failure", function(done) {
|
|
rejectConnectPromise();
|
|
|
|
connectPromise.then(function() {
|
|
done(new Error("Promise should have been rejected, not resolved"));
|
|
}, function() {
|
|
checkFailures(done, function() {
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("name", "connectionFailure"));
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("reason", "websocket-setup"));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("outgoing calls", function() {
|
|
it("should request the outgoing call data", function() {
|
|
dispatcher.dispatch(
|
|
new sharedActions.SetupWindowData(fakeSetupWindowData));
|
|
|
|
sinon.assert.calledOnce(client.setupOutgoingCall);
|
|
sinon.assert.calledWith(client.setupOutgoingCall,
|
|
["fakeEmail"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
|
});
|
|
|
|
it("should include all email addresses in the call data", function() {
|
|
fakeSetupWindowData.contact = {
|
|
name: [ "Mr Smith" ],
|
|
email: [{
|
|
type: "home",
|
|
value: "fakeEmail",
|
|
pref: true
|
|
},
|
|
{
|
|
type: "work",
|
|
value: "emailFake",
|
|
pref: false
|
|
}]
|
|
};
|
|
|
|
dispatcher.dispatch(
|
|
new sharedActions.SetupWindowData(fakeSetupWindowData));
|
|
|
|
sinon.assert.calledOnce(client.setupOutgoingCall);
|
|
sinon.assert.calledWith(client.setupOutgoingCall,
|
|
["fakeEmail", "emailFake"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
|
});
|
|
|
|
it("should include trim phone numbers for the call data", function() {
|
|
fakeSetupWindowData.contact = {
|
|
name: [ "Mr Smith" ],
|
|
tel: [{
|
|
type: "home",
|
|
value: "+44-5667+345 496(2335)45+ 456+",
|
|
pref: true
|
|
}]
|
|
};
|
|
|
|
dispatcher.dispatch(
|
|
new sharedActions.SetupWindowData(fakeSetupWindowData));
|
|
|
|
sinon.assert.calledOnce(client.setupOutgoingCall);
|
|
sinon.assert.calledWith(client.setupOutgoingCall,
|
|
["+445667345496233545456"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
|
});
|
|
|
|
it("should include all email and telephone values in the call data", function() {
|
|
fakeSetupWindowData.contact = {
|
|
name: [ "Mr Smith" ],
|
|
email: [{
|
|
type: "home",
|
|
value: "fakeEmail",
|
|
pref: true
|
|
}, {
|
|
type: "work",
|
|
value: "emailFake",
|
|
pref: false
|
|
}],
|
|
tel: [{
|
|
type: "work",
|
|
value: "01234567890",
|
|
pref: false
|
|
}, {
|
|
type: "home",
|
|
value: "09876543210",
|
|
pref: false
|
|
}]
|
|
};
|
|
|
|
dispatcher.dispatch(
|
|
new sharedActions.SetupWindowData(fakeSetupWindowData));
|
|
|
|
sinon.assert.calledOnce(client.setupOutgoingCall);
|
|
sinon.assert.calledWith(client.setupOutgoingCall,
|
|
["fakeEmail", "emailFake", "01234567890", "09876543210"],
|
|
sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
|
});
|
|
|
|
describe("server response handling", function() {
|
|
beforeEach(function() {
|
|
sandbox.stub(dispatcher, "dispatch");
|
|
});
|
|
|
|
it("should dispatch a connect call action on success", function() {
|
|
var callData = {
|
|
apiKey: "fakeKey"
|
|
};
|
|
|
|
client.setupOutgoingCall.callsArgWith(2, null, callData);
|
|
|
|
store.setupWindowData(
|
|
new sharedActions.SetupWindowData(fakeSetupWindowData));
|
|
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("name", "connectCall"));
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("sessionData", callData));
|
|
});
|
|
|
|
it("should dispatch a connection failure action on failure", function() {
|
|
client.setupOutgoingCall.callsArgWith(2, {});
|
|
|
|
store.setupWindowData(
|
|
new sharedActions.SetupWindowData(fakeSetupWindowData));
|
|
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("name", "connectionFailure"));
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("reason", "setup"));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("#acceptCall", function() {
|
|
beforeEach(function() {
|
|
store._websocket = {
|
|
accept: sinon.stub()
|
|
};
|
|
});
|
|
|
|
it("should save the call type", function() {
|
|
store.acceptCall(
|
|
new sharedActions.AcceptCall({callType: CALL_TYPES.AUDIO_ONLY}));
|
|
|
|
expect(store.getStoreState("callType")).eql(CALL_TYPES.AUDIO_ONLY);
|
|
expect(store.getStoreState("videoMuted")).eql(true);
|
|
});
|
|
|
|
it("should call accept on the websocket", function() {
|
|
store.acceptCall(
|
|
new sharedActions.AcceptCall({callType: CALL_TYPES.AUDIO_ONLY}));
|
|
|
|
sinon.assert.calledOnce(store._websocket.accept);
|
|
});
|
|
|
|
it("should change the state to 'ongoing'", function() {
|
|
store.acceptCall(
|
|
new sharedActions.AcceptCall({callType: CALL_TYPES.AUDIO_ONLY}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.ONGOING);
|
|
});
|
|
|
|
it("should connect the session", function() {
|
|
store.setStoreState(fakeSessionData);
|
|
|
|
store.acceptCall(
|
|
new sharedActions.AcceptCall({callType: CALL_TYPES.AUDIO_ONLY}));
|
|
|
|
sinon.assert.calledOnce(sdkDriver.connectSession);
|
|
sinon.assert.calledWithExactly(sdkDriver.connectSession, {
|
|
apiKey: "fakeKey",
|
|
sessionId: "321456",
|
|
sessionToken: "341256"
|
|
});
|
|
});
|
|
|
|
it("should call mozLoop.addConversationContext", function() {
|
|
store.setStoreState(fakeSessionData);
|
|
|
|
store.acceptCall(
|
|
new sharedActions.AcceptCall({callType: CALL_TYPES.AUDIO_ONLY}));
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.addConversationContext);
|
|
sinon.assert.calledWithExactly(fakeMozLoop.addConversationContext,
|
|
"28", "321456", "142536");
|
|
});
|
|
});
|
|
|
|
describe("#declineCall", function() {
|
|
beforeEach(function() {
|
|
store._websocket = fakeWebsocket;
|
|
|
|
store.setStoreState({windowId: 42});
|
|
});
|
|
|
|
it("should block the caller if necessary", function() {
|
|
store.declineCall(new sharedActions.DeclineCall({blockCaller: true}));
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.calls.blockDirectCaller);
|
|
});
|
|
|
|
it("should call decline on the websocket", function() {
|
|
store.declineCall(new sharedActions.DeclineCall({blockCaller: false}));
|
|
|
|
sinon.assert.calledOnce(fakeWebsocket.decline);
|
|
});
|
|
|
|
it("should close the websocket", function() {
|
|
store.declineCall(new sharedActions.DeclineCall({blockCaller: false}));
|
|
|
|
sinon.assert.calledOnce(fakeWebsocket.close);
|
|
});
|
|
|
|
it("should clear the call in progress for the backend", function() {
|
|
store.declineCall(new sharedActions.DeclineCall({blockCaller: false}));
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.calls.clearCallInProgress);
|
|
sinon.assert.calledWithExactly(fakeMozLoop.calls.clearCallInProgress, 42);
|
|
});
|
|
|
|
it("should set the call state to CLOSE", function() {
|
|
store.declineCall(new sharedActions.DeclineCall({blockCaller: false}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.CLOSE);
|
|
});
|
|
});
|
|
|
|
describe("#connectCall", function() {
|
|
it("should save the call session data", function() {
|
|
store.connectCall(
|
|
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
|
|
|
expect(store.getStoreState("apiKey")).eql("fakeKey");
|
|
expect(store.getStoreState("callId")).eql("142536");
|
|
expect(store.getStoreState("sessionId")).eql("321456");
|
|
expect(store.getStoreState("sessionToken")).eql("341256");
|
|
expect(store.getStoreState("websocketToken")).eql("543216");
|
|
expect(store.getStoreState("progressURL")).eql("fakeURL");
|
|
});
|
|
|
|
it("should initialize the websocket", function() {
|
|
sandbox.stub(loop, "CallConnectionWebSocket").returns({
|
|
promiseConnect: function() { return connectPromise; },
|
|
on: sinon.spy()
|
|
});
|
|
|
|
store.connectCall(
|
|
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
|
|
|
sinon.assert.calledOnce(loop.CallConnectionWebSocket);
|
|
sinon.assert.calledWithExactly(loop.CallConnectionWebSocket, {
|
|
url: "fakeURL",
|
|
callId: "142536",
|
|
websocketToken: "543216"
|
|
});
|
|
});
|
|
|
|
it("should connect the websocket to the server", function() {
|
|
store.connectCall(
|
|
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
|
|
|
sinon.assert.calledOnce(store._websocket.promiseConnect);
|
|
});
|
|
|
|
describe("WebSocket connection result", function() {
|
|
beforeEach(function() {
|
|
store.connectCall(
|
|
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
|
|
|
sandbox.stub(dispatcher, "dispatch");
|
|
});
|
|
|
|
it("should dispatch a connection progress action on success", function(done) {
|
|
resolveConnectPromise(WS_STATES.INIT);
|
|
|
|
connectPromise.then(function() {
|
|
checkFailures(done, function() {
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("name", "connectionProgress"));
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("wsState", WS_STATES.INIT));
|
|
});
|
|
}, function() {
|
|
done(new Error("Promise should have been resolve, not rejected"));
|
|
});
|
|
});
|
|
|
|
it("should dispatch a connection failure action on failure", function(done) {
|
|
rejectConnectPromise();
|
|
|
|
connectPromise.then(function() {
|
|
done(new Error("Promise should have been rejected, not resolved"));
|
|
}, function() {
|
|
checkFailures(done, function() {
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("name", "connectionFailure"));
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("reason", "websocket-setup"));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("#hangupCall", function() {
|
|
var wsMediaFailSpy, wsCloseSpy;
|
|
beforeEach(function() {
|
|
wsMediaFailSpy = sinon.spy();
|
|
wsCloseSpy = sinon.spy();
|
|
|
|
store._websocket = {
|
|
mediaFail: wsMediaFailSpy,
|
|
close: wsCloseSpy
|
|
};
|
|
store.setStoreState({callState: CALL_STATES.ONGOING});
|
|
store.setStoreState({windowId: "42"});
|
|
});
|
|
|
|
it("should disconnect the session", function() {
|
|
store.hangupCall(new sharedActions.HangupCall());
|
|
|
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
|
});
|
|
|
|
it("should send a media-fail message to the websocket if it is open", function() {
|
|
store.hangupCall(new sharedActions.HangupCall());
|
|
|
|
sinon.assert.calledOnce(wsMediaFailSpy);
|
|
});
|
|
|
|
it("should ensure the websocket is closed", function() {
|
|
store.hangupCall(new sharedActions.HangupCall());
|
|
|
|
sinon.assert.calledOnce(wsCloseSpy);
|
|
});
|
|
|
|
it("should set the callState to finished", function() {
|
|
store.hangupCall(new sharedActions.HangupCall());
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.FINISHED);
|
|
});
|
|
|
|
it("should release mozLoop callsData", function() {
|
|
store.hangupCall(new sharedActions.HangupCall());
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.calls.clearCallInProgress);
|
|
sinon.assert.calledWithExactly(
|
|
fakeMozLoop.calls.clearCallInProgress, "42");
|
|
});
|
|
});
|
|
|
|
describe("#remotePeerDisconnected", function() {
|
|
var wsMediaFailSpy, wsCloseSpy;
|
|
beforeEach(function() {
|
|
wsMediaFailSpy = sinon.spy();
|
|
wsCloseSpy = sinon.spy();
|
|
|
|
store._websocket = {
|
|
mediaFail: wsMediaFailSpy,
|
|
close: wsCloseSpy
|
|
};
|
|
store.setStoreState({callState: CALL_STATES.ONGOING});
|
|
store.setStoreState({windowId: "42"});
|
|
});
|
|
|
|
it("should disconnect the session", function() {
|
|
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
|
peerHungup: true
|
|
}));
|
|
|
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
|
});
|
|
|
|
it("should ensure the websocket is closed", function() {
|
|
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
|
peerHungup: true
|
|
}));
|
|
|
|
sinon.assert.calledOnce(wsCloseSpy);
|
|
});
|
|
|
|
it("should release mozLoop callsData", function() {
|
|
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
|
peerHungup: true
|
|
}));
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.calls.clearCallInProgress);
|
|
sinon.assert.calledWithExactly(
|
|
fakeMozLoop.calls.clearCallInProgress, "42");
|
|
});
|
|
|
|
it("should set the callState to finished if the peer hungup", function() {
|
|
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
|
peerHungup: true
|
|
}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.FINISHED);
|
|
});
|
|
|
|
it("should set the callState to terminated if the peer was disconnected" +
|
|
"for an unintentional reason", function() {
|
|
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
|
peerHungup: false
|
|
}));
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.TERMINATED);
|
|
});
|
|
|
|
it("should set the reason to peerNetworkDisconnected if the peer was" +
|
|
"disconnected for an unintentional reason", function() {
|
|
store.remotePeerDisconnected(new sharedActions.RemotePeerDisconnected({
|
|
peerHungup: false
|
|
}));
|
|
|
|
expect(store.getStoreState("callStateReason"))
|
|
.eql("peerNetworkDisconnected");
|
|
});
|
|
});
|
|
|
|
describe("#cancelCall", function() {
|
|
beforeEach(function() {
|
|
store._websocket = fakeWebsocket;
|
|
|
|
store.setStoreState({callState: CALL_STATES.CONNECTING});
|
|
store.setStoreState({windowId: "42"});
|
|
});
|
|
|
|
it("should disconnect the session", function() {
|
|
store.cancelCall(new sharedActions.CancelCall());
|
|
|
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
|
});
|
|
|
|
it("should send a cancel message to the websocket if it is open for outgoing calls", function() {
|
|
store.setStoreState({outgoing: true});
|
|
|
|
store.cancelCall(new sharedActions.CancelCall());
|
|
|
|
sinon.assert.calledOnce(wsCancelSpy);
|
|
});
|
|
|
|
it("should ensure the websocket is closed", function() {
|
|
store.cancelCall(new sharedActions.CancelCall());
|
|
|
|
sinon.assert.calledOnce(wsCloseSpy);
|
|
});
|
|
|
|
it("should set the state to close if the call is connecting", function() {
|
|
store.cancelCall(new sharedActions.CancelCall());
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.CLOSE);
|
|
});
|
|
|
|
it("should set the state to close if the call has terminated already", function() {
|
|
store.setStoreState({callState: CALL_STATES.TERMINATED});
|
|
|
|
store.cancelCall(new sharedActions.CancelCall());
|
|
|
|
expect(store.getStoreState("callState")).eql(CALL_STATES.CLOSE);
|
|
});
|
|
|
|
it("should release mozLoop callsData", function() {
|
|
store.cancelCall(new sharedActions.CancelCall());
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.calls.clearCallInProgress);
|
|
sinon.assert.calledWithExactly(
|
|
fakeMozLoop.calls.clearCallInProgress, "42");
|
|
});
|
|
});
|
|
|
|
describe("#retryCall", function() {
|
|
it("should set the state to gather", function() {
|
|
store.setStoreState({callState: CALL_STATES.TERMINATED});
|
|
|
|
store.retryCall(new sharedActions.RetryCall());
|
|
|
|
expect(store.getStoreState("callState"))
|
|
.eql(CALL_STATES.GATHER);
|
|
});
|
|
|
|
it("should request the outgoing call data", function() {
|
|
store.setStoreState({
|
|
callState: CALL_STATES.TERMINATED,
|
|
outgoing: true,
|
|
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO,
|
|
contact: contact
|
|
});
|
|
|
|
store.retryCall(new sharedActions.RetryCall());
|
|
|
|
sinon.assert.calledOnce(client.setupOutgoingCall);
|
|
sinon.assert.calledWith(client.setupOutgoingCall,
|
|
["fakeEmail"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
|
});
|
|
});
|
|
|
|
describe("#mediaConnected", function() {
|
|
it("should send mediaUp via the websocket", function() {
|
|
store._websocket = fakeWebsocket;
|
|
|
|
store.mediaConnected(new sharedActions.MediaConnected());
|
|
|
|
sinon.assert.calledOnce(wsMediaUpSpy);
|
|
});
|
|
});
|
|
|
|
describe("#setMute", function() {
|
|
it("should save the mute state for the audio stream", function() {
|
|
store.setStoreState({"audioMuted": false});
|
|
|
|
dispatcher.dispatch(new sharedActions.SetMute({
|
|
type: "audio",
|
|
enabled: true
|
|
}));
|
|
|
|
expect(store.getStoreState("audioMuted")).eql(false);
|
|
});
|
|
|
|
it("should save the mute state for the video stream", function() {
|
|
store.setStoreState({"videoMuted": true});
|
|
|
|
dispatcher.dispatch(new sharedActions.SetMute({
|
|
type: "video",
|
|
enabled: false
|
|
}));
|
|
|
|
expect(store.getStoreState("videoMuted")).eql(true);
|
|
});
|
|
});
|
|
|
|
describe("#fetchRoomEmailLink", function() {
|
|
it("should request a new call url to the server", function() {
|
|
store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({
|
|
roomOwner: "bob@invalid.tld",
|
|
roomName: "FakeRoomName"
|
|
}));
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.rooms.create);
|
|
sinon.assert.calledWithMatch(fakeMozLoop.rooms.create, {
|
|
roomOwner: "bob@invalid.tld",
|
|
roomName: "FakeRoomName"
|
|
});
|
|
});
|
|
|
|
it("should update the emailLink attribute when the new room url is received",
|
|
function() {
|
|
fakeMozLoop.rooms.create = function(roomData, cb) {
|
|
cb(null, {roomUrl: "http://fake.invalid/"});
|
|
};
|
|
store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({
|
|
roomOwner: "bob@invalid.tld",
|
|
roomName: "FakeRoomName"
|
|
}));
|
|
|
|
expect(store.getStoreState("emailLink")).eql("http://fake.invalid/");
|
|
});
|
|
|
|
it("should trigger an error:emailLink event in case of failure",
|
|
function() {
|
|
var trigger = sandbox.stub(store, "trigger");
|
|
fakeMozLoop.rooms.create = function(roomData, cb) {
|
|
cb(new Error("error"));
|
|
};
|
|
store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({
|
|
roomOwner: "bob@invalid.tld",
|
|
roomName: "FakeRoomName"
|
|
}));
|
|
|
|
sinon.assert.calledOnce(trigger);
|
|
sinon.assert.calledWithExactly(trigger, "error:emailLink");
|
|
});
|
|
});
|
|
|
|
describe("#windowUnload", function() {
|
|
var fakeWebsocket;
|
|
|
|
beforeEach(function() {
|
|
fakeWebsocket = store._websocket = {
|
|
close: sinon.stub(),
|
|
decline: sinon.stub()
|
|
};
|
|
|
|
store.setStoreState({windowId: 42});
|
|
});
|
|
|
|
it("should decline the connection on the websocket for incoming calls if the state is alerting", function() {
|
|
store.setStoreState({
|
|
callState: CALL_STATES.ALERTING,
|
|
outgoing: false
|
|
});
|
|
|
|
store.windowUnload();
|
|
|
|
sinon.assert.calledOnce(fakeWebsocket.decline);
|
|
});
|
|
|
|
it("should disconnect the sdk session", function() {
|
|
store.windowUnload();
|
|
|
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
|
});
|
|
|
|
it("should close the websocket", function() {
|
|
store.windowUnload();
|
|
|
|
sinon.assert.calledOnce(fakeWebsocket.close);
|
|
});
|
|
|
|
it("should clear the call in progress for the backend", function() {
|
|
store.windowUnload();
|
|
|
|
sinon.assert.calledOnce(fakeMozLoop.calls.clearCallInProgress);
|
|
sinon.assert.calledWithExactly(fakeMozLoop.calls.clearCallInProgress, 42);
|
|
});
|
|
});
|
|
|
|
describe("Events", function() {
|
|
describe("Websocket progress", function() {
|
|
beforeEach(function() {
|
|
store.connectCall(
|
|
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
|
|
|
|
sandbox.stub(dispatcher, "dispatch");
|
|
});
|
|
|
|
it("should dispatch a connection failure action on 'terminate' for outgoing calls", function() {
|
|
store.setStoreState({
|
|
outgoing: true
|
|
});
|
|
|
|
store._websocket.trigger("progress", {
|
|
state: WS_STATES.TERMINATED,
|
|
reason: WEBSOCKET_REASONS.REJECT,
|
|
});
|
|
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("name", "connectionFailure"));
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("reason", WEBSOCKET_REASONS.REJECT));
|
|
});
|
|
|
|
it("should dispatch a connection failure action on 'terminate' for incoming calls if the previous state was not 'alerting' or 'init'", function() {
|
|
store.setStoreState({
|
|
outgoing: false
|
|
});
|
|
|
|
store._websocket.trigger("progress", {
|
|
state: WS_STATES.TERMINATED,
|
|
reason: WEBSOCKET_REASONS.CANCEL
|
|
}, WS_STATES.CONNECTING);
|
|
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
|
new sharedActions.ConnectionFailure({
|
|
reason: WEBSOCKET_REASONS.CANCEL
|
|
}));
|
|
});
|
|
|
|
it("should dispatch a cancel call action on 'terminate' for incoming calls if the previous state was 'init'", function() {
|
|
store.setStoreState({
|
|
outgoing: false
|
|
});
|
|
|
|
store._websocket.trigger("progress", {
|
|
state: WS_STATES.TERMINATED,
|
|
reason: WEBSOCKET_REASONS.CANCEL
|
|
}, WS_STATES.INIT);
|
|
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
|
new sharedActions.CancelCall({}));
|
|
});
|
|
|
|
it("should dispatch a cancel call action on 'terminate' for incoming calls if the previous state was 'alerting'", function() {
|
|
store.setStoreState({
|
|
outgoing: false
|
|
});
|
|
|
|
store._websocket.trigger("progress", {
|
|
state: WS_STATES.TERMINATED,
|
|
reason: WEBSOCKET_REASONS.CANCEL
|
|
}, WS_STATES.ALERTING);
|
|
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
|
new sharedActions.CancelCall({}));
|
|
});
|
|
|
|
it("should dispatch a connection progress action on 'alerting'", function() {
|
|
store._websocket.trigger("progress", {state: WS_STATES.ALERTING});
|
|
|
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
|
// Can't use instanceof here, as that matches any action
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("name", "connectionProgress"));
|
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
|
sinon.match.hasOwn("wsState", WS_STATES.ALERTING));
|
|
});
|
|
});
|
|
});
|
|
});
|