diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index e784069..db9518c 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -44,6 +44,8 @@ jobs:
runs-on: windows-latest # безпечно для будь-яких .NET проектів
steps:
- uses: actions/checkout@v4
+ env:
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
with: { fetch-depth: 0 }
- uses: actions/setup-dotnet@v4
@@ -56,27 +58,27 @@ jobs:
dotnet tool install --global dotnet-sonarscanner
echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
dotnet sonarscanner begin `
- /k:"ppanchen_NetSdrClient" `
- /o:"ppanchen" `
+ /k:"MinTins_ReengineeringCourse" `
+ /o:"roman-flakei" `
/d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
/d:sonar.cs.opencover.reportsPaths="**/coverage.xml" `
/d:sonar.cpd.cs.minimumTokens=40 `
/d:sonar.cpd.cs.minimumLines=5 `
/d:sonar.exclusions=**/bin/**,**/obj/**,**/sonarcloud.yml `
- /d:sonar.qualitygate.wait=true
+ /d:sonar.qualitygate.wait=false
shell: pwsh
# 2) BUILD & TEST
- name: Restore
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 NetSdrClientAppTests/NetSdrClientAppTests.csproj -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.csproj b/EchoTcpServer/EchoServer.csproj
index 2150e37..ed9781c 100644
--- a/EchoTcpServer/EchoServer.csproj
+++ b/EchoTcpServer/EchoServer.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net10.0
enable
enable
diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs
index 5966c57..fab6e1f 100644
--- a/EchoTcpServer/Program.cs
+++ b/EchoTcpServer/Program.cs
@@ -13,7 +13,7 @@ public class EchoServer
{
private readonly int _port;
private TcpListener _listener;
- private CancellationTokenSource _cancellationTokenSource;
+ private readonly CancellationTokenSource _cancellationTokenSource;
public EchoServer(int port)
diff --git a/NetSdrArchTests/ArchitectureTests.cs b/NetSdrArchTests/ArchitectureTests.cs
new file mode 100644
index 0000000..9a102a2
--- /dev/null
+++ b/NetSdrArchTests/ArchitectureTests.cs
@@ -0,0 +1,109 @@
+using NetArchTest.Rules;
+using NUnit.Framework;
+
+namespace NetSdrArchTests
+{
+ ///
+ /// Lab 5 — Architectural rules using NetArchTest.
+ /// These tests enforce dependency constraints between layers.
+ ///
+ /// Note on project structure:
+ /// - TcpClientWrapper, ITcpClient → namespace NetSdrClientApp.Networking
+ /// - UdpClientWrapper, IUdpClient → global namespace (no namespace declaration)
+ /// Rules are written to reflect this actual structure.
+ ///
+ public class ArchitectureTests
+ {
+ private static readonly System.Reflection.Assembly AppAssembly =
+ typeof(NetSdrClientApp.Networking.ITcpClient).Assembly;
+
+ // ---------------------------------------------------------------
+ // Rule 1: Networking namespace must NOT depend on Messages namespace
+ // Rationale: networking is infrastructure — must not know about
+ // domain-level message construction
+ // ---------------------------------------------------------------
+ [Test]
+ public void Networking_ShouldNotDependOn_Messages()
+ {
+ var result = Types
+ .InAssembly(AppAssembly)
+ .That()
+ .ResideInNamespace("NetSdrClientApp.Networking")
+ .ShouldNot()
+ .HaveDependencyOn("NetSdrClientApp.Messages")
+ .GetResult();
+
+ Assert.That(result.IsSuccessful, Is.True,
+ "Networking layer must not depend on Messages layer. " +
+ "Failing types: " + string.Join(", ", result.FailingTypeNames ?? []));
+ }
+
+ // ---------------------------------------------------------------
+ // Rule 2: Messages namespace must NOT depend on Networking namespace
+ // Rationale: message building is pure domain logic,
+ // independent of transport implementation
+ // ---------------------------------------------------------------
+ [Test]
+ public void Messages_ShouldNotDependOn_Networking()
+ {
+ var result = Types
+ .InAssembly(AppAssembly)
+ .That()
+ .ResideInNamespace("NetSdrClientApp.Messages")
+ .ShouldNot()
+ .HaveDependencyOn("NetSdrClientApp.Networking")
+ .GetResult();
+
+ Assert.That(result.IsSuccessful, Is.True,
+ "Messages layer must not depend on Networking layer. " +
+ "Failing types: " + string.Join(", ", result.FailingTypeNames ?? []));
+ }
+
+ // ---------------------------------------------------------------
+ // Rule 3: TcpClientWrapper must implement ITcpClient
+ // Rationale: TCP networking component must be abstracted
+ // behind interface for testability and substitutability
+ // Note: scoped to ITcpClient only because UdpClientWrapper/IUdpClient
+ // reside in global namespace (outside NetSdrClientApp.Networking)
+ // ---------------------------------------------------------------
+ [Test]
+ public void TcpClientWrapper_ShouldImplement_ITcpClient()
+ {
+ var result = Types
+ .InAssembly(AppAssembly)
+ .That()
+ .ResideInNamespace("NetSdrClientApp.Networking")
+ .And()
+ .AreClasses()
+ .Should()
+ .ImplementInterface(typeof(NetSdrClientApp.Networking.ITcpClient))
+ .GetResult();
+
+ Assert.That(result.IsSuccessful, Is.True,
+ "All concrete classes in NetSdrClientApp.Networking must implement ITcpClient. " +
+ "Failing types: " + string.Join(", ", result.FailingTypeNames ?? []));
+ }
+
+ // ---------------------------------------------------------------
+ // Rule 4: NetSdrClient orchestrator must reside in root namespace
+ // Rationale: the top-level client must not leak into sub-layers
+ // ---------------------------------------------------------------
+ [Test]
+ public void NetSdrClient_ShouldResideIn_RootNamespace()
+ {
+ var result = Types
+ .InAssembly(AppAssembly)
+ .That()
+ .HaveNameStartingWith("NetSdrClient")
+ .And()
+ .AreClasses()
+ .Should()
+ .ResideInNamespace("NetSdrClientApp")
+ .GetResult();
+
+ Assert.That(result.IsSuccessful, Is.True,
+ "NetSdrClient class must reside in root NetSdrClientApp namespace. " +
+ "Failing types: " + string.Join(", ", result.FailingTypeNames ?? []));
+ }
+ }
+}
diff --git a/NetSdrArchTests/NetSdrArchTests.csproj b/NetSdrArchTests/NetSdrArchTests.csproj
new file mode 100644
index 0000000..e26d559
--- /dev/null
+++ b/NetSdrArchTests/NetSdrArchTests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net10.0
+ latest
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/NetSdrClient.sln b/NetSdrClient.sln
index 42431fb..2cae7ca 100644
--- a/NetSdrClient.sln
+++ b/NetSdrClient.sln
@@ -9,24 +9,66 @@ 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}") = "NetSdrArchTests", "NetSdrArchTests\NetSdrArchTests.csproj", "{23A9A89D-B1B6-495C-B709-3E3C88B0B957}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|x64.Build.0 = Debug|Any CPU
+ {3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3CA739B1-888F-4B84-8E76-387640E3B3E9}.Debug|x86.Build.0 = Debug|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|x64.ActiveCfg = Release|Any CPU
+ {3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|x64.Build.0 = Release|Any CPU
+ {3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|x86.ActiveCfg = Release|Any CPU
+ {3CA739B1-888F-4B84-8E76-387640E3B3E9}.Release|x86.Build.0 = Release|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|x64.Build.0 = Debug|Any CPU
+ {D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Debug|x86.Build.0 = Debug|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|x64.ActiveCfg = Release|Any CPU
+ {D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|x64.Build.0 = Release|Any CPU
+ {D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|x86.ActiveCfg = Release|Any CPU
+ {D0155366-89B4-4BA4-90E2-2ECC8C1EB915}.Release|x86.Build.0 = Release|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|x64.Build.0 = Debug|Any CPU
+ {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Debug|x86.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
+ {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|x64.ActiveCfg = Release|Any CPU
+ {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|x64.Build.0 = Release|Any CPU
+ {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|x86.ActiveCfg = Release|Any CPU
+ {9179F2F7-EBEE-4A5D-9FD9-F6E3C18DD263}.Release|x86.Build.0 = Release|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|x64.Build.0 = Debug|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Debug|x86.Build.0 = Debug|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|Any CPU.Build.0 = Release|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|x64.ActiveCfg = Release|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|x64.Build.0 = Release|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|x86.ActiveCfg = Release|Any CPU
+ {23A9A89D-B1B6-495C-B709-3E3C88B0B957}.Release|x86.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..f0a3cc0 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; }
@@ -60,13 +60,9 @@ public void Disconect()
public async Task StartIQAsync()
{
- if (!_tcpClient.Connected)
- {
- Console.WriteLine("No active connection.");
- return;
- }
+ if (!EnsureConnected()) return;
-; var iqDataMode = (byte)0x80;
+ var iqDataMode = (byte)0x80;
var start = (byte)0x02;
var fifo16bitCaptureMode = (byte)0x01;
var n = (byte)1;
@@ -84,11 +80,7 @@ public async Task StartIQAsync()
public async Task StopIQAsync()
{
- if (!_tcpClient.Connected)
- {
- Console.WriteLine("No active connection.");
- return;
- }
+ if (!EnsureConnected()) return;
var stop = (byte)0x01;
@@ -114,9 +106,17 @@ public async Task ChangeFrequencyAsync(long hz, int channel)
await SendTcpRequest(msg);
}
+
+ private bool EnsureConnected()
+ {
+ if (_tcpClient.Connected) return true;
+ Console.WriteLine("No active connection.");
+ return false;
+ }
+
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}"));
@@ -135,11 +135,7 @@ private void _udpClient_MessageReceived(object? sender, byte[] e)
private async Task SendTcpRequest(byte[] msg)
{
- if (!_tcpClient.Connected)
- {
- Console.WriteLine("No active connection.");
- return null;
- }
+ if (!EnsureConnected()) return null;
responseTaskSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var responseTask = responseTaskSource.Task;
diff --git a/NetSdrClientApp/NetSdrClientApp.csproj b/NetSdrClientApp/NetSdrClientApp.csproj
index 2ac9100..9cebf4e 100644
--- a/NetSdrClientApp/NetSdrClientApp.csproj
+++ b/NetSdrClientApp/NetSdrClientApp.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net10.0
enable
enable
diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs
index 1f37e2e..71eb77f 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;
@@ -7,13 +7,15 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
namespace NetSdrClientApp.Networking
{
+ [ExcludeFromCodeCoverage]
public class TcpClientWrapper : ITcpClient
{
- private string _host;
- private int _port;
+ private readonly string _host;
+ private readonly int _port;
private TcpClient? _tcpClient;
private NetworkStream? _stream;
private CancellationTokenSource _cts;
@@ -87,15 +89,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()
@@ -117,7 +111,7 @@ private async Task StartListeningAsync()
}
}
}
- catch (OperationCanceledException ex)
+ catch (OperationCanceledException)
{
//empty
}
diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs
index 31e0b79..a32c4b7 100644
--- a/NetSdrClientApp/Networking/UdpClientWrapper.cs
+++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs
@@ -1,11 +1,13 @@
-using System;
+using System;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Diagnostics.CodeAnalysis;
+[ExcludeFromCodeCoverage]
public class UdpClientWrapper : IUdpClient
{
private readonly IPEndPoint _localEndPoint;
@@ -61,16 +63,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()
diff --git a/NetSdrClientApp/Program.cs b/NetSdrClientApp/Program.cs
index fda2e69..9577a7d 100644
--- a/NetSdrClientApp/Program.cs
+++ b/NetSdrClientApp/Program.cs
@@ -1,4 +1,7 @@
-using NetSdrClientApp;
+//
+// Excluded from coverage - entry point only
+
+using NetSdrClientApp;
using NetSdrClientApp.Networking;
Console.WriteLine(@"Usage:
diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj
index 3cbc46a..e0b6e01 100644
--- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj
+++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
@@ -11,7 +11,11 @@
-
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
diff --git a/NetSdrClientAppTests/NetSdrClientTests.cs b/NetSdrClientAppTests/NetSdrClientTests.cs
index ad00c4f..936a525 100644
--- a/NetSdrClientAppTests/NetSdrClientTests.cs
+++ b/NetSdrClientAppTests/NetSdrClientTests.cs
@@ -115,5 +115,64 @@ public async Task StopIQTest()
Assert.That(_client.IQStarted, Is.False);
}
- //TODO: cover the rest of the NetSdrClient code here
+ [Test]
+ public async Task StopIQNoConnectionTest()
+ {
+ // act
+ await _client.StopIQAsync();
+
+ // assert — без з'єднання StopListening не викликається
+ _updMock.Verify(udp => udp.StopListening(), Times.Never);
+ Assert.That(_client.IQStarted, Is.False);
+ }
+
+ [Test]
+ public async Task ChangeFrequencyAsyncTest()
+ {
+ // Arrange
+ await ConnectAsyncTest();
+
+ // Act
+ await _client.ChangeFrequencyAsync(20000000, 1);
+
+ // Assert
+ _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Exactly(4)); // 3 з connect + 1
+ }
+
+ [Test]
+ public async Task ChangeFrequencyNoConnectionTest()
+ {
+ // Act
+ await _client.ChangeFrequencyAsync(20000000, 1);
+
+ // Assert
+ _tcpMock.Verify(tcp => tcp.SendMessageAsync(It.IsAny()), Times.Never);
+ }
+
+ [Test]
+ public async Task StartIQSetsIQStartedTrueTest()
+ {
+ // Arrange
+ await ConnectAsyncTest();
+
+ // Act
+ await _client.StartIQAsync();
+
+ // Assert
+ Assert.That(_client.IQStarted, Is.True);
+ }
+
+ [Test]
+ public async Task StopIQSetsIQStartedFalseTest()
+ {
+ // Arrange
+ await ConnectAsyncTest();
+ await _client.StartIQAsync();
+
+ // Act
+ await _client.StopIQAsync();
+
+ // Assert
+ Assert.That(_client.IQStarted, Is.False);
+ }
}
diff --git a/NetSdrClientAppTests/NetSdrMessageHelperTests.cs b/NetSdrClientAppTests/NetSdrMessageHelperTests.cs
index b40fff7..dfc34ce 100644
--- a/NetSdrClientAppTests/NetSdrMessageHelperTests.cs
+++ b/NetSdrClientAppTests/NetSdrMessageHelperTests.cs
@@ -64,6 +64,190 @@ public void GetDataItemMessageTest()
Assert.That(parametersBytes.Count(), Is.EqualTo(parametersLength));
}
- //TODO: add more NetSdrMessageHelper tests
+ [Test]
+ public void GetControlItemMessage_LengthIsCorrect()
+ {
+ var msg = NetSdrMessageHelper.GetControlItemMessage(
+ NetSdrMessageHelper.MsgTypes.SetControlItem,
+ NetSdrMessageHelper.ControlItemCodes.ReceiverFrequency,
+ new byte[6]);
+
+ Assert.That(msg.Length, Is.EqualTo(10)); // 2 header + 2 code + 6 params
+ }
+
+ [Test]
+ public void GetControlItemMessage_TypeIsEncodedCorrectly()
+ {
+ var type = NetSdrMessageHelper.MsgTypes.SetControlItem;
+ var msg = NetSdrMessageHelper.GetControlItemMessage(
+ type,
+ NetSdrMessageHelper.ControlItemCodes.ReceiverState,
+ new byte[4]);
+
+ var num = BitConverter.ToUInt16(msg.Take(2).ToArray());
+ var actualType = (NetSdrMessageHelper.MsgTypes)(num >> 13);
+
+ Assert.That(actualType, Is.EqualTo(type));
+ }
+
+ [Test]
+ public void GetDataItemMessage_EmptyParams_ReturnsHeaderOnly()
+ {
+ var msg = NetSdrMessageHelper.GetDataItemMessage(
+ NetSdrMessageHelper.MsgTypes.DataItem0,
+ new byte[0]);
+
+ Assert.That(msg.Length, Is.EqualTo(2)); // тільки header
+ }
+
+ [Test]
+ public void GetDataItemMessage_TypeEncodedCorrectly()
+ {
+ var type = NetSdrMessageHelper.MsgTypes.DataItem0;
+ var msg = NetSdrMessageHelper.GetDataItemMessage(type, new byte[100]);
+
+ var num = BitConverter.ToUInt16(msg.Take(2).ToArray());
+ var actualType = (NetSdrMessageHelper.MsgTypes)(num >> 13);
+
+ Assert.That(actualType, Is.EqualTo(type));
+ }
+
+ [Test]
+ public void TranslateMessage_ControlItem_ReturnsCorrectTypeAndCode()
+ {
+ // Arrange — побудувати повідомлення і одразу розібрати
+ var type = NetSdrMessageHelper.MsgTypes.SetControlItem;
+ var code = NetSdrMessageHelper.ControlItemCodes.ReceiverFrequency;
+ var parameters = new byte[] { 0x01, 0x02, 0x03 };
+ var msg = NetSdrMessageHelper.GetControlItemMessage(type, code, parameters);
+
+ // Act
+ var success = NetSdrMessageHelper.TranslateMessage(msg, out var outType, out var outCode, out var outSeq, out var body);
+
+ // Assert
+ Assert.That(success, Is.True);
+ Assert.That(outType, Is.EqualTo(type));
+ Assert.That(outCode, Is.EqualTo(code));
+ Assert.That(outSeq, Is.EqualTo(0));
+ Assert.That(body, Is.EqualTo(parameters));
+ }
+
+ [Test]
+ public void TranslateMessage_DataItem_ReturnsCorrectTypeAndSequence()
+ {
+ // Arrange
+ var type = NetSdrMessageHelper.MsgTypes.DataItem0;
+ var parameters = new byte[] { 0x10, 0x20, 0x30, 0x40 };
+ var msg = NetSdrMessageHelper.GetDataItemMessage(type, parameters);
+
+ // Act
+ var success = NetSdrMessageHelper.TranslateMessage(msg, out var outType, out _, out var outSeq, out var body);
+
+ // Assert
+ Assert.That(success, Is.True);
+ Assert.That(outType, Is.EqualTo(type));
+ Assert.That(body.Length, Is.GreaterThan(0));
+ }
+
+ [Test]
+ public void TranslateMessage_AckType_ParsedCorrectly()
+ {
+ var type = NetSdrMessageHelper.MsgTypes.Ack;
+ var code = NetSdrMessageHelper.ControlItemCodes.ReceiverState;
+ var msg = NetSdrMessageHelper.GetControlItemMessage(type, code, new byte[2]);
+
+ var success = NetSdrMessageHelper.TranslateMessage(msg, out var outType, out var outCode, out _, out _);
+
+ Assert.That(success, Is.True);
+ Assert.That(outType, Is.EqualTo(type));
+ Assert.That(outCode, Is.EqualTo(code));
+ }
+
+ [Test]
+ public void GetSamples_16bit_ReturnsCorrectCount()
+ {
+ // Arrange — 8 байт = 4 семпли по 16 біт
+ var body = new byte[] { 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00 };
+
+ // Act
+ var samples = NetSdrMessageHelper.GetSamples(16, body).ToList();
+
+ // Assert
+ Assert.That(samples.Count, Is.EqualTo(4));
+ }
+
+ [Test]
+ public void GetSamples_16bit_ReturnsCorrectValues()
+ {
+ var body = new byte[] { 0x05, 0x00, 0x0A, 0x00 };
+
+ var samples = NetSdrMessageHelper.GetSamples(16, body).ToList();
+
+ Assert.That(samples[0], Is.EqualTo(5));
+ Assert.That(samples[1], Is.EqualTo(10));
+ }
+
+ [Test]
+ public void GetSamples_8bit_ReturnsCorrectCount()
+ {
+ var body = new byte[] { 0x01, 0x02, 0x03, 0x04 };
+
+ var samples = NetSdrMessageHelper.GetSamples(8, body).ToList();
+
+ Assert.That(samples.Count, Is.EqualTo(4));
+ }
+
+ [Test]
+ public void GetSamples_EmptyBody_ReturnsNoSamples()
+ {
+ var samples = NetSdrMessageHelper.GetSamples(16, new byte[0]).ToList();
+
+ Assert.That(samples.Count, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void GetSamples_InvalidSampleSize_ThrowsException()
+ {
+ // sampleSize > 32 біти (4 байти) — має кинути виняток
+ Assert.Throws(() =>
+ NetSdrMessageHelper.GetSamples(64, new byte[] { 0x01 }).ToList());
+ }
+
+ [Test]
+ public void GetHeader_MessageTooLong_ThrowsArgumentException()
+ {
+ // параметри довжиною більше ніж maxMessageLength
+ var hugeParams = new byte[9000];
+
+ Assert.Throws(() =>
+ NetSdrMessageHelper.GetControlItemMessage(
+ NetSdrMessageHelper.MsgTypes.SetControlItem,
+ NetSdrMessageHelper.ControlItemCodes.ReceiverState,
+ hugeParams));
+ }
+
+ [Test]
+ public void GetControlItemMessage_ZeroParams_LengthIsHeaderPlusCode()
+ {
+ var msg = NetSdrMessageHelper.GetControlItemMessage(
+ NetSdrMessageHelper.MsgTypes.CurrentControlItem,
+ NetSdrMessageHelper.ControlItemCodes.ADModes,
+ new byte[0]);
+
+ // 2 байти header + 2 байти code = 4
+ Assert.That(msg.Length, Is.EqualTo(4));
+ }
+
+ [Test]
+ public void GetDataItemMessage_NoneCode_NoCodeBytesInMessage()
+ {
+ var parameters = new byte[] { 0xAA, 0xBB };
+ var msg = NetSdrMessageHelper.GetDataItemMessage(
+ NetSdrMessageHelper.MsgTypes.DataItem1,
+ parameters);
+
+ // 2 байти header + 2 байти parameters = 4 (без code)
+ Assert.That(msg.Length, Is.EqualTo(4));
+ }
}
}
\ No newline at end of file