From 3a4f189916a7f68384aa38ef420fa7ec5c515c03 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Fri, 16 Mar 2018 15:46:53 +0100 Subject: [PATCH] SEBWIN-219: Extracted creation of proxy and host objects from base implementations and started implementing unit tests for hosts. --- .../Communication/ClientHost.cs | 2 +- SafeExamBrowser.Client/CompositionRoot.cs | 4 +- .../Communication/Hosts/IHostObject.cs | 19 ++ .../Communication/Hosts/IHostObjectFactory.cs | 21 ++ .../Communication/Proxies/IProxyObject.cs | 19 ++ .../Proxies/IProxyObjectFactory.cs | 6 +- .../SafeExamBrowser.Contracts.csproj | 3 + .../Communication/Hosts/BaseHostImpl.cs | 53 ++++ .../Communication/Hosts/BaseHostTests.cs | 227 ++++++++++++++++++ .../Communication/Proxies/BaseProxyTests.cs | 37 ++- .../Communication/Proxies/ClientProxyTests.cs | 6 +- .../Proxies/RuntimeProxyTests.cs | 6 +- .../Proxies/ServiceProxyTests.cs | 6 +- .../SafeExamBrowser.Core.UnitTests.csproj | 2 + .../Communication/Hosts/BaseHost.cs | 23 +- .../Communication/Hosts/HostObjectFactory.cs | 37 +++ .../Communication/Proxies/BaseProxy.cs | 32 +-- .../Proxies/ProxyObjectFactory.cs | 5 +- .../SafeExamBrowser.Core.csproj | 1 + .../Communication/RuntimeHost.cs | 2 +- SafeExamBrowser.Runtime/CompositionRoot.cs | 3 +- 21 files changed, 448 insertions(+), 66 deletions(-) create mode 100644 SafeExamBrowser.Contracts/Communication/Hosts/IHostObject.cs create mode 100644 SafeExamBrowser.Contracts/Communication/Hosts/IHostObjectFactory.cs create mode 100644 SafeExamBrowser.Contracts/Communication/Proxies/IProxyObject.cs create mode 100644 SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostImpl.cs create mode 100644 SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostTests.cs create mode 100644 SafeExamBrowser.Core/Communication/Hosts/HostObjectFactory.cs diff --git a/SafeExamBrowser.Client/Communication/ClientHost.cs b/SafeExamBrowser.Client/Communication/ClientHost.cs index 47f5395c..4f62cf73 100644 --- a/SafeExamBrowser.Client/Communication/ClientHost.cs +++ b/SafeExamBrowser.Client/Communication/ClientHost.cs @@ -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; } diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index 9279a868..062224e9 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -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; diff --git a/SafeExamBrowser.Contracts/Communication/Hosts/IHostObject.cs b/SafeExamBrowser.Contracts/Communication/Hosts/IHostObject.cs new file mode 100644 index 00000000..d1fd739d --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Hosts/IHostObject.cs @@ -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 +{ + /// + /// The host object to be used in communication hosts. + /// + public interface IHostObject : ICommunicationObject + { + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Hosts/IHostObjectFactory.cs b/SafeExamBrowser.Contracts/Communication/Hosts/IHostObjectFactory.cs new file mode 100644 index 00000000..aeb61921 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Hosts/IHostObjectFactory.cs @@ -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 +{ + /// + /// A factory to create host objects for communication hosts. + /// + public interface IHostObjectFactory + { + /// + /// Utilizes the given communication object to create a host object (see ) for the specified endpoint address. + /// + IHostObject CreateObject(string address, ICommunication communicationObject); + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Proxies/IProxyObject.cs b/SafeExamBrowser.Contracts/Communication/Proxies/IProxyObject.cs new file mode 100644 index 00000000..359d42b7 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Proxies/IProxyObject.cs @@ -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 +{ + /// + /// The communication object to be used in an . + /// + public interface IProxyObject : ICommunication, ICommunicationObject + { + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Proxies/IProxyObjectFactory.cs b/SafeExamBrowser.Contracts/Communication/Proxies/IProxyObjectFactory.cs index d10d85d3..31c459d4 100644 --- a/SafeExamBrowser.Contracts/Communication/Proxies/IProxyObjectFactory.cs +++ b/SafeExamBrowser.Contracts/Communication/Proxies/IProxyObjectFactory.cs @@ -9,13 +9,13 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies { /// - /// A factory to create communication objects for proxies. + /// A factory to create proxy objects for communication proxies. /// public interface IProxyObjectFactory { /// - /// Creates a communication object (see ) for the specified endpoint address. + /// Creates a proxy object (see ) for the specified endpoint address. /// - ICommunication CreateObject(string address); + IProxyObject CreateObject(string address); } } diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index c7226e24..f4b5c8e8 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -58,10 +58,13 @@ + + + diff --git a/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostImpl.cs b/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostImpl.cs new file mode 100644 index 00000000..b0fb5a11 --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostImpl.cs @@ -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 OnConnectStub { get; set; } + public Action OnDisconnectStub { get; set; } + public Func OnReceiveStub { get; set; } + public Func 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); + } + } +} diff --git a/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostTests.cs b/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostTests.cs new file mode 100644 index 00000000..80560a30 --- /dev/null +++ b/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostTests.cs @@ -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 hostObject; + private Mock hostObjectFactory; + private Mock logger; + private BaseHostImpl sut; + + [TestInitialize] + public void Initialize() + { + hostObject = new Mock(); + hostObjectFactory = new Mock(); + logger = new Mock(); + + hostObjectFactory.Setup(f => f.CreateObject(It.IsAny(), It.IsAny())).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(), sut), Times.Once); + + Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId); + } + + [TestMethod] + [ExpectedException(typeof(CommunicationException))] + public void MustCorrectlyHandleStartupException() + { + hostObject.Setup(h => h.Open()).Throws(); + + 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(); + + 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(); + } + } +} diff --git a/SafeExamBrowser.Core.UnitTests/Communication/Proxies/BaseProxyTests.cs b/SafeExamBrowser.Core.UnitTests/Communication/Proxies/BaseProxyTests.cs index 96c5b8e7..24d9cca9 100644 --- a/SafeExamBrowser.Core.UnitTests/Communication/Proxies/BaseProxyTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Communication/Proxies/BaseProxyTests.cs @@ -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(); + var proxy = new Mock(); 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(); + var proxy = new Mock(); var connectionResponse = new ConnectionResponse { CommunicationToken = Guid.NewGuid(), @@ -71,7 +70,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies proxy.Setup(p => p.Connect(It.IsAny())).Returns(connectionResponse); proxy.Setup(p => p.Disconnect(It.IsAny())).Returns(disconnectionResponse); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Opened); + proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).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(); + var proxy = new Mock(); 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(); + var proxy = new Mock(); var response = new ConnectionResponse { CommunicationToken = Guid.NewGuid(), @@ -125,7 +124,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies }; proxy.Setup(p => p.Connect(It.IsAny())).Returns(response); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Faulted); + proxy.Setup(o => o.State).Returns(CommunicationState.Faulted); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).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(); + var proxy = new Mock(); var response = new ConnectionResponse { CommunicationToken = Guid.NewGuid(), @@ -153,7 +152,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies }; proxy.Setup(p => p.Connect(It.IsAny())).Returns(response); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Faulted); + proxy.Setup(o => o.State).Returns(CommunicationState.Faulted); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).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(); + var proxy = new Mock(); var connectionResponse = new ConnectionResponse { CommunicationToken = Guid.NewGuid(), @@ -183,7 +182,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies proxy.Setup(p => p.Connect(It.IsAny())).Returns(connectionResponse); proxy.Setup(p => p.Send(message)).Returns(response.Object); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Opened); + proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).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(); + var proxy = new Mock(); var connectionResponse = new ConnectionResponse { CommunicationToken = Guid.NewGuid(), @@ -208,7 +207,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies proxy.Setup(p => p.Connect(It.IsAny())).Returns(connectionResponse); proxy.Setup(p => p.Send(It.IsAny())).Returns(response.Object); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Opened); + proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).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(); + var proxy = new Mock(); var connectionResponse = new ConnectionResponse { CommunicationToken = Guid.NewGuid(), @@ -264,7 +263,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies proxy.Setup(p => p.Connect(It.IsAny())).Returns(connectionResponse); proxy.Setup(p => p.Send(It.Is(m => m.Purport == SimpleMessagePurport.Ping))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged)); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Opened); + proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).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(); + var proxy = new Mock(); var connectionResponse = new ConnectionResponse { CommunicationToken = Guid.NewGuid(), @@ -290,7 +289,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies proxy.Setup(p => p.Connect(It.IsAny())).Returns(connectionResponse); proxy.Setup(p => p.Send(It.Is(m => m.Purport == SimpleMessagePurport.Ping))).Returns(new SimpleResponse(SimpleResponsePurport.UnknownMessage)); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Opened); + proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).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(); + var proxy = new Mock(); var connectionResponse = new ConnectionResponse { CommunicationToken = Guid.NewGuid(), @@ -316,7 +315,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies proxy.Setup(p => p.Connect(It.IsAny())).Returns(connectionResponse); proxy.Setup(p => p.Send(It.IsAny())).Throws(); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Opened); + proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).Returns(proxy.Object); var token = Guid.NewGuid(); diff --git a/SafeExamBrowser.Core.UnitTests/Communication/Proxies/ClientProxyTests.cs b/SafeExamBrowser.Core.UnitTests/Communication/Proxies/ClientProxyTests.cs index 97205118..7424271b 100644 --- a/SafeExamBrowser.Core.UnitTests/Communication/Proxies/ClientProxyTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Communication/Proxies/ClientProxyTests.cs @@ -23,7 +23,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies { private Mock logger; private Mock proxyObjectFactory; - private Mock proxy; + private Mock proxy; private ClientProxy sut; [TestInitialize] @@ -37,10 +37,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies logger = new Mock(); proxyObjectFactory = new Mock(); - proxy = new Mock(); + proxy = new Mock(); proxy.Setup(p => p.Connect(It.IsAny())).Returns(response); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Opened); + proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).Returns(proxy.Object); sut = new ClientProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object); diff --git a/SafeExamBrowser.Core.UnitTests/Communication/Proxies/RuntimeProxyTests.cs b/SafeExamBrowser.Core.UnitTests/Communication/Proxies/RuntimeProxyTests.cs index 7a22d067..7c6a7f9c 100644 --- a/SafeExamBrowser.Core.UnitTests/Communication/Proxies/RuntimeProxyTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Communication/Proxies/RuntimeProxyTests.cs @@ -24,7 +24,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies { private Mock logger; private Mock proxyObjectFactory; - private Mock proxy; + private Mock proxy; private RuntimeProxy sut; [TestInitialize] @@ -38,10 +38,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies logger = new Mock(); proxyObjectFactory = new Mock(); - proxy = new Mock(); + proxy = new Mock(); proxy.Setup(p => p.Connect(It.IsAny())).Returns(response); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Opened); + proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).Returns(proxy.Object); sut = new RuntimeProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object); diff --git a/SafeExamBrowser.Core.UnitTests/Communication/Proxies/ServiceProxyTests.cs b/SafeExamBrowser.Core.UnitTests/Communication/Proxies/ServiceProxyTests.cs index b610838f..3385aa36 100644 --- a/SafeExamBrowser.Core.UnitTests/Communication/Proxies/ServiceProxyTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Communication/Proxies/ServiceProxyTests.cs @@ -23,7 +23,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies { private Mock logger; private Mock proxyObjectFactory; - private Mock proxy; + private Mock proxy; private ServiceProxy sut; [TestInitialize] @@ -37,10 +37,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies logger = new Mock(); proxyObjectFactory = new Mock(); - proxy = new Mock(); + proxy = new Mock(); proxy.Setup(p => p.Connect(It.IsAny())).Returns(response); - proxy.As().Setup(o => o.State).Returns(CommunicationState.Opened); + proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny())).Returns(proxy.Object); sut = new ServiceProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object); diff --git a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj index dc7fb843..630f0e30 100644 --- a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj +++ b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj @@ -84,6 +84,8 @@ + + diff --git a/SafeExamBrowser.Core/Communication/Hosts/BaseHost.cs b/SafeExamBrowser.Core/Communication/Hosts/BaseHost.cs index 93540c09..85df3f6d 100644 --- a/SafeExamBrowser.Core/Communication/Hosts/BaseHost.cs +++ b/SafeExamBrowser.Core/Communication/Hosts/BaseHost.cs @@ -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() : ""; diff --git a/SafeExamBrowser.Core/Communication/Hosts/HostObjectFactory.cs b/SafeExamBrowser.Core/Communication/Hosts/HostObjectFactory.cs new file mode 100644 index 00000000..063bb24f --- /dev/null +++ b/SafeExamBrowser.Core/Communication/Hosts/HostObjectFactory.cs @@ -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 +{ + /// + /// Default implementation of the utilizing WCF (). + /// + 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) + { + } + } + } +} diff --git a/SafeExamBrowser.Core/Communication/Proxies/BaseProxy.cs b/SafeExamBrowser.Core/Communication/Proxies/BaseProxy.cs index cca1ad89..862d2a20 100644 --- a/SafeExamBrowser.Core/Communication/Proxies/BaseProxy.cs +++ b/SafeExamBrowser.Core/Communication/Proxies/BaseProxy.cs @@ -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 } /// - /// etrieves the string representation of the given , or indicates that a response is null. + /// Retrieves the string representation of the given , or indicates that a response is null. /// protected string ToString(Response response) { return response != null ? response.ToString() : ""; } + 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() diff --git a/SafeExamBrowser.Core/Communication/Proxies/ProxyObjectFactory.cs b/SafeExamBrowser.Core/Communication/Proxies/ProxyObjectFactory.cs index b816f8d2..0dd9462c 100644 --- a/SafeExamBrowser.Core/Communication/Proxies/ProxyObjectFactory.cs +++ b/SafeExamBrowser.Core/Communication/Proxies/ProxyObjectFactory.cs @@ -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 /// public class ProxyObjectFactory : IProxyObjectFactory { - public ICommunication CreateObject(string address) + public IProxyObject CreateObject(string address) { var endpoint = new EndpointAddress(address); - var channel = ChannelFactory.CreateChannel(new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), endpoint); + var channel = ChannelFactory.CreateChannel(new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport), endpoint); return channel; } diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index 7f22750b..553c2361 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -60,6 +60,7 @@ + diff --git a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs index c2876f3b..419c9f05 100644 --- a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs +++ b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs @@ -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; } diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 1f3de615..bce653e3 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -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);