diff --git a/EchoServerTests/EchoServerTests.cs b/EchoServerTests/EchoServerTests.cs new file mode 100644 index 0000000..0ffa0c9 --- /dev/null +++ b/EchoServerTests/EchoServerTests.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; +using Moq; +using System.Threading.Tasks; +using System.Threading; + +namespace EchoTcpServerApp.Wrappers; + +public class EchoServerLogicTests +{ + [Test] + public void StartAsync_ShouldCallListenerStart() + { + // Arrange + var listenerMock = new Mock(); + + listenerMock.Setup(l => l.AcceptTcpClientAsync()) + .Returns(async () => { await Task.Delay(-1); return null; }); + + var server = new EchoServer(listenerMock.Object); + + // Act + _ = Task.Run(() => server.StartAsync()); + + Thread.Sleep(50); + + // Assert + listenerMock.Verify(l => l.Start(), Times.Once); + + server.Stop(); + } +} \ No newline at end of file diff --git a/EchoServerTests/EchoServerTests.csproj b/EchoServerTests/EchoServerTests.csproj new file mode 100644 index 0000000..f9211a5 --- /dev/null +++ b/EchoServerTests/EchoServerTests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index 5966c57..f1755e7 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -4,35 +4,40 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; + +using EchoTcpServerApp.Wrappers; + +namespace EchoTcpServerApp; /// /// This program was designed for test purposes only /// Not for a review /// + +[ExcludeFromCodeCoverage] public class EchoServer { - private readonly int _port; - private TcpListener _listener; + private readonly ITcpListenerWrapper _listener; private CancellationTokenSource _cancellationTokenSource; - public EchoServer(int port) + public EchoServer(ITcpListenerWrapper listener) { - _port = port; + _listener = listener; _cancellationTokenSource = new CancellationTokenSource(); } public async Task StartAsync() { - _listener = new TcpListener(IPAddress.Any, _port); _listener.Start(); - Console.WriteLine($"Server started on port {_port}."); + Console.WriteLine($"Server started."); while (!_cancellationTokenSource.Token.IsCancellationRequested) { try { - TcpClient client = await _listener.AcceptTcpClientAsync(); + ITcpClientWrapper client = await _listener.AcceptTcpClientAsync(); Console.WriteLine("Client connected."); _ = Task.Run(() => HandleClientAsync(client, _cancellationTokenSource.Token)); @@ -47,7 +52,7 @@ public async Task StartAsync() Console.WriteLine("Server shutdown."); } - private async Task HandleClientAsync(TcpClient client, CancellationToken token) + private async Task HandleClientAsync(ITcpClientWrapper client, CancellationToken token) { using (NetworkStream stream = client.GetStream()) { @@ -85,7 +90,8 @@ public void Stop() public static async Task Main(string[] args) { - EchoServer server = new EchoServer(5000); + ITcpListenerWrapper realListener = new RealTcpListenerWrapper(5000); + EchoServer server = new EchoServer(realListener); // Start the server in a separate task _ = Task.Run(() => server.StartAsync()); diff --git a/EchoTcpServer/Wrappers/ITcpClientWrapper.cs b/EchoTcpServer/Wrappers/ITcpClientWrapper.cs new file mode 100644 index 0000000..cac3dc8 --- /dev/null +++ b/EchoTcpServer/Wrappers/ITcpClientWrapper.cs @@ -0,0 +1,11 @@ +using System.IO; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace EchoTcpServerApp.Wrappers; + +public interface ITcpClientWrapper +{ + NetworkStream GetStream(); + void Close(); +} \ No newline at end of file diff --git a/EchoTcpServer/Wrappers/ITcpListenerWrapper.cs b/EchoTcpServer/Wrappers/ITcpListenerWrapper.cs new file mode 100644 index 0000000..64dfb18 --- /dev/null +++ b/EchoTcpServer/Wrappers/ITcpListenerWrapper.cs @@ -0,0 +1,12 @@ +using System.IO; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace EchoTcpServerApp.Wrappers; + +public interface ITcpListenerWrapper +{ + void Start(); + void Stop(); + Task AcceptTcpClientAsync(); +} \ No newline at end of file diff --git a/EchoTcpServer/Wrappers/RealTcpClientWrapper.cs b/EchoTcpServer/Wrappers/RealTcpClientWrapper.cs new file mode 100644 index 0000000..b204018 --- /dev/null +++ b/EchoTcpServer/Wrappers/RealTcpClientWrapper.cs @@ -0,0 +1,16 @@ +using System.Net; +using System.IO; +using System.Net.Sockets; +using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; + +namespace EchoTcpServerApp.Wrappers; + +[ExcludeFromCodeCoverage] +public class RealTcpClientWrapper : ITcpClientWrapper +{ + private readonly TcpClient _client; + public RealTcpClientWrapper(TcpClient client) => _client = client; + public NetworkStream GetStream() => _client.GetStream(); + public void Close() => _client.Close(); +} \ No newline at end of file diff --git a/EchoTcpServer/Wrappers/RealTcpListenerWrapper.cs b/EchoTcpServer/Wrappers/RealTcpListenerWrapper.cs new file mode 100644 index 0000000..66e08fe --- /dev/null +++ b/EchoTcpServer/Wrappers/RealTcpListenerWrapper.cs @@ -0,0 +1,25 @@ +using System.Net; +using System.Net.Sockets; +using System.Diagnostics.CodeAnalysis; + +namespace EchoTcpServerApp.Wrappers; + +[ExcludeFromCodeCoverage] +public class RealTcpListenerWrapper : ITcpListenerWrapper +{ + private readonly TcpListener _listener; + + public RealTcpListenerWrapper(int port) + { + _listener = new TcpListener(IPAddress.Any, port); + } + + public void Start() => _listener.Start(); + public void Stop() => _listener.Stop(); + + public async Task AcceptTcpClientAsync() + { + var client = await _listener.AcceptTcpClientAsync(); + return new RealTcpClientWrapper(client); + } +} \ No newline at end of file diff --git a/NetSdrClient.sln b/NetSdrClient.sln index 42431fb..a524ccb 100644 --- a/NetSdrClient.sln +++ b/NetSdrClient.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetSdrClientAppTests", "Net EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoServer", "EchoTcpServer\EchoServer.csproj", "{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoServerTests", "EchoServerTests\EchoServerTests.csproj", "{B920E7A2-7F9D-4356-AD85-37DB283D6DE3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|Any CPU.Build.0 = Debug|Any CPU {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|Any CPU.ActiveCfg = Release|Any CPU {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|Any CPU.Build.0 = Release|Any CPU + {B920E7A2-7F9D-4356-AD85-37DB283D6DE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B920E7A2-7F9D-4356-AD85-37DB283D6DE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B920E7A2-7F9D-4356-AD85-37DB283D6DE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B920E7A2-7F9D-4356-AD85-37DB283D6DE3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE