SEBWIN-219: Decoupled proxy object creation from BaseProxy and implemented unit tests for the latter.
This commit is contained in:
parent
b206b0d5be
commit
17c068de6f
16 changed files with 366 additions and 48 deletions
|
@ -72,7 +72,7 @@ namespace SafeExamBrowser.Client
|
||||||
text = new Text(logger);
|
text = new Text(logger);
|
||||||
messageBox = new MessageBox(text);
|
messageBox = new MessageBox(text);
|
||||||
uiFactory = new UserInterfaceFactory(text);
|
uiFactory = new UserInterfaceFactory(text);
|
||||||
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ModuleLogger(logger, typeof(RuntimeProxy)));
|
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, typeof(RuntimeProxy)));
|
||||||
|
|
||||||
var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods);
|
var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods);
|
||||||
var processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
|
var processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
|
||||||
|
|
|
@ -17,20 +17,21 @@ namespace SafeExamBrowser.Contracts.Communication
|
||||||
public interface ICommunicationProxy
|
public interface ICommunicationProxy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired when the connection to the proxy was lost, e.g. if a ping request failed or a communication fault occurred.
|
/// Fired when the connection to the host was lost, e.g. if a ping request failed or a communication fault occurred.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event CommunicationEventHandler ConnectionLost;
|
event CommunicationEventHandler ConnectionLost;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to establish a connection. Returns <c>true</c> if the connection has been accepted, otherwise <c>false</c>. If a
|
/// Tries to establish a connection. Returns <c>true</c> if the connection has been accepted, otherwise <c>false</c>. If a
|
||||||
/// connection was successfully established, a ping mechanism will be activated to periodically check the connection status.
|
/// connection was successfully established and the auto-ping flag is set, the connection status will be periodically checked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
|
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
|
||||||
bool Connect(Guid? token = null);
|
bool Connect(Guid? token = null, bool autoPing = true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Terminates an open connection. Returns <c>true</c> if the disconnection has been acknowledged, otherwise <c>false</c>.
|
/// Terminates an open connection. Returns <c>true</c> if the disconnection has been acknowledged, otherwise <c>false</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">If no connection has been established.</exception>
|
||||||
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
|
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
|
||||||
bool Disconnect();
|
bool Disconnect();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Contracts.Communication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A factory to create communication objects for proxies.
|
||||||
|
/// </summary>
|
||||||
|
public interface IProxyObjectFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a communication object (see <see cref="ICommunication"/>) for the specified endpoint address.
|
||||||
|
/// </summary>
|
||||||
|
ICommunication CreateObject(string address);
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,6 +62,7 @@
|
||||||
<Compile Include="Communication\IClientProxy.cs" />
|
<Compile Include="Communication\IClientProxy.cs" />
|
||||||
<Compile Include="Communication\ICommunicationHost.cs" />
|
<Compile Include="Communication\ICommunicationHost.cs" />
|
||||||
<Compile Include="Communication\ICommunicationProxy.cs" />
|
<Compile Include="Communication\ICommunicationProxy.cs" />
|
||||||
|
<Compile Include="Communication\IProxyObjectFactory.cs" />
|
||||||
<Compile Include="Communication\IProxyFactory.cs" />
|
<Compile Include="Communication\IProxyFactory.cs" />
|
||||||
<Compile Include="Communication\IRuntimeHost.cs" />
|
<Compile Include="Communication\IRuntimeHost.cs" />
|
||||||
<Compile Include="Communication\IRuntimeProxy.cs" />
|
<Compile Include="Communication\IRuntimeProxy.cs" />
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using SafeExamBrowser.Contracts.Communication;
|
||||||
|
using SafeExamBrowser.Contracts.Communication.Messages;
|
||||||
|
using SafeExamBrowser.Contracts.Communication.Responses;
|
||||||
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
|
using SafeExamBrowser.Core.Communication;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Core.UnitTests.Communication
|
||||||
|
{
|
||||||
|
internal class BaseProxyImpl : BaseProxy
|
||||||
|
{
|
||||||
|
public BaseProxyImpl(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Connect(Guid? token = null, bool autoPing = false)
|
||||||
|
{
|
||||||
|
return base.Connect(token, autoPing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Disconnect()
|
||||||
|
{
|
||||||
|
return base.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new Response Send(Message message)
|
||||||
|
{
|
||||||
|
return base.Send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
197
SafeExamBrowser.Core.UnitTests/Communication/BaseProxyTests.cs
Normal file
197
SafeExamBrowser.Core.UnitTests/Communication/BaseProxyTests.cs
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.ServiceModel;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Moq;
|
||||||
|
using SafeExamBrowser.Contracts.Communication;
|
||||||
|
using SafeExamBrowser.Contracts.Communication.Messages;
|
||||||
|
using SafeExamBrowser.Contracts.Communication.Responses;
|
||||||
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Core.UnitTests.Communication
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class BaseProxyTests
|
||||||
|
{
|
||||||
|
private Mock<IProxyObjectFactory> proxyObjectFactory;
|
||||||
|
private Mock<ILogger> logger;
|
||||||
|
private BaseProxyImpl sut;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
proxyObjectFactory = new Mock<IProxyObjectFactory>();
|
||||||
|
logger = new Mock<ILogger>();
|
||||||
|
|
||||||
|
sut = new BaseProxyImpl("net.pipe://some/address/here", proxyObjectFactory.Object, logger.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustConnectCorrectly()
|
||||||
|
{
|
||||||
|
var proxy = new Mock<ICommunication>();
|
||||||
|
var response = new ConnectionResponse
|
||||||
|
{
|
||||||
|
CommunicationToken = Guid.NewGuid(),
|
||||||
|
ConnectionEstablished = true
|
||||||
|
};
|
||||||
|
|
||||||
|
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
|
||||||
|
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
|
||||||
|
|
||||||
|
var token = Guid.NewGuid();
|
||||||
|
var connected = sut.Connect(token);
|
||||||
|
|
||||||
|
proxy.Verify(p => p.Connect(token), Times.Once);
|
||||||
|
proxyObjectFactory.Verify(f => f.CreateObject(It.IsAny<string>()), Times.Once);
|
||||||
|
|
||||||
|
Assert.IsTrue(connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustDisconnectCorrectly()
|
||||||
|
{
|
||||||
|
var proxy = new Mock<ICommunication>();
|
||||||
|
var connectionResponse = new ConnectionResponse
|
||||||
|
{
|
||||||
|
CommunicationToken = Guid.NewGuid(),
|
||||||
|
ConnectionEstablished = true
|
||||||
|
};
|
||||||
|
var disconnectionResponse = new DisconnectionResponse
|
||||||
|
{
|
||||||
|
ConnectionTerminated = true
|
||||||
|
};
|
||||||
|
|
||||||
|
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(connectionResponse);
|
||||||
|
proxy.Setup(p => p.Disconnect(It.IsAny<DisconnectionMessage>())).Returns(disconnectionResponse);
|
||||||
|
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Opened);
|
||||||
|
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
|
||||||
|
|
||||||
|
var token = Guid.NewGuid();
|
||||||
|
var connected = sut.Connect(token);
|
||||||
|
var disconnected = sut.Disconnect();
|
||||||
|
|
||||||
|
proxy.Verify(p => p.Disconnect(It.Is<DisconnectionMessage>(m => m.CommunicationToken == connectionResponse.CommunicationToken)), Times.Once);
|
||||||
|
|
||||||
|
Assert.IsTrue(connected);
|
||||||
|
Assert.IsTrue(disconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustHandleConnectionRefusalCorrectly()
|
||||||
|
{
|
||||||
|
var proxy = new Mock<ICommunication>();
|
||||||
|
var response = new ConnectionResponse
|
||||||
|
{
|
||||||
|
CommunicationToken = Guid.NewGuid(),
|
||||||
|
ConnectionEstablished = false
|
||||||
|
};
|
||||||
|
|
||||||
|
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
|
||||||
|
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
|
||||||
|
|
||||||
|
var token = Guid.NewGuid();
|
||||||
|
var connected = sut.Connect(token);
|
||||||
|
|
||||||
|
proxy.Verify(p => p.Connect(token), Times.Once);
|
||||||
|
proxyObjectFactory.Verify(f => f.CreateObject(It.IsAny<string>()), Times.Once);
|
||||||
|
|
||||||
|
Assert.IsFalse(connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(InvalidOperationException))]
|
||||||
|
public void MustFailToDisconnectIfNotConnected()
|
||||||
|
{
|
||||||
|
sut.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(CommunicationException))]
|
||||||
|
public void MustFailToDisconnectIfChannelNotOpen()
|
||||||
|
{
|
||||||
|
var proxy = new Mock<ICommunication>();
|
||||||
|
var response = new ConnectionResponse
|
||||||
|
{
|
||||||
|
CommunicationToken = Guid.NewGuid(),
|
||||||
|
ConnectionEstablished = true
|
||||||
|
};
|
||||||
|
|
||||||
|
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
|
||||||
|
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Faulted);
|
||||||
|
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
|
||||||
|
|
||||||
|
var token = Guid.NewGuid();
|
||||||
|
|
||||||
|
sut.Connect(token);
|
||||||
|
sut.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(InvalidOperationException))]
|
||||||
|
public void MustFailToSendIfNotConnected()
|
||||||
|
{
|
||||||
|
sut.Send(new Mock<Message>().Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(CommunicationException))]
|
||||||
|
public void MustFailToSendIfChannelNotOpen()
|
||||||
|
{
|
||||||
|
var proxy = new Mock<ICommunication>();
|
||||||
|
var response = new ConnectionResponse
|
||||||
|
{
|
||||||
|
CommunicationToken = Guid.NewGuid(),
|
||||||
|
ConnectionEstablished = true
|
||||||
|
};
|
||||||
|
|
||||||
|
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
|
||||||
|
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Faulted);
|
||||||
|
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
|
||||||
|
|
||||||
|
var token = Guid.NewGuid();
|
||||||
|
|
||||||
|
sut.Connect(token);
|
||||||
|
sut.Send(new Mock<Message>().Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(ArgumentNullException))]
|
||||||
|
public void MustNotAllowSendingNull()
|
||||||
|
{
|
||||||
|
sut.Send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MustSendCorrectly()
|
||||||
|
{
|
||||||
|
var proxy = new Mock<ICommunication>();
|
||||||
|
var connectionResponse = new ConnectionResponse
|
||||||
|
{
|
||||||
|
CommunicationToken = Guid.NewGuid(),
|
||||||
|
ConnectionEstablished = true
|
||||||
|
};
|
||||||
|
var message = new SimpleMessage(SimpleMessagePurport.Authenticate);
|
||||||
|
var response = new Mock<Response>();
|
||||||
|
|
||||||
|
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(connectionResponse);
|
||||||
|
proxy.Setup(p => p.Send(message)).Returns(response.Object);
|
||||||
|
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Opened);
|
||||||
|
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
|
||||||
|
|
||||||
|
var token = Guid.NewGuid();
|
||||||
|
var connected = sut.Connect(token);
|
||||||
|
var received = sut.Send(message);
|
||||||
|
|
||||||
|
Assert.AreEqual(response.Object, received);
|
||||||
|
Assert.AreEqual(connectionResponse.CommunicationToken, message.CommunicationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,6 +69,7 @@
|
||||||
<HintPath>..\packages\Moq.4.8.1\lib\net45\Moq.dll</HintPath>
|
<HintPath>..\packages\Moq.4.8.1\lib\net45\Moq.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.ServiceModel" />
|
||||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
<Reference Include="System.Threading.Tasks.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.4.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
|
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.4.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
@ -83,6 +84,8 @@
|
||||||
<Compile Include="Behaviour\OperationModel\I18nOperationTests.cs" />
|
<Compile Include="Behaviour\OperationModel\I18nOperationTests.cs" />
|
||||||
<Compile Include="Behaviour\OperationModel\DelegateOperationTests.cs" />
|
<Compile Include="Behaviour\OperationModel\DelegateOperationTests.cs" />
|
||||||
<Compile Include="Behaviour\OperationModel\OperationSequenceTests.cs" />
|
<Compile Include="Behaviour\OperationModel\OperationSequenceTests.cs" />
|
||||||
|
<Compile Include="Communication\BaseProxyImpl.cs" />
|
||||||
|
<Compile Include="Communication\BaseProxyTests.cs" />
|
||||||
<Compile Include="I18n\TextTests.cs" />
|
<Compile Include="I18n\TextTests.cs" />
|
||||||
<Compile Include="I18n\XmlTextResourceTests.cs" />
|
<Compile Include="I18n\XmlTextResourceTests.cs" />
|
||||||
<Compile Include="Logging\DefaultLogFormatterTests.cs" />
|
<Compile Include="Logging\DefaultLogFormatterTests.cs" />
|
||||||
|
|
|
@ -17,8 +17,7 @@ using SafeExamBrowser.Contracts.Logging;
|
||||||
namespace SafeExamBrowser.Core.Communication
|
namespace SafeExamBrowser.Core.Communication
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base implementation of an <see cref="ICommunicationProxy"/>. Automatically starts a ping mechanism with a timeout of
|
/// Base implementation of an <see cref="ICommunicationProxy"/>.
|
||||||
/// <see cref="ONE_MINUTE"/> once a connection was established.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseProxy : ICommunicationProxy
|
public abstract class BaseProxy : ICommunicationProxy
|
||||||
{
|
{
|
||||||
|
@ -26,7 +25,8 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
private static readonly object @lock = new object();
|
private static readonly object @lock = new object();
|
||||||
|
|
||||||
private string address;
|
private string address;
|
||||||
private ICommunication channel;
|
private ICommunication proxy;
|
||||||
|
private IProxyObjectFactory factory;
|
||||||
private Guid? communicationToken;
|
private Guid? communicationToken;
|
||||||
private Timer timer;
|
private Timer timer;
|
||||||
|
|
||||||
|
@ -34,31 +34,34 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
|
|
||||||
public event CommunicationEventHandler ConnectionLost;
|
public event CommunicationEventHandler ConnectionLost;
|
||||||
|
|
||||||
public BaseProxy(string address, ILogger logger)
|
public BaseProxy(string address, IProxyObjectFactory factory, ILogger logger)
|
||||||
{
|
{
|
||||||
this.address = address;
|
this.address = address;
|
||||||
|
this.factory = factory;
|
||||||
this.Logger = logger;
|
this.Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool Connect(Guid? token = null)
|
public virtual bool Connect(Guid? token = null, bool autoPing = true)
|
||||||
{
|
{
|
||||||
var endpoint = new EndpointAddress(address);
|
proxy = factory.CreateObject(address);
|
||||||
|
|
||||||
channel = ChannelFactory<ICommunication>.CreateChannel(new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), endpoint);
|
if (proxy is ICommunicationObject communicationObject)
|
||||||
(channel as ICommunicationObject).Closed += BaseProxy_Closed;
|
{
|
||||||
(channel as ICommunicationObject).Closing += BaseProxy_Closing;
|
communicationObject.Closed += BaseProxy_Closed;
|
||||||
(channel as ICommunicationObject).Faulted += BaseProxy_Faulted;
|
communicationObject.Closing += BaseProxy_Closing;
|
||||||
(channel as ICommunicationObject).Opened += BaseProxy_Opened;
|
communicationObject.Faulted += BaseProxy_Faulted;
|
||||||
(channel as ICommunicationObject).Opening += BaseProxy_Opening;
|
communicationObject.Opened += BaseProxy_Opened;
|
||||||
|
communicationObject.Opening += BaseProxy_Opening;
|
||||||
|
}
|
||||||
|
|
||||||
Logger.Debug($"Trying to connect to endpoint '{address}'{(token.HasValue ? $" with authentication token '{token}'" : string.Empty)}...");
|
Logger.Debug($"Trying to connect to endpoint '{address}'{(token.HasValue ? $" with authentication token '{token}'" : string.Empty)}...");
|
||||||
|
|
||||||
var response = channel.Connect(token);
|
var response = proxy.Connect(token);
|
||||||
|
|
||||||
communicationToken = response.CommunicationToken;
|
communicationToken = response.CommunicationToken;
|
||||||
Logger.Debug($"Connection was {(response.ConnectionEstablished ? "established" : "refused")}.");
|
Logger.Debug($"Connection was {(response.ConnectionEstablished ? "established" : "refused")}.");
|
||||||
|
|
||||||
if (response.ConnectionEstablished)
|
if (response.ConnectionEstablished && autoPing)
|
||||||
{
|
{
|
||||||
StartAutoPing();
|
StartAutoPing();
|
||||||
}
|
}
|
||||||
|
@ -72,41 +75,64 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
StopAutoPing();
|
StopAutoPing();
|
||||||
|
|
||||||
var message = new DisconnectionMessage { CommunicationToken = communicationToken.Value };
|
var message = new DisconnectionMessage { CommunicationToken = communicationToken.Value };
|
||||||
var response = channel.Disconnect(message);
|
var response = proxy.Disconnect(message);
|
||||||
|
|
||||||
Logger.Debug($"{(response.ConnectionTerminated ? "Disconnected" : "Failed to disconnect")} from {address}.");
|
Logger.Debug($"{(response.ConnectionTerminated ? "Disconnected" : "Failed to disconnect")} from {address}.");
|
||||||
|
|
||||||
return response.ConnectionTerminated;
|
return response.ConnectionTerminated;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response Send(Message message)
|
/// <summary>
|
||||||
|
/// Sends the given message, optionally returning a response. If no response is expected, <c>null</c> will be returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ArgumentNullException">If the given message is <c>null</c>.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">If no connection has been established yet.</exception>
|
||||||
|
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
|
||||||
|
protected virtual Response Send(Message message)
|
||||||
{
|
{
|
||||||
|
if (message is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
FailIfNotConnected(nameof(Send));
|
FailIfNotConnected(nameof(Send));
|
||||||
|
|
||||||
message.CommunicationToken = communicationToken.Value;
|
message.CommunicationToken = communicationToken.Value;
|
||||||
|
|
||||||
var response = channel.Send(message);
|
var response = proxy.Send(message);
|
||||||
|
|
||||||
Logger.Debug($"Sent message '{ToString(message)}', got response '{ToString(response)}'.");
|
Logger.Debug($"Sent message '{ToString(message)}', got response '{ToString(response)}'.");
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the given purport as <see cref="SimpleMessage"/>.
|
||||||
|
/// </summary>
|
||||||
protected Response Send(SimpleMessagePurport purport)
|
protected Response Send(SimpleMessagePurport purport)
|
||||||
{
|
{
|
||||||
return Send(new SimpleMessage(purport));
|
return Send(new SimpleMessage(purport));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the given response is a <see cref="SimpleResponse"/> with purport <see cref="SimpleResponsePurport.Acknowledged"/>.
|
||||||
|
/// </summary>
|
||||||
protected bool IsAcknowledged(Response response)
|
protected bool IsAcknowledged(Response response)
|
||||||
{
|
{
|
||||||
return response is SimpleResponse simpleResponse && simpleResponse.Purport == SimpleResponsePurport.Acknowledged;
|
return response is SimpleResponse simpleResponse && simpleResponse.Purport == SimpleResponsePurport.Acknowledged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the string representation of the given <see cref="Message"/>, or indicates that a message is <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
protected string ToString(Message message)
|
protected string ToString(Message message)
|
||||||
{
|
{
|
||||||
return message != null ? message.ToString() : "<null>";
|
return message != null ? message.ToString() : "<null>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// etrieves the string representation of the given <see cref="Response"/>, or indicates that a response is <c>null</c>.
|
||||||
|
/// </summary>
|
||||||
protected string ToString(Response response)
|
protected string ToString(Response response)
|
||||||
{
|
{
|
||||||
return response != null ? response.ToString() : "<null>";
|
return response != null ? response.ToString() : "<null>";
|
||||||
|
@ -144,7 +170,7 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
throw new InvalidOperationException($"Cannot perform '{operationName}' before being connected to endpoint!");
|
throw new InvalidOperationException($"Cannot perform '{operationName}' before being connected to endpoint!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel == null || (channel as ICommunicationObject).State != CommunicationState.Opened)
|
if (proxy == null || (proxy as ICommunicationObject)?.State != CommunicationState.Opened)
|
||||||
{
|
{
|
||||||
throw new CommunicationException($"Tried to perform {operationName}, but channel was {GetChannelState()}!");
|
throw new CommunicationException($"Tried to perform {operationName}, but channel was {GetChannelState()}!");
|
||||||
}
|
}
|
||||||
|
@ -152,7 +178,7 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
|
|
||||||
private string GetChannelState()
|
private string GetChannelState()
|
||||||
{
|
{
|
||||||
return channel == null ? "null" : $"in state '{(channel as ICommunicationObject).State}'";
|
return proxy == null ? "null" : $"in state '{(proxy as ICommunicationObject)?.State}'";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartAutoPing()
|
private void StartAutoPing()
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ClientProxy : BaseProxy, IClientProxy
|
public class ClientProxy : BaseProxy, IClientProxy
|
||||||
{
|
{
|
||||||
public ClientProxy(string address, ILogger logger) : base(address, logger)
|
public ClientProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
SafeExamBrowser.Core/Communication/ProxyObjectFactory.cs
Normal file
27
SafeExamBrowser.Core/Communication/ProxyObjectFactory.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System.ServiceModel;
|
||||||
|
using SafeExamBrowser.Contracts.Communication;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Core.Communication
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation of the <see cref="IProxyObjectFactory"/> utilizing WCF (<see cref="ChannelFactory"/>).
|
||||||
|
/// </summary>
|
||||||
|
public class ProxyObjectFactory : IProxyObjectFactory
|
||||||
|
{
|
||||||
|
public ICommunication CreateObject(string address)
|
||||||
|
{
|
||||||
|
var endpoint = new EndpointAddress(address);
|
||||||
|
var channel = ChannelFactory<ICommunication>.CreateChannel(new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), endpoint);
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RuntimeProxy : BaseProxy, IRuntimeProxy
|
public class RuntimeProxy : BaseProxy, IRuntimeProxy
|
||||||
{
|
{
|
||||||
public RuntimeProxy(string address, ILogger logger) : base(address, logger)
|
public RuntimeProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,18 +20,18 @@ namespace SafeExamBrowser.Core.Communication
|
||||||
{
|
{
|
||||||
public bool Ignore { private get; set; }
|
public bool Ignore { private get; set; }
|
||||||
|
|
||||||
public ServiceProxy(string address, ILogger logger) : base(address, logger)
|
public ServiceProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Connect(Guid? token = null)
|
public override bool Connect(Guid? token = null, bool autoPing = true)
|
||||||
{
|
{
|
||||||
if (IgnoreOperation(nameof(Connect)))
|
if (IgnoreOperation(nameof(Connect)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.Connect();
|
return base.Connect(autoPing: autoPing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Disconnect()
|
public override bool Disconnect()
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
<Compile Include="Communication\BaseProxy.cs" />
|
<Compile Include="Communication\BaseProxy.cs" />
|
||||||
<Compile Include="Communication\BaseHost.cs" />
|
<Compile Include="Communication\BaseHost.cs" />
|
||||||
<Compile Include="Communication\ClientProxy.cs" />
|
<Compile Include="Communication\ClientProxy.cs" />
|
||||||
|
<Compile Include="Communication\ProxyObjectFactory.cs" />
|
||||||
<Compile Include="Communication\RuntimeProxy.cs" />
|
<Compile Include="Communication\RuntimeProxy.cs" />
|
||||||
<Compile Include="Communication\ServiceProxy.cs" />
|
<Compile Include="Communication\ServiceProxy.cs" />
|
||||||
<Compile Include="Logging\DefaultLogFormatter.cs" />
|
<Compile Include="Logging\DefaultLogFormatter.cs" />
|
||||||
|
|
|
@ -45,38 +45,38 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustConnectToService()
|
public void MustConnectToService()
|
||||||
{
|
{
|
||||||
service.Setup(s => s.Connect(null)).Returns(true);
|
service.Setup(s => s.Connect(null, true)).Returns(true);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
|
|
||||||
service.Setup(s => s.Connect(null)).Returns(true);
|
service.Setup(s => s.Connect(null, true)).Returns(true);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
|
|
||||||
service.Verify(s => s.Connect(null), Times.Exactly(2));
|
service.Verify(s => s.Connect(null, true), Times.Exactly(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustNotFailIfServiceNotAvailable()
|
public void MustNotFailIfServiceNotAvailable()
|
||||||
{
|
{
|
||||||
service.Setup(s => s.Connect(null)).Returns(false);
|
service.Setup(s => s.Connect(null, true)).Returns(false);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
|
|
||||||
service.Setup(s => s.Connect(null)).Returns(false);
|
service.Setup(s => s.Connect(null, true)).Returns(false);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
|
|
||||||
service.Setup(s => s.Connect(null)).Throws<Exception>();
|
service.Setup(s => s.Connect(null, true)).Throws<Exception>();
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
|
|
||||||
service.Setup(s => s.Connect(null)).Throws<Exception>();
|
service.Setup(s => s.Connect(null, true)).Throws<Exception>();
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
|
@ -85,7 +85,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustFailIfServiceMandatoryAndNotAvailable()
|
public void MustFailIfServiceMandatoryAndNotAvailable()
|
||||||
{
|
{
|
||||||
service.Setup(s => s.Connect(null)).Returns(false);
|
service.Setup(s => s.Connect(null, true)).Returns(false);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
||||||
|
|
||||||
var result = sut.Perform();
|
var result = sut.Perform();
|
||||||
|
@ -96,7 +96,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustNotFailIfServiceOptionalAndNotAvailable()
|
public void MustNotFailIfServiceOptionalAndNotAvailable()
|
||||||
{
|
{
|
||||||
service.Setup(s => s.Connect(null)).Returns(false);
|
service.Setup(s => s.Connect(null, true)).Returns(false);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
||||||
|
|
||||||
var result = sut.Perform();
|
var result = sut.Perform();
|
||||||
|
@ -109,13 +109,13 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustDisconnectWhenReverting()
|
public void MustDisconnectWhenReverting()
|
||||||
{
|
{
|
||||||
service.Setup(s => s.Connect(null)).Returns(true);
|
service.Setup(s => s.Connect(null, true)).Returns(true);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
sut.Revert();
|
sut.Revert();
|
||||||
|
|
||||||
service.Setup(s => s.Connect(null)).Returns(true);
|
service.Setup(s => s.Connect(null, true)).Returns(true);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
|
@ -127,7 +127,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustNotFailWhenDisconnecting()
|
public void MustNotFailWhenDisconnecting()
|
||||||
{
|
{
|
||||||
service.Setup(s => s.Connect(null)).Returns(true);
|
service.Setup(s => s.Connect(null, true)).Returns(true);
|
||||||
service.Setup(s => s.Disconnect()).Throws<Exception>();
|
service.Setup(s => s.Disconnect()).Throws<Exception>();
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
||||||
|
|
||||||
|
@ -140,25 +140,25 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustNotDisconnnectIfNotAvailable()
|
public void MustNotDisconnnectIfNotAvailable()
|
||||||
{
|
{
|
||||||
service.Setup(s => s.Connect(null)).Returns(false);
|
service.Setup(s => s.Connect(null, true)).Returns(false);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
sut.Revert();
|
sut.Revert();
|
||||||
|
|
||||||
service.Setup(s => s.Connect(null)).Returns(false);
|
service.Setup(s => s.Connect(null, true)).Returns(false);
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
sut.Revert();
|
sut.Revert();
|
||||||
|
|
||||||
service.Setup(s => s.Connect(null)).Throws<Exception>();
|
service.Setup(s => s.Connect(null, true)).Throws<Exception>();
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
sut.Revert();
|
sut.Revert();
|
||||||
|
|
||||||
service.Setup(s => s.Connect(null)).Throws<Exception>();
|
service.Setup(s => s.Connect(null, true)).Throws<Exception>();
|
||||||
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
|
||||||
|
|
||||||
sut.Perform();
|
sut.Perform();
|
||||||
|
|
|
@ -15,16 +15,18 @@ namespace SafeExamBrowser.Runtime.Communication
|
||||||
{
|
{
|
||||||
internal class ProxyFactory : IProxyFactory
|
internal class ProxyFactory : IProxyFactory
|
||||||
{
|
{
|
||||||
|
private IProxyObjectFactory factory;
|
||||||
private ILogger logger;
|
private ILogger logger;
|
||||||
|
|
||||||
public ProxyFactory(ILogger logger)
|
public ProxyFactory(IProxyObjectFactory factory, ILogger logger)
|
||||||
{
|
{
|
||||||
|
this.factory = factory;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IClientProxy CreateClientProxy(string address)
|
public IClientProxy CreateClientProxy(string address)
|
||||||
{
|
{
|
||||||
return new ClientProxy(address, new ModuleLogger(logger, typeof(ClientProxy)));
|
return new ClientProxy(address, factory, new ModuleLogger(logger, typeof(ClientProxy)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,9 +50,9 @@ namespace SafeExamBrowser.Runtime
|
||||||
var uiFactory = new UserInterfaceFactory(text);
|
var uiFactory = new UserInterfaceFactory(text);
|
||||||
var desktop = new Desktop(new ModuleLogger(logger, typeof(Desktop)));
|
var desktop = new Desktop(new ModuleLogger(logger, typeof(Desktop)));
|
||||||
var processFactory = new ProcessFactory(desktop, new ModuleLogger(logger, typeof(ProcessFactory)));
|
var processFactory = new ProcessFactory(desktop, new ModuleLogger(logger, typeof(ProcessFactory)));
|
||||||
var proxyFactory = new ProxyFactory(logger);
|
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger);
|
||||||
var runtimeHost = new RuntimeHost(runtimeInfo.RuntimeAddress, configuration, new ModuleLogger(logger, typeof(RuntimeHost)));
|
var runtimeHost = new RuntimeHost(runtimeInfo.RuntimeAddress, configuration, new ModuleLogger(logger, typeof(RuntimeHost)));
|
||||||
var serviceProxy = new ServiceProxy(runtimeInfo.ServiceAddress, new ModuleLogger(logger, typeof(ServiceProxy)));
|
var serviceProxy = new ServiceProxy(runtimeInfo.ServiceAddress, new ProxyObjectFactory(), new ModuleLogger(logger, typeof(ServiceProxy)));
|
||||||
var sessionController = new SessionController(configuration, logger, processFactory, proxyFactory, runtimeHost, serviceProxy);
|
var sessionController = new SessionController(configuration, logger, processFactory, proxyFactory, runtimeHost, serviceProxy);
|
||||||
|
|
||||||
var bootstrapOperations = new Queue<IOperation>();
|
var bootstrapOperations = new Queue<IOperation>();
|
||||||
|
|
Loading…
Reference in a new issue