diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..ae591b1
Binary files /dev/null and b/.DS_Store differ
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..446b951
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "nuget"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index e784069..0b30ac2 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -56,8 +56,8 @@ jobs:
dotnet tool install --global dotnet-sonarscanner
echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
dotnet sonarscanner begin `
- /k:"ppanchen_NetSdrClient" `
- /o:"ppanchen" `
+ /k:"Dandddyy_ReengineeringCourse" `
+ /o:"dandddyy" `
/d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
/d:sonar.cs.opencover.reportsPaths="**/coverage.xml" `
/d:sonar.cpd.cs.minimumTokens=40 `
@@ -70,13 +70,13 @@ jobs:
run: dotnet restore NetSdrClient.sln
- name: Build
run: dotnet build NetSdrClient.sln -c Release --no-restore
- #- name: Tests with coverage (OpenCover)
- # run: |
- # dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
- # /p:CollectCoverage=true `
- # /p:CoverletOutput=TestResults/coverage.xml `
- # /p:CoverletOutputFormat=opencover
- # shell: pwsh
+ - name: Tests with coverage (OpenCover)
+ run: |
+ dotnet test NetSdrClient.sln -c Release --no-build `
+ /p:CollectCoverage=true `
+ /p:CoverletOutput=TestResults/coverage.xml `
+ /p:CoverletOutputFormat=opencover
+ shell: pwsh
# 3) END: SonarScanner
- name: SonarScanner End
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
diff --git a/EchoTcpServer/EchoServer.cs b/EchoTcpServer/EchoServer.cs
new file mode 100644
index 0000000..f305dcd
--- /dev/null
+++ b/EchoTcpServer/EchoServer.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EchoTcpServer
+{
+ public class EchoServer : IDisposable
+ {
+ private readonly IPAddress _ipAddress;
+ private readonly int _port;
+ private TcpListener? _listener;
+ private CancellationTokenSource _cancellationTokenSource;
+
+ public EchoServer(IPAddress ipAddress, int port)
+ {
+ _ipAddress = ipAddress;
+ _port = port;
+ _cancellationTokenSource = new CancellationTokenSource();
+ }
+
+ public async Task StartAsync()
+ {
+ _listener = new TcpListener(_ipAddress, _port);
+ _listener.Start();
+ Console.WriteLine($"Server started on {_ipAddress}:{_port}.");
+
+ try
+ {
+ while (!_cancellationTokenSource.Token.IsCancellationRequested)
+ {
+ TcpClient client = await _listener.AcceptTcpClientAsync(_cancellationTokenSource.Token);
+ Console.WriteLine("Client connected.");
+ _ = Task.Run(() => HandleClientAsync(client, _cancellationTokenSource.Token));
+ }
+ }
+ catch (OperationCanceledException) { /* Очікувано при вимкненні */ }
+ catch (Exception ex) { Console.WriteLine($"Server error: {ex.Message}"); }
+ finally { Console.WriteLine("Server shutdown."); }
+ }
+
+ private async Task HandleClientAsync(TcpClient client, CancellationToken token)
+ {
+ using (client)
+ using (NetworkStream stream = client.GetStream())
+ {
+ try
+ {
+ byte[] buffer = new byte[8192];
+ int bytesRead;
+ while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
+ {
+ await stream.WriteAsync(buffer, 0, bytesRead, token);
+ Console.WriteLine($"Echoed {bytesRead} bytes to the client.");
+ }
+ }
+ catch (Exception ex) when (ex is not OperationCanceledException)
+ {
+ Console.WriteLine($"Error handling client: {ex.Message}");
+ }
+ }
+ }
+
+ public void Stop()
+ {
+ if (!_cancellationTokenSource.IsCancellationRequested)
+ {
+ _cancellationTokenSource.Cancel();
+ }
+ _listener?.Stop();
+ }
+
+ public void Dispose()
+ {
+ Stop();
+ _cancellationTokenSource.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EchoTcpServer/EchoServer.csproj b/EchoTcpServer/EchoServer.csproj
index 2150e37..91b464a 100644
--- a/EchoTcpServer/EchoServer.csproj
+++ b/EchoTcpServer/EchoServer.csproj
@@ -1,4 +1,4 @@
-
+
Exe
diff --git a/EchoTcpServer/IUdpClient.cs b/EchoTcpServer/IUdpClient.cs
new file mode 100644
index 0000000..d21bc4e
--- /dev/null
+++ b/EchoTcpServer/IUdpClient.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Diagnostics.CodeAnalysis;
+
+namespace EchoTcpServer
+{
+ public interface IUdpClient : IDisposable
+ {
+ void Send(byte[] dgram, int bytes, IPEndPoint endPoint);
+ }
+
+ [ExcludeFromCodeCoverage]
+ public class StandardUdpClient : IUdpClient
+ {
+ private readonly UdpClient _client = new UdpClient();
+ public void Send(byte[] dgram, int bytes, IPEndPoint endPoint) => _client.Send(dgram, bytes, endPoint);
+ public void Dispose() => _client.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs
index 5966c57..011b208 100644
--- a/EchoTcpServer/Program.cs
+++ b/EchoTcpServer/Program.cs
@@ -1,173 +1,29 @@
using System;
using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
-///
-/// This program was designed for test purposes only
-/// Not for a review
-///
-public class EchoServer
+namespace EchoTcpServer
{
- private readonly int _port;
- private TcpListener _listener;
- private CancellationTokenSource _cancellationTokenSource;
-
-
- public EchoServer(int port)
+ [ExcludeFromCodeCoverage]
+ public class Program
{
- _port = port;
- _cancellationTokenSource = new CancellationTokenSource();
- }
-
- public async Task StartAsync()
- {
- _listener = new TcpListener(IPAddress.Any, _port);
- _listener.Start();
- Console.WriteLine($"Server started on port {_port}.");
-
- while (!_cancellationTokenSource.Token.IsCancellationRequested)
+ public static async Task Main(string[] args)
{
- try
- {
- TcpClient client = await _listener.AcceptTcpClientAsync();
- Console.WriteLine("Client connected.");
+ using var server = new EchoServer(IPAddress.Any, 5000);
+ _ = Task.Run(() => server.StartAsync());
- _ = Task.Run(() => HandleClientAsync(client, _cancellationTokenSource.Token));
- }
- catch (ObjectDisposedException)
- {
- // Listener has been closed
- break;
- }
- }
+ using var standardUdpClient = new StandardUdpClient();
+ using var sender = new UdpTimedSender("127.0.0.1", 60000, standardUdpClient);
- Console.WriteLine("Server shutdown.");
- }
-
- private async Task HandleClientAsync(TcpClient client, CancellationToken token)
- {
- using (NetworkStream stream = client.GetStream())
- {
- try
- {
- byte[] buffer = new byte[8192];
- int bytesRead;
-
- while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
- {
- // Echo back the received message
- await stream.WriteAsync(buffer, 0, bytesRead, token);
- Console.WriteLine($"Echoed {bytesRead} bytes to the client.");
- }
- }
- catch (Exception ex) when (!(ex is OperationCanceledException))
- {
- Console.WriteLine($"Error: {ex.Message}");
- }
- finally
- {
- client.Close();
- Console.WriteLine("Client disconnected.");
- }
- }
- }
-
- public void Stop()
- {
- _cancellationTokenSource.Cancel();
- _listener.Stop();
- _cancellationTokenSource.Dispose();
- Console.WriteLine("Server stopped.");
- }
-
- public static async Task Main(string[] args)
- {
- EchoServer server = new EchoServer(5000);
-
- // Start the server in a separate task
- _ = Task.Run(() => server.StartAsync());
-
- string host = "127.0.0.1"; // Target IP
- int port = 60000; // Target Port
- int intervalMilliseconds = 5000; // Send every 3 seconds
-
- using (var sender = new UdpTimedSender(host, port))
- {
Console.WriteLine("Press any key to stop sending...");
- sender.StartSending(intervalMilliseconds);
+ sender.StartSending(5000);
Console.WriteLine("Press 'q' to quit...");
- while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q)
- {
- // Just wait until 'q' is pressed
- }
+ while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q) { }
sender.StopSending();
server.Stop();
- Console.WriteLine("Sender stopped.");
}
}
-}
-
-
-public class UdpTimedSender : IDisposable
-{
- private readonly string _host;
- private readonly int _port;
- private readonly UdpClient _udpClient;
- private Timer _timer;
-
- public UdpTimedSender(string host, int port)
- {
- _host = host;
- _port = port;
- _udpClient = new UdpClient();
- }
-
- public void StartSending(int intervalMilliseconds)
- {
- if (_timer != null)
- throw new InvalidOperationException("Sender is already running.");
-
- _timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds);
- }
-
- ushort i = 0;
-
- private void SendMessageCallback(object state)
- {
- try
- {
- //dummy data
- Random rnd = new Random();
- byte[] samples = new byte[1024];
- rnd.NextBytes(samples);
- i++;
-
- byte[] msg = (new byte[] { 0x04, 0x84 }).Concat(BitConverter.GetBytes(i)).Concat(samples).ToArray();
- var endpoint = new IPEndPoint(IPAddress.Parse(_host), _port);
-
- _udpClient.Send(msg, msg.Length, endpoint);
- Console.WriteLine($"Message sent to {_host}:{_port} ");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Error sending message: {ex.Message}");
- }
- }
-
- public void StopSending()
- {
- _timer?.Dispose();
- _timer = null;
- }
-
- public void Dispose()
- {
- StopSending();
- _udpClient.Dispose();
- }
}
\ No newline at end of file
diff --git a/EchoTcpServer/UdpTimedSender.cs b/EchoTcpServer/UdpTimedSender.cs
new file mode 100644
index 0000000..f5885ba
--- /dev/null
+++ b/EchoTcpServer/UdpTimedSender.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Security.Cryptography;
+
+namespace EchoTcpServer
+{
+ public class UdpTimedSender : IDisposable
+ {
+ private readonly string _host;
+ private readonly int _port;
+ private readonly IUdpClient _udpClient;
+ private Timer? _timer;
+ private ushort _messageCount = 0;
+
+ public UdpTimedSender(string host, int port, IUdpClient udpClient)
+ {
+ _host = host;
+ _port = port;
+ _udpClient = udpClient;
+ }
+
+ public void StartSending(int intervalMilliseconds)
+ {
+ if (_timer != null)
+ throw new InvalidOperationException("Sender is already running.");
+
+ _timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds);
+ }
+
+ private void SendMessageCallback(object? state)
+ {
+ try
+ {
+ byte[] samples = new byte[1024];
+ RandomNumberGenerator.Fill(samples);
+ _messageCount++;
+
+ byte[] msg = new byte[] { 0x04, 0x84 }.Concat(BitConverter.GetBytes(_messageCount)).Concat(samples).ToArray();
+ var endpoint = new IPEndPoint(IPAddress.Parse(_host), _port);
+
+ _udpClient.Send(msg, msg.Length, endpoint);
+ Console.WriteLine($"Message sent to {_host}:{_port}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error sending message: {ex.Message}");
+ }
+ }
+
+ public void StopSending()
+ {
+ _timer?.Dispose();
+ _timer = null;
+ }
+
+ public void Dispose()
+ {
+ StopSending();
+ _udpClient.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/EchoTcpServerTests/EchoServerTests.cs b/EchoTcpServerTests/EchoServerTests.cs
new file mode 100644
index 0000000..21a6f22
--- /dev/null
+++ b/EchoTcpServerTests/EchoServerTests.cs
@@ -0,0 +1,58 @@
+using NUnit.Framework;
+using EchoTcpServer;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EchoTcpServerTests
+{
+ [TestFixture]
+ public class EchoServerTests
+ {
+ [Test]
+ public async Task EchoServer_ShouldEchoDataBack()
+ {
+ // Arrange
+ int testPort = 5055;
+ using var server = new EchoServer(IPAddress.Loopback, testPort);
+ var serverTask = Task.Run(() => server.StartAsync());
+
+ await Task.Delay(100);
+
+ using var client = new TcpClient();
+ await client.ConnectAsync(IPAddress.Loopback, testPort);
+ using var stream = client.GetStream();
+
+ byte[] dataToSend = Encoding.UTF8.GetBytes("Hello Lab 6");
+ byte[] receiveBuffer = new byte[1024];
+
+ // Act
+ await stream.WriteAsync(dataToSend, 0, dataToSend.Length);
+ int bytesRead = await stream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length);
+ string response = Encoding.UTF8.GetString(receiveBuffer, 0, bytesRead);
+
+ // Assert
+ Assert.That(response, Is.EqualTo("Hello Lab 6"));
+
+ // Cleanup
+ server.Stop();
+ await serverTask;
+ }
+
+ [Test]
+ public async Task Stop_ShouldGracefullyCancelListenerLoop()
+ {
+ // Arrange
+ using var server = new EchoServer(IPAddress.Loopback, 5056);
+ var serverTask = Task.Run(() => server.StartAsync());
+
+ await Task.Delay(50);
+
+ // Act
+ server.Stop();
+
+ Assert.DoesNotThrowAsync(async () => await serverTask);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EchoTcpServerTests/EchoTcpServerTests.csproj b/EchoTcpServerTests/EchoTcpServerTests.csproj
new file mode 100644
index 0000000..cc755d9
--- /dev/null
+++ b/EchoTcpServerTests/EchoTcpServerTests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EchoTcpServerTests/UdpTimedSenderTests.cs b/EchoTcpServerTests/UdpTimedSenderTests.cs
new file mode 100644
index 0000000..441b41f
--- /dev/null
+++ b/EchoTcpServerTests/UdpTimedSenderTests.cs
@@ -0,0 +1,44 @@
+using Moq;
+using NUnit.Framework;
+using EchoTcpServer;
+using System;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace EchoTcpServerTests
+{
+ [TestFixture]
+ public class UdpTimedSenderTests
+ {
+ [Test]
+ public void StartSending_WhenAlreadyRunning_ShouldThrowException()
+ {
+ var mockUdp = new Mock();
+ using var sender = new UdpTimedSender("127.0.0.1", 60000, mockUdp.Object);
+
+ sender.StartSending(1000);
+
+ Assert.Throws(() => sender.StartSending(1000));
+ }
+
+ [Test]
+ public async Task StartSending_ShouldSendMessagesPeriodically()
+ {
+ // Arrange
+ var mockUdp = new Mock();
+ int callCount = 0;
+ mockUdp.Setup(u => u.Send(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback(() => callCount++);
+
+ using var sender = new UdpTimedSender("127.0.0.1", 60000, mockUdp.Object);
+
+ // Act
+ sender.StartSending(50);
+ await Task.Delay(150);
+ sender.StopSending();
+
+ // Assert
+ Assert.That(callCount, Is.GreaterThanOrEqualTo(2), "The timer should have triggered the sending at least 2 times.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/NetSdrClient.sln b/NetSdrClient.sln
index 42431fb..3b88600 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}") = "EchoTcpServerTests", "EchoTcpServerTests\EchoTcpServerTests.csproj", "{7298C746-69F8-45A3-8A35-4513081C1936}"
+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
+ {7298C746-69F8-45A3-8A35-4513081C1936}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7298C746-69F8-45A3-8A35-4513081C1936}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7298C746-69F8-45A3-8A35-4513081C1936}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7298C746-69F8-45A3-8A35-4513081C1936}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs
index 0d69b4d..1fb0248 100644
--- a/NetSdrClientApp/Messages/NetSdrMessageHelper.cs
+++ b/NetSdrClientApp/Messages/NetSdrMessageHelper.cs
@@ -83,7 +83,7 @@ public static bool TranslateMessage(byte[] msg, out MsgTypes type, out ControlIt
msgEnumarable = msgEnumarable.Skip(_msgControlItemLength);
msgLength -= _msgControlItemLength;
- if (Enum.IsDefined(typeof(ControlItemCodes), value))
+ if (Enum.IsDefined(typeof(ControlItemCodes), (int)value))
{
itemCode = (ControlItemCodes)value;
}
diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs
index b0a7c05..8d55e99 100644
--- a/NetSdrClientApp/NetSdrClient.cs
+++ b/NetSdrClientApp/NetSdrClient.cs
@@ -1,4 +1,4 @@
-using NetSdrClientApp.Messages;
+using NetSdrClientApp.Messages;
using NetSdrClientApp.Networking;
using System;
using System.Collections.Generic;
@@ -14,8 +14,8 @@ namespace NetSdrClientApp
{
public class NetSdrClient
{
- private ITcpClient _tcpClient;
- private IUdpClient _udpClient;
+ private readonly ITcpClient _tcpClient;
+ private readonly IUdpClient _udpClient;
public bool IQStarted { get; set; }
@@ -66,7 +66,7 @@ public async Task StartIQAsync()
return;
}
-; var iqDataMode = (byte)0x80;
+ var iqDataMode = (byte)0x80;
var start = (byte)0x02;
var fifo16bitCaptureMode = (byte)0x01;
var n = (byte)1;
@@ -116,7 +116,7 @@ public async Task ChangeFrequencyAsync(long hz, int channel)
private void _udpClient_MessageReceived(object? sender, byte[] e)
{
- NetSdrMessageHelper.TranslateMessage(e, out MsgTypes type, out ControlItemCodes code, out ushort sequenceNum, out byte[] body);
+ NetSdrMessageHelper.TranslateMessage(e, out _, out _, out _, out byte[] body);
var samples = NetSdrMessageHelper.GetSamples(16, body);
Console.WriteLine($"Samples recieved: " + body.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}"));
@@ -131,14 +131,14 @@ private void _udpClient_MessageReceived(object? sender, byte[] e)
}
}
- private TaskCompletionSource responseTaskSource;
+ private TaskCompletionSource? responseTaskSource;
private async Task SendTcpRequest(byte[] msg)
{
if (!_tcpClient.Connected)
{
Console.WriteLine("No active connection.");
- return null;
+ return Array.Empty();
}
responseTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs
index 1f37e2e..f18a547 100644
--- a/NetSdrClientApp/Networking/TcpClientWrapper.cs
+++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -87,15 +87,7 @@ public async Task SendMessageAsync(byte[] data)
public async Task SendMessageAsync(string str)
{
var data = Encoding.UTF8.GetBytes(str);
- if (Connected && _stream != null && _stream.CanWrite)
- {
- Console.WriteLine($"Message sent: " + data.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}"));
- await _stream.WriteAsync(data, 0, data.Length);
- }
- else
- {
- throw new InvalidOperationException("Not connected to a server.");
- }
+ await SendMessageAsync(data);
}
private async Task StartListeningAsync()
diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs
index 31e0b79..9ccc733 100644
--- a/NetSdrClientApp/Networking/UdpClientWrapper.cs
+++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
@@ -61,16 +61,7 @@ public void StopListening()
public void Exit()
{
- try
- {
- _cts?.Cancel();
- _udpClient?.Close();
- Console.WriteLine("Stopped listening for UDP messages.");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Error while stopping: {ex.Message}");
- }
+ StopListening();
}
public override int GetHashCode()
@@ -82,4 +73,4 @@ public override int GetHashCode()
return BitConverter.ToInt32(hash, 0);
}
-}
\ No newline at end of file
+}
diff --git a/NetSdrClientAppTests/ArchitectureTests.cs b/NetSdrClientAppTests/ArchitectureTests.cs
new file mode 100644
index 0000000..fa0da2d
--- /dev/null
+++ b/NetSdrClientAppTests/ArchitectureTests.cs
@@ -0,0 +1,32 @@
+using NetArchTest.Rules;
+using NetSdrClientApp;
+using NUnit.Framework;
+
+namespace NetSdrClientAppTests
+{
+ [TestFixture]
+ public class ArchitectureTests
+ {
+ [Test]
+ public void Messages_ShouldNotDependOn_Networking()
+ {
+ var result = Types.InAssembly(typeof(NetSdrClient).Assembly)
+ .That().ResideInNamespace("NetSdrClientApp.Messages")
+ .ShouldNot().HaveDependencyOn("NetSdrClientApp.Networking")
+ .GetResult();
+
+ Assert.That(result.IsSuccessful, Is.True, "Architectural error: The Messages module should not know about the Networking module!");
+ }
+
+ [Test]
+ public void Interfaces_ShouldStartWith_I()
+ {
+ var result = Types.InAssembly(typeof(NetSdrClient).Assembly)
+ .That().AreInterfaces()
+ .Should().HaveNameStartingWith("I")
+ .GetResult();
+
+ Assert.That(result.IsSuccessful, Is.True, "All interfaces must begin with the letter 'I'.");
+ }
+ }
+}
diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj
index 3cbc46a..470d4fa 100644
--- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj
+++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj
@@ -10,9 +10,15 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
@@ -26,4 +32,4 @@
-
+
\ No newline at end of file
diff --git a/NetSdrClientAppTests/NetSdrClientTests.cs b/NetSdrClientAppTests/NetSdrClientTests.cs
index ad00c4f..562ae56 100644
--- a/NetSdrClientAppTests/NetSdrClientTests.cs
+++ b/NetSdrClientAppTests/NetSdrClientTests.cs
@@ -1,6 +1,8 @@
-using Moq;
+using Moq;
using NetSdrClientApp;
using NetSdrClientApp.Networking;
+using System.IO;
+using System.Threading.Tasks;
namespace NetSdrClientAppTests;
@@ -35,6 +37,15 @@ public void Setup()
_client = new NetSdrClient(_tcpMock.Object, _updMock.Object);
}
+
+ [TearDown]
+ public void TearDown()
+ {
+ if (File.Exists("samples.bin"))
+ {
+ File.Delete("samples.bin");
+ }
+ }
[Test]
public async Task ConnectAsyncTest()
@@ -61,7 +72,7 @@ public async Task DisconnectWithNoConnectionTest()
[Test]
public async Task DisconnectTest()
{
- //Arrange
+ //Arrange
await ConnectAsyncTest();
//act
@@ -75,7 +86,6 @@ public async Task DisconnectTest()
[Test]
public async Task StartIQNoConnectionTest()
{
-
//act
await _client.StartIQAsync();
@@ -88,7 +98,7 @@ public async Task StartIQNoConnectionTest()
[Test]
public async Task StartIQTest()
{
- //Arrange
+ //Arrange
await ConnectAsyncTest();
//act
@@ -103,7 +113,7 @@ public async Task StartIQTest()
[Test]
public async Task StopIQTest()
{
- //Arrange
+ //Arrange
await ConnectAsyncTest();
//act
@@ -115,5 +125,92 @@ public async Task StopIQTest()
Assert.That(_client.IQStarted, Is.False);
}
- //TODO: cover the rest of the NetSdrClient code here
+ [Test]
+ public async Task ConnectAsync_WhenAlreadyConnected_ShouldNotSendMessages()
+ {
+ // Arrange
+ _tcpMock.Setup(tcp => tcp.Connected).Returns(true);
+
+ // Act
+ await _client.ConnectAsync();
+
+ // Assert
+ _tcpMock.Verify(tcp => tcp.Connect(), Times.Never);
+ _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never);
+ }
+
+ [Test]
+ public async Task ChangeFrequencyAsync_WhenNoConnection_ShouldNotSendMessage()
+ {
+ // Arrange
+ _tcpMock.Setup(tcp => tcp.Connected).Returns(false);
+
+ // Act
+ await _client.ChangeFrequencyAsync(14000000, 0);
+
+ // Assert
+ _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never);
+ }
+
+ [Test]
+ public async Task ChangeFrequencyAsync_WhenConnected_ShouldSendMessage()
+ {
+ // Arrange
+ await _client.ConnectAsync();
+
+ // Act
+ await _client.ChangeFrequencyAsync(14000000, 0);
+
+ // Assert
+ _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Exactly(4));
+ }
+
+ [Test]
+ public void TcpClient_MessageReceived_Unsolicited_DoesNotThrow()
+ {
+ // Arrange
+ byte[] dummyMessage = { 0x00, 0x01, 0x02 };
+
+ // Act & Assert
+ Assert.DoesNotThrow(() =>
+ {
+ _tcpMock.Raise(tcp => tcp.MessageReceived += null, this, dummyMessage);
+ });
+ }
+
+ [Test]
+ public void NetSdrClient_InitialState_IQStartedIsFalse()
+ {
+ Assert.That(_client.IQStarted, Is.False);
+ }
+
+ [Test]
+ public async Task StopIQNoConnectionTest()
+ {
+ // Act
+ await _client.StopIQAsync();
+
+ // Assert
+ _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never);
+ _tcpMock.VerifyGet(tcp => tcp.Connected, Times.AtLeastOnce);
+ }
+
+ [Test]
+ public void UdpClient_MessageReceived_ShouldWriteToFile()
+ {
+ // Arrange
+ byte[] dummyPacket = new byte[64];
+
+ dummyPacket[0] = 64;
+ dummyPacket[1] = 0;
+
+ // Act
+ Assert.DoesNotThrow(() =>
+ {
+ _updMock.Raise(udp => udp.MessageReceived += null, this, dummyPacket);
+ });
+
+ // Assert
+ Assert.That(File.Exists("samples.bin"), Is.True, "Файл samples.bin має бути створений");
+ }
}
diff --git a/NetSdrClientAppTests/TcpClientWrapperTests.cs b/NetSdrClientAppTests/TcpClientWrapperTests.cs
new file mode 100644
index 0000000..a78a854
--- /dev/null
+++ b/NetSdrClientAppTests/TcpClientWrapperTests.cs
@@ -0,0 +1,77 @@
+using NUnit.Framework;
+using NetSdrClientApp.Networking;
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+
+namespace NetSdrClientAppTests
+{
+ [TestFixture]
+ public class TcpClientWrapperTests
+ {
+ [Test]
+ public void Connected_InitialState_ShouldBeFalse()
+ {
+ var wrapper = new TcpClientWrapper("127.0.0.1", 12345);
+ Assert.That(wrapper.Connected, Is.False);
+ }
+
+ [Test]
+ public void Disconnect_WhenNotConnected_ShouldNotThrow()
+ {
+ var wrapper = new TcpClientWrapper("127.0.0.1", 12345);
+ Assert.DoesNotThrow(() => wrapper.Disconnect());
+ }
+
+ [Test]
+ public void SendMessageAsync_Bytes_WhenNotConnected_ShouldThrowInvalidOperationException()
+ {
+ var wrapper = new TcpClientWrapper("127.0.0.1", 12345);
+ Assert.ThrowsAsync(async () => await wrapper.SendMessageAsync(new byte[] { 1, 2, 3 }));
+ }
+
+ [Test]
+ public void SendMessageAsync_String_WhenNotConnected_ShouldThrowInvalidOperationException()
+ {
+ var wrapper = new TcpClientWrapper("127.0.0.1", 12345);
+ Assert.ThrowsAsync(async () => await wrapper.SendMessageAsync("Hello"));
+ }
+
+ [Test]
+ public void Connect_WhenServerIsDown_ShouldCatchExceptionAndRemainDisconnected()
+ {
+ var wrapper = new TcpClientWrapper("127.0.0.1", 55555);
+ wrapper.Connect();
+
+ Assert.That(wrapper.Connected, Is.False);
+ }
+
+ [Test]
+ public async Task ConnectAndDisconnect_WithRealLocalServer_ShouldChangeState()
+ {
+ // Arrange
+ var listener = new TcpListener(IPAddress.Loopback, 0);
+ listener.Start();
+ int port = ((IPEndPoint)listener.LocalEndpoint).Port;
+
+ var wrapper = new TcpClientWrapper("127.0.0.1", port);
+
+ // Act
+ wrapper.Connect();
+
+ using var client = await listener.AcceptTcpClientAsync();
+
+ // Assert
+ Assert.That(wrapper.Connected, Is.True);
+
+ // Act
+ wrapper.Disconnect();
+
+ // Assert
+ Assert.That(wrapper.Connected, Is.False);
+
+ listener.Stop();
+ }
+ }
+}
diff --git a/NetSdrClientAppTests/UdpClientWrapperTests.cs b/NetSdrClientAppTests/UdpClientWrapperTests.cs
new file mode 100644
index 0000000..17e8f9e
--- /dev/null
+++ b/NetSdrClientAppTests/UdpClientWrapperTests.cs
@@ -0,0 +1,52 @@
+using NUnit.Framework;
+using NetSdrClientApp.Networking;
+using System.Threading.Tasks;
+
+namespace NetSdrClientAppTests
+{
+ [TestFixture]
+ public class UdpClientWrapperTests
+ {
+ [Test]
+ public void StopListening_WhenNotStarted_ShouldNotThrow()
+ {
+ var wrapper = new UdpClientWrapper(0);
+ Assert.DoesNotThrow(() => wrapper.StopListening());
+ }
+
+ [Test]
+ public void Exit_WhenNotStarted_ShouldNotThrow()
+ {
+ var wrapper = new UdpClientWrapper(0);
+ Assert.DoesNotThrow(() => wrapper.Exit());
+ }
+
+ [Test]
+ public void GetHashCode_ShouldReturnConsistentValueForSameEndpoint()
+ {
+ // Arrange
+ var wrapper1 = new UdpClientWrapper(12348);
+ var wrapper2 = new UdpClientWrapper(12348);
+
+ // Act & Assert
+ Assert.That(wrapper1.GetHashCode(), Is.EqualTo(wrapper2.GetHashCode()));
+ }
+
+ [Test]
+ public async Task StartAndStopListening_ShouldHandleCancellationGracefully()
+ {
+ // Arrange
+ var wrapper = new UdpClientWrapper(0);
+
+ // Act
+ var listenTask = wrapper.StartListeningAsync();
+
+ await Task.Delay(50);
+
+ wrapper.StopListening();
+
+ // Assert
+ Assert.DoesNotThrowAsync(async () => await listenTask);
+ }
+ }
+}
diff --git a/README.md b/README.md
index b3a9029..5da61fc 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,15 @@
# Лабораторні з реінжинірингу (8×)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
-[](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
+[](https://sonarcloud.io/summary/new_code?id=Dandddyy_ReengineeringCourse)
Цей репозиторій використовується для курсу **реінжиніринг ПЗ**.