SEBWIN-219: Extracted creation of proxy and host objects from base implementations and started implementing unit tests for hosts.

This commit is contained in:
dbuechel 2018-03-16 15:46:53 +01:00
parent e4940383fb
commit 3a4f189916
21 changed files with 448 additions and 66 deletions

View file

@ -24,7 +24,7 @@ namespace SafeExamBrowser.Client.Communication
public event CommunicationEventHandler Shutdown;
public ClientHost(string address, ILogger logger, int processId) : base(address, logger)
public ClientHost(string address, IHostObjectFactory factory, ILogger logger, int processId) : base(address, factory, logger)
{
this.processId = processId;
}

View file

@ -26,6 +26,7 @@ using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.WindowsApi;
using SafeExamBrowser.Core.Behaviour.OperationModel;
using SafeExamBrowser.Core.Communication.Hosts;
using SafeExamBrowser.Core.Communication.Proxies;
using SafeExamBrowser.Core.I18n;
using SafeExamBrowser.Core.Logging;
@ -159,7 +160,8 @@ namespace SafeExamBrowser.Client
private IOperation BuildCommunicationHostOperation()
{
var processId = Process.GetCurrentProcess().Id;
var host = new ClientHost(configuration.RuntimeInfo.ClientAddress, new ModuleLogger(logger, typeof(ClientHost)), processId);
var factory = new HostObjectFactory();
var host = new ClientHost(configuration.RuntimeInfo.ClientAddress, factory, new ModuleLogger(logger, typeof(ClientHost)), processId);
var operation = new CommunicationOperation(host, logger);
clientHost = host;

View file

@ -0,0 +1,19 @@
/*
* 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;
namespace SafeExamBrowser.Contracts.Communication.Hosts
{
/// <summary>
/// The host object to be used in communication hosts.
/// </summary>
public interface IHostObject : ICommunicationObject
{
}
}

View file

@ -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.Hosts
{
/// <summary>
/// A factory to create host objects for communication hosts.
/// </summary>
public interface IHostObjectFactory
{
/// <summary>
/// Utilizes the given communication object to create a host object (see <see cref="IHostObject"/>) for the specified endpoint address.
/// </summary>
IHostObject CreateObject(string address, ICommunication communicationObject);
}
}

View file

@ -0,0 +1,19 @@
/*
* 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;
namespace SafeExamBrowser.Contracts.Communication.Proxies
{
/// <summary>
/// The communication object to be used in an <see cref="ICommunicationProxy"/>.
/// </summary>
public interface IProxyObject : ICommunication, ICommunicationObject
{
}
}

View file

@ -9,13 +9,13 @@
namespace SafeExamBrowser.Contracts.Communication.Proxies
{
/// <summary>
/// A factory to create communication objects for proxies.
/// A factory to create proxy objects for communication proxies.
/// </summary>
public interface IProxyObjectFactory
{
/// <summary>
/// Creates a communication object (see <see cref="ICommunication"/>) for the specified endpoint address.
/// Creates a proxy object (see <see cref="IProxyObject"/>) for the specified endpoint address.
/// </summary>
ICommunication CreateObject(string address);
IProxyObject CreateObject(string address);
}
}

View file

@ -58,10 +58,13 @@
<Compile Include="Behaviour\OperationModel\IOperationSequence.cs" />
<Compile Include="Behaviour\OperationModel\OperationResult.cs" />
<Compile Include="Communication\Hosts\IClientHost.cs" />
<Compile Include="Communication\Hosts\IHostObject.cs" />
<Compile Include="Communication\Hosts\IHostObjectFactory.cs" />
<Compile Include="Communication\ICommunication.cs" />
<Compile Include="Communication\Proxies\IClientProxy.cs" />
<Compile Include="Communication\ICommunicationHost.cs" />
<Compile Include="Communication\ICommunicationProxy.cs" />
<Compile Include="Communication\Proxies\IProxyObject.cs" />
<Compile Include="Communication\Proxies\IProxyObjectFactory.cs" />
<Compile Include="Communication\Proxies\IProxyFactory.cs" />
<Compile Include="Communication\Hosts\IRuntimeHost.cs" />

View file

@ -0,0 +1,53 @@
/*
* 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.Data;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Hosts;
namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts
{
internal class BaseHostImpl : BaseHost
{
public Func<Guid?, bool> OnConnectStub { get; set; }
public Action OnDisconnectStub { get; set; }
public Func<Message, Response> OnReceiveStub { get; set; }
public Func<SimpleMessagePurport, Response> OnReceiveSimpleMessageStub { get; set; }
public BaseHostImpl(string address, IHostObjectFactory factory, ILogger logger) : base(address, factory, logger)
{
}
public Guid? GetCommunicationToken()
{
return CommunicationToken;
}
protected override bool OnConnect(Guid? token)
{
return OnConnectStub?.Invoke(token) == true;
}
protected override void OnDisconnect()
{
OnDisconnectStub?.Invoke();
}
protected override Response OnReceive(Message message)
{
return OnReceiveStub?.Invoke(message);
}
protected override Response OnReceive(SimpleMessagePurport message)
{
return OnReceiveSimpleMessageStub?.Invoke(message);
}
}
}

View file

@ -0,0 +1,227 @@
/*
* 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 System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts
{
[TestClass]
public class BaseHostTests
{
private Mock<IHostObject> hostObject;
private Mock<IHostObjectFactory> hostObjectFactory;
private Mock<ILogger> logger;
private BaseHostImpl sut;
[TestInitialize]
public void Initialize()
{
hostObject = new Mock<IHostObject>();
hostObjectFactory = new Mock<IHostObjectFactory>();
logger = new Mock<ILogger>();
hostObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>(), It.IsAny<ICommunication>())).Returns(hostObject.Object);
sut = new BaseHostImpl("net.pipe://some/address/here", hostObjectFactory.Object, logger.Object);
}
[TestMethod]
public void MustCorrectlyStartHost()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
hostObject.Setup(h => h.Open()).Callback(() => threadId = Thread.CurrentThread.ManagedThreadId);
sut.Start();
hostObjectFactory.Verify(f => f.CreateObject(It.IsAny<string>(), sut), Times.Once);
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
}
[TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustCorrectlyHandleStartupException()
{
hostObject.Setup(h => h.Open()).Throws<Exception>();
sut.Start();
}
[TestMethod]
public void MustCorrectlyStopHost()
{
sut.Start();
sut.Stop();
hostObject.Verify(h => h.Close(), Times.Once);
}
[TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustCorrectlyHandleShutdownException()
{
hostObject.Setup(h => h.Close()).Throws<Exception>();
sut.Start();
sut.Stop();
}
[TestMethod]
public void MustNotFailToStopIfNotRunning()
{
sut.Stop();
sut.Stop();
sut.Stop();
}
[TestMethod]
public void MustNotFailToEvaluateIsRunningIfNotRunning()
{
var running = sut.IsRunning;
Assert.IsFalse(running);
}
[TestMethod]
public void MustCorrectlyIndicateWhetherHostIsRunning()
{
hostObject.SetupGet(h => h.State).Returns(CommunicationState.Faulted);
sut.Start();
Assert.IsFalse(sut.IsRunning);
hostObject.SetupGet(h => h.State).Returns(CommunicationState.Opened);
Assert.IsTrue(sut.IsRunning);
}
[TestMethod]
public void MustCorrectlyHandleConnectionRequest()
{
var token = Guid.NewGuid();
var receivedToken = default(Guid?);
sut.OnConnectStub = (t) =>
{
receivedToken = t;
return true;
};
var response = sut.Connect(token);
Assert.IsTrue(response.ConnectionEstablished);
Assert.AreEqual(token, receivedToken);
Assert.AreEqual(sut.GetCommunicationToken(), response.CommunicationToken);
}
[TestMethod]
public void MustCorrectlyHandleDeniedConnectionRequest()
{
var token = Guid.NewGuid();
var receivedToken = default(Guid?);
sut.OnConnectStub = (t) =>
{
receivedToken = t;
return false;
};
var response = sut.Connect(token);
Assert.IsFalse(response.ConnectionEstablished);
Assert.AreEqual(token, receivedToken);
Assert.IsNull(sut.GetCommunicationToken());
Assert.IsNull(response.CommunicationToken);
}
[TestMethod]
public void MustCorrectlyHandleDisconnectionRequest()
{
var message = new DisconnectionMessage();
var disconnected = false;
sut.OnConnectStub = (t) => { return true; };
sut.OnDisconnectStub = () => disconnected = true;
sut.Connect();
message.CommunicationToken = sut.GetCommunicationToken().Value;
var response = sut.Disconnect(message);
Assert.IsTrue(disconnected);
Assert.IsTrue(response.ConnectionTerminated);
Assert.IsNull(sut.GetCommunicationToken());
}
[TestMethod]
public void MustCorrectlyHandleUnauthorizedDisconnectionRequest()
{
var disconnected = false;
sut.OnConnectStub = (t) => { return true; };
sut.OnDisconnectStub = () => disconnected = true;
sut.Connect();
var response = sut.Disconnect(new DisconnectionMessage());
Assert.IsFalse(disconnected);
Assert.IsFalse(response.ConnectionTerminated);
Assert.IsNotNull(sut.GetCommunicationToken());
}
[TestMethod]
public void MustCorrectlyHandleUnauthorizedTransmission()
{
var received = false;
var simpleReceived = false;
sut.OnReceiveStub = (m) => { received = true; return null; };
sut.OnReceiveSimpleMessageStub = (m) => { simpleReceived = true; return null; };
var response = sut.Send(new DisconnectionMessage());
Assert.IsFalse(received);
Assert.IsFalse(simpleReceived);
Assert.IsInstanceOfType(response, typeof(SimpleResponse));
Assert.AreEqual(SimpleResponsePurport.Unauthorized, (response as SimpleResponse)?.Purport);
}
[TestMethod]
public void MustCorrectlyHandlePingMessage()
{
// TODO
Assert.Fail();
}
[TestMethod]
public void MustCorrectlyReceiveSimpleMessage()
{
// TODO
Assert.Fail();
}
[TestMethod]
public void MustCorrectlyReceiveMessage()
{
// TODO
Assert.Fail();
}
}
}

View file

@ -10,7 +10,6 @@ using System;
using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging;
@ -36,7 +35,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[TestMethod]
public void MustConnectCorrectly()
{
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var response = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -58,7 +57,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[TestMethod]
public void MustDisconnectCorrectly()
{
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var connectionResponse = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -71,7 +70,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
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);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
var token = Guid.NewGuid();
@ -87,7 +86,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[TestMethod]
public void MustHandleConnectionRefusalCorrectly()
{
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var response = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -117,7 +116,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[ExpectedException(typeof(CommunicationException))]
public void MustFailToDisconnectIfChannelNotOpen()
{
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var response = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -125,7 +124,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
};
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Faulted);
proxy.Setup(o => o.State).Returns(CommunicationState.Faulted);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
var token = Guid.NewGuid();
@ -145,7 +144,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[ExpectedException(typeof(CommunicationException))]
public void MustFailToSendIfChannelNotOpen()
{
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var response = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -153,7 +152,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
};
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Faulted);
proxy.Setup(o => o.State).Returns(CommunicationState.Faulted);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
var token = Guid.NewGuid();
@ -172,7 +171,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[TestMethod]
public void MustSendCorrectly()
{
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var connectionResponse = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -183,7 +182,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
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);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
var token = Guid.NewGuid();
@ -197,7 +196,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[TestMethod]
public void MustSendSimpleMessageCorrectly()
{
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var connectionResponse = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -208,7 +207,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(connectionResponse);
proxy.Setup(p => p.Send(It.IsAny<Message>())).Returns(response.Object);
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Opened);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
var token = Guid.NewGuid();
@ -255,7 +254,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[TestMethod]
public void TestConnectionMustPingHost()
{
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var connectionResponse = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -264,7 +263,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(connectionResponse);
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Ping))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged));
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Opened);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
var token = Guid.NewGuid();
@ -279,7 +278,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
public void TestConnectionMustInvokeConnectionLostEvent()
{
var lost = false;
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var connectionResponse = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -290,7 +289,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(connectionResponse);
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Ping))).Returns(new SimpleResponse(SimpleResponsePurport.UnknownMessage));
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Opened);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
var token = Guid.NewGuid();
@ -305,7 +304,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
public void TestConnectionMustNotFail()
{
var lost = false;
var proxy = new Mock<ICommunication>();
var proxy = new Mock<IProxyObject>();
var connectionResponse = new ConnectionResponse
{
CommunicationToken = Guid.NewGuid(),
@ -316,7 +315,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(connectionResponse);
proxy.Setup(p => p.Send(It.IsAny<Message>())).Throws<Exception>();
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Opened);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
var token = Guid.NewGuid();

View file

@ -23,7 +23,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
{
private Mock<ILogger> logger;
private Mock<IProxyObjectFactory> proxyObjectFactory;
private Mock<ICommunication> proxy;
private Mock<IProxyObject> proxy;
private ClientProxy sut;
[TestInitialize]
@ -37,10 +37,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
logger = new Mock<ILogger>();
proxyObjectFactory = new Mock<IProxyObjectFactory>();
proxy = new Mock<ICommunication>();
proxy = new Mock<IProxyObject>();
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Opened);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
sut = new ClientProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object);

View file

@ -24,7 +24,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
{
private Mock<ILogger> logger;
private Mock<IProxyObjectFactory> proxyObjectFactory;
private Mock<ICommunication> proxy;
private Mock<IProxyObject> proxy;
private RuntimeProxy sut;
[TestInitialize]
@ -38,10 +38,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
logger = new Mock<ILogger>();
proxyObjectFactory = new Mock<IProxyObjectFactory>();
proxy = new Mock<ICommunication>();
proxy = new Mock<IProxyObject>();
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Opened);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
sut = new RuntimeProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object);

View file

@ -23,7 +23,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
{
private Mock<ILogger> logger;
private Mock<IProxyObjectFactory> proxyObjectFactory;
private Mock<ICommunication> proxy;
private Mock<IProxyObject> proxy;
private ServiceProxy sut;
[TestInitialize]
@ -37,10 +37,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
logger = new Mock<ILogger>();
proxyObjectFactory = new Mock<IProxyObjectFactory>();
proxy = new Mock<ICommunication>();
proxy = new Mock<IProxyObject>();
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
proxy.As<ICommunicationObject>().Setup(o => o.State).Returns(CommunicationState.Opened);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
sut = new ServiceProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object);

View file

@ -84,6 +84,8 @@
<Compile Include="Behaviour\OperationModel\I18nOperationTests.cs" />
<Compile Include="Behaviour\OperationModel\DelegateOperationTests.cs" />
<Compile Include="Behaviour\OperationModel\OperationSequenceTests.cs" />
<Compile Include="Communication\Hosts\BaseHostImpl.cs" />
<Compile Include="Communication\Hosts\BaseHostTests.cs" />
<Compile Include="Communication\Proxies\BaseProxyImpl.cs" />
<Compile Include="Communication\Proxies\BaseProxyTests.cs" />
<Compile Include="Communication\Proxies\ClientProxyTests.cs" />

View file

@ -11,6 +11,7 @@ using System.ServiceModel;
using System.Threading;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Core.Communication.Hosts
@ -25,7 +26,8 @@ namespace SafeExamBrowser.Core.Communication.Hosts
private readonly object @lock = new object();
private string address;
private ServiceHost host;
private IHostObject host;
private IHostObjectFactory factory;
private Thread hostThread;
protected Guid? CommunicationToken { get; private set; }
@ -42,9 +44,10 @@ namespace SafeExamBrowser.Core.Communication.Hosts
}
}
public BaseHost(string address, ILogger logger)
public BaseHost(string address, IHostObjectFactory factory, ILogger logger)
{
this.address = address;
this.factory = factory;
this.Logger = logger;
}
@ -153,7 +156,7 @@ namespace SafeExamBrowser.Core.Communication.Hosts
{
Logger.Debug($"Terminated communication host for endpoint '{address}'.");
}
else
else if (exception != null)
{
throw new CommunicationException($"Failed to terminate communication host for endpoint '{address}'!", exception);
}
@ -171,16 +174,15 @@ namespace SafeExamBrowser.Core.Communication.Hosts
try
{
host = new ServiceHost(this);
host.AddServiceEndpoint(typeof(ICommunication), new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), address);
host = factory.CreateObject(address, this);
host.Closed += Host_Closed;
host.Closing += Host_Closing;
host.Faulted += Host_Faulted;
host.Opened += Host_Opened;
host.Opening += Host_Opening;
host.UnknownMessageReceived += Host_UnknownMessageReceived;
host.Open();
host.Open();
Logger.Debug($"Successfully started communication host for endpoint '{address}'.");
startedEvent.Set();
@ -200,7 +202,7 @@ namespace SafeExamBrowser.Core.Communication.Hosts
try
{
host?.Close();
success = hostThread.Join(TWO_SECONDS);
success = hostThread?.Join(TWO_SECONDS) == true;
}
catch (Exception e)
{
@ -236,11 +238,6 @@ namespace SafeExamBrowser.Core.Communication.Hosts
Logger.Debug("Communication host is opening...");
}
private void Host_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e)
{
Logger.Warn($"Communication host has received an unknown message: {e?.Message}.");
}
private string ToString(Message message)
{
return message != null ? message.ToString() : "<null>";

View file

@ -0,0 +1,37 @@
/*
* 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 SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Hosts;
namespace SafeExamBrowser.Core.Communication.Hosts
{
/// <summary>
/// Default implementation of the <see cref="IHostObjectFactory"/> utilizing WCF (<see cref="ServiceHost"/>).
/// </summary>
public class HostObjectFactory : IHostObjectFactory
{
public IHostObject CreateObject(string address, ICommunication communicationObject)
{
var host = new Host(communicationObject);
host.AddServiceEndpoint(typeof(ICommunication), new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), address);
return host;
}
private class Host : ServiceHost, IHostObject
{
internal Host(object singletonInstance, params Uri[] baseAddresses) : base(singletonInstance, baseAddresses)
{
}
}
}
}

View file

@ -25,7 +25,7 @@ namespace SafeExamBrowser.Core.Communication.Proxies
private static readonly object @lock = new object();
private string address;
private ICommunication proxy;
private IProxyObject proxy;
private IProxyObjectFactory factory;
private Guid? communicationToken;
private Timer timer;
@ -43,19 +43,10 @@ namespace SafeExamBrowser.Core.Communication.Proxies
public virtual bool Connect(Guid? token = null, bool autoPing = true)
{
proxy = factory.CreateObject(address);
if (proxy is ICommunicationObject communicationObject)
{
communicationObject.Closed += BaseProxy_Closed;
communicationObject.Closing += BaseProxy_Closing;
communicationObject.Faulted += BaseProxy_Faulted;
communicationObject.Opened += BaseProxy_Opened;
communicationObject.Opening += BaseProxy_Opening;
}
Logger.Debug($"Trying to connect to endpoint '{address}'{(token.HasValue ? $" with authentication token '{token}'" : string.Empty)}...");
InitializeProxyObject();
var response = proxy.Connect(token);
communicationToken = response.CommunicationToken;
@ -160,13 +151,24 @@ namespace SafeExamBrowser.Core.Communication.Proxies
}
/// <summary>
/// etrieves the string representation of the given <see cref="Response"/>, or indicates that a response is <c>null</c>.
/// Retrieves the string representation of the given <see cref="Response"/>, or indicates that a response is <c>null</c>.
/// </summary>
protected string ToString(Response response)
{
return response != null ? response.ToString() : "<null>";
}
private void InitializeProxyObject()
{
proxy = factory.CreateObject(address);
proxy.Closed += BaseProxy_Closed;
proxy.Closing += BaseProxy_Closing;
proxy.Faulted += BaseProxy_Faulted;
proxy.Opened += BaseProxy_Opened;
proxy.Opening += BaseProxy_Opening;
}
private void BaseProxy_Closed(object sender, EventArgs e)
{
Logger.Debug("Communication channel has been closed.");
@ -199,7 +201,7 @@ namespace SafeExamBrowser.Core.Communication.Proxies
throw new InvalidOperationException($"Cannot perform '{operationName}' before being connected to endpoint!");
}
if (proxy == null || (proxy as ICommunicationObject)?.State != CommunicationState.Opened)
if (proxy == null || proxy.State != CommunicationState.Opened)
{
throw new CommunicationException($"Tried to perform {operationName}, but channel was {GetChannelState()}!");
}
@ -207,7 +209,7 @@ namespace SafeExamBrowser.Core.Communication.Proxies
private string GetChannelState()
{
return proxy == null ? "null" : $"in state '{(proxy as ICommunicationObject)?.State}'";
return proxy == null ? "null" : $"in state '{proxy.State}'";
}
private void StartAutoPing()

View file

@ -7,7 +7,6 @@
*/
using System.ServiceModel;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Proxies;
namespace SafeExamBrowser.Core.Communication.Proxies
@ -17,10 +16,10 @@ namespace SafeExamBrowser.Core.Communication.Proxies
/// </summary>
public class ProxyObjectFactory : IProxyObjectFactory
{
public ICommunication CreateObject(string address)
public IProxyObject CreateObject(string address)
{
var endpoint = new EndpointAddress(address);
var channel = ChannelFactory<ICommunication>.CreateChannel(new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), endpoint);
var channel = ChannelFactory<IProxyObject>.CreateChannel(new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), endpoint);
return channel;
}

View file

@ -60,6 +60,7 @@
<Compile Include="Behaviour\OperationModel\I18nOperation.cs" />
<Compile Include="Behaviour\OperationModel\DelegateOperation.cs" />
<Compile Include="Behaviour\OperationModel\OperationSequence.cs" />
<Compile Include="Communication\Hosts\HostObjectFactory.cs" />
<Compile Include="Communication\Proxies\BaseProxy.cs" />
<Compile Include="Communication\Hosts\BaseHost.cs" />
<Compile Include="Communication\Proxies\ClientProxy.cs" />

View file

@ -30,7 +30,7 @@ namespace SafeExamBrowser.Runtime.Communication
public event CommunicationEventHandler ReconfigurationRequested;
public event CommunicationEventHandler ShutdownRequested;
public RuntimeHost(string address, IConfigurationRepository configuration, ILogger logger) : base(address, logger)
public RuntimeHost(string address, IConfigurationRepository configuration, IHostObjectFactory factory, ILogger logger) : base(address, factory, logger)
{
this.configuration = configuration;
}

View file

@ -14,6 +14,7 @@ using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Behaviour.OperationModel;
using SafeExamBrowser.Core.Communication.Hosts;
using SafeExamBrowser.Core.Communication.Proxies;
using SafeExamBrowser.Core.I18n;
using SafeExamBrowser.Core.Logging;
@ -51,7 +52,7 @@ namespace SafeExamBrowser.Runtime
var desktop = new Desktop(new ModuleLogger(logger, typeof(Desktop)));
var processFactory = new ProcessFactory(desktop, new ModuleLogger(logger, typeof(ProcessFactory)));
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 HostObjectFactory(), new ModuleLogger(logger, typeof(RuntimeHost)));
var serviceProxy = new ServiceProxy(runtimeInfo.ServiceAddress, new ProxyObjectFactory(), new ModuleLogger(logger, typeof(ServiceProxy)));
var sessionController = new SessionController(configuration, logger, processFactory, proxyFactory, runtimeHost, serviceProxy);