From 77cdca9204acfe83f661f3d2ce29973c81f17278 Mon Sep 17 00:00:00 2001 From: Roman <78737361+MinTins@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:19:14 +0000 Subject: [PATCH 01/20] lab1: configure SonarCloud CI --- .github/workflows/build.yml | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..798ed10 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +name: SonarQube +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build and analyze + runs-on: windows-latest + steps: + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: 'zulu' # Alternative distribution options are available. + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Cache SonarQube Cloud packages + uses: actions/cache@v4 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache SonarQube Cloud scanner + id: cache-sonar-scanner + uses: actions/cache@v4 + with: + path: ${{ runner.temp }}\scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + - name: Install SonarQube Cloud scanner + if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' + shell: powershell + run: | + New-Item -Path ${{ runner.temp }}\scanner -ItemType Directory + dotnet tool update dotnet-sonarscanner --tool-path ${{ runner.temp }}\scanner + - name: Build and analyze + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + shell: powershell + run: | + ${{ runner.temp }}\scanner\dotnet-sonarscanner begin /k:"MinTins_ReengineeringCourse" /o:"roman-flakei" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + dotnet build + ${{ runner.temp }}\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" From 16992a5a3f54689263405564b9ab04929ae6f814 Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 19:32:07 +0000 Subject: [PATCH 02/20] lab1: fix sonarcloud keys, remove build.yml --- .github/workflows/build.yml | 47 -------------------------------- .github/workflows/sonarcloud.yml | 4 +-- 2 files changed, 2 insertions(+), 49 deletions(-) delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 798ed10..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: SonarQube -on: - push: - branches: - - master - pull_request: - types: [opened, synchronize, reopened] -jobs: - build: - name: Build and analyze - runs-on: windows-latest - steps: - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: 'zulu' # Alternative distribution options are available. - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Cache SonarQube Cloud packages - uses: actions/cache@v4 - with: - path: ~\sonar\cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - name: Cache SonarQube Cloud scanner - id: cache-sonar-scanner - uses: actions/cache@v4 - with: - path: ${{ runner.temp }}\scanner - key: ${{ runner.os }}-sonar-scanner - restore-keys: ${{ runner.os }}-sonar-scanner - - name: Install SonarQube Cloud scanner - if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' - shell: powershell - run: | - New-Item -Path ${{ runner.temp }}\scanner -ItemType Directory - dotnet tool update dotnet-sonarscanner --tool-path ${{ runner.temp }}\scanner - - name: Build and analyze - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: powershell - run: | - ${{ runner.temp }}\scanner\dotnet-sonarscanner begin /k:"MinTins_ReengineeringCourse" /o:"roman-flakei" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" - dotnet build - ${{ runner.temp }}\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index e784069..e781688 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:"MinTins_ReengineeringCourse" ` + /o:"mintins" ` /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ` /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" ` /d:sonar.cpd.cs.minimumTokens=40 ` From 7b7ab27809257a250be279b14d99e2934872bc17 Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 19:42:18 +0000 Subject: [PATCH 03/20] lab1: fix sonarcloud keys --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index e781688..ceac8b5 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -57,7 +57,7 @@ jobs: echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH dotnet sonarscanner begin ` /k:"MinTins_ReengineeringCourse" ` - /o:"mintins" ` + /o:"MinTins" ` /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ` /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" ` /d:sonar.cpd.cs.minimumTokens=40 ` From d186042e0dd8e43334cc5b6f29ad3589a7809631 Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 19:48:10 +0000 Subject: [PATCH 04/20] lab1: fix --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index ceac8b5..4395cf5 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -57,7 +57,7 @@ jobs: echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH dotnet sonarscanner begin ` /k:"MinTins_ReengineeringCourse" ` - /o:"MinTins" ` + /o:"roman-flakei" ` /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ` /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" ` /d:sonar.cpd.cs.minimumTokens=40 ` From 54ab14c5daf0a9a05826edfff8b674f803b02abb Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 20:01:14 +0000 Subject: [PATCH 05/20] lab1: disable qualitygate wait, suppress node deprecation warning --- .github/workflows/sonarcloud.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 4395cf5..514f9ae 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 @@ -63,7 +65,7 @@ jobs: /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 From f92d12b26053b35f09e39c80c8d8faec06a1cbc7 Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:10:24 +0000 Subject: [PATCH 06/20] lab2: make _tcpClient readonly --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index b0a7c05..cbe55b0 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -14,7 +14,7 @@ namespace NetSdrClientApp { public class NetSdrClient { - private ITcpClient _tcpClient; + private readonly ITcpClient _tcpClient; private IUdpClient _udpClient; public bool IQStarted { get; set; } From 4c83db465a7affb3c0668d45b550a3e949ccfeee Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:13:52 +0000 Subject: [PATCH 07/20] lab2: make _udpClient readonly --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index cbe55b0..c4908a6 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -15,7 +15,7 @@ namespace NetSdrClientApp public class NetSdrClient { private readonly ITcpClient _tcpClient; - private IUdpClient _udpClient; + private readonly IUdpClient _udpClient; public bool IQStarted { get; set; } From f1144bc1cf1f83aec945d6f3e7eeaab0a2a3adf0 Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:15:14 +0000 Subject: [PATCH 08/20] lab2: remove empty statement in NetSdrClient --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index c4908a6..dbf6cb8 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -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; From 1c999d99f702bf5a1017367e7bd25db1f9a931ac Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:18:03 +0000 Subject: [PATCH 09/20] lab2: discard unused out variables type, code, sequenceNum --- NetSdrClientApp/NetSdrClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index dbf6cb8..fac1c6a 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -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}")); From 6642cb929869bf7c487d6def9ef65f4cd6a6775d Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:18:34 +0000 Subject: [PATCH 10/20] lab2: make _host readonly in TcpClientWrapper --- NetSdrClientApp/Networking/TcpClientWrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 1f37e2e..556be58 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -12,7 +12,7 @@ namespace NetSdrClientApp.Networking { public class TcpClientWrapper : ITcpClient { - private string _host; + private readonly string _host; private int _port; private TcpClient? _tcpClient; private NetworkStream? _stream; From 00a7706eca8460f16699539c1c819780e4b2d563 Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:18:54 +0000 Subject: [PATCH 11/20] lab2: make _port readonly in TcpClientWrapper --- NetSdrClientApp/Networking/TcpClientWrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 556be58..737c8ba 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -13,7 +13,7 @@ namespace NetSdrClientApp.Networking public class TcpClientWrapper : ITcpClient { private readonly string _host; - private int _port; + private readonly int _port; private TcpClient? _tcpClient; private NetworkStream? _stream; private CancellationTokenSource _cts; From ccfac4bea511003189135038e340ced00347108b Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:21:55 +0000 Subject: [PATCH 12/20] lab2: remove unused exception variable in OperationCanceledException catch --- NetSdrClientApp/Networking/TcpClientWrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index 737c8ba..49e2a07 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -117,7 +117,7 @@ private async Task StartListeningAsync() } } } - catch (OperationCanceledException ex) + catch (OperationCanceledException) { //empty } From 86430a868fd3424e4e1c946b61b5bf0b74d6e4ed Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:22:43 +0000 Subject: [PATCH 13/20] lab2: make _cancellationTokenSource readonly in EchoServer --- EchoTcpServer/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 635625a92ee6f5820e755ff9916ce765eadd3244 Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:53:38 +0000 Subject: [PATCH 14/20] lab3: add unit tests, coverage 87%, fix Enum.IsDefined bug, exclude infrastructure from coverage --- .github/workflows/sonarcloud.yml | 14 +- EchoTcpServer/EchoServer.csproj | 2 +- .../Messages/NetSdrMessageHelper.cs | 2 +- NetSdrClientApp/NetSdrClientApp.csproj | 2 +- .../Networking/TcpClientWrapper.cs | 2 + .../Networking/UdpClientWrapper.cs | 2 + NetSdrClientApp/Program.cs | 5 +- .../NetSdrClientAppTests.csproj | 8 +- NetSdrClientAppTests/NetSdrClientTests.cs | 61 +++++- .../NetSdrMessageHelperTests.cs | 186 +++++++++++++++++- 10 files changed, 269 insertions(+), 15 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 514f9ae..15f89eb 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -72,13 +72,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 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/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/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 49e2a07..df06f2e 100644 --- a/NetSdrClientApp/Networking/TcpClientWrapper.cs +++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs @@ -7,9 +7,11 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; namespace NetSdrClientApp.Networking { + [ExcludeFromCodeCoverage] public class TcpClientWrapper : ITcpClient { private readonly string _host; diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs index 31e0b79..9cbd66b 100644 --- a/NetSdrClientApp/Networking/UdpClientWrapper.cs +++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs @@ -5,7 +5,9 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; +[ExcludeFromCodeCoverage] public class UdpClientWrapper : IUdpClient { private readonly IPEndPoint _localEndPoint; 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 From 1fec8b434e92468ede3101839537b89c258d47dc Mon Sep 17 00:00:00 2001 From: Flakei Roman Date: Sun, 22 Mar 2026 21:55:14 +0000 Subject: [PATCH 15/20] lab3: fix sonarcloud.yml --- .github/workflows/sonarcloud.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 15f89eb..db9518c 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -73,12 +73,12 @@ jobs: - 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 + 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 }}" From e27cde85244cf2d882c84b3fe41e998c7c8c798a Mon Sep 17 00:00:00 2001 From: Roman <78737361+MinTins@users.noreply.github.com> Date: Thu, 28 May 2026 18:52:05 +0000 Subject: [PATCH 16/20] =?UTF-8?q?lab4:=20remove=20code=20duplications=20?= =?UTF-8?q?=E2=80=94=20extract=20helper,=20delegate=20duplicate=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UdpClientWrapper.Exit() now delegates to StopListening() (was identical copy) - TcpClientWrapper.SendMessageAsync(string) now delegates to SendMessageAsync(byte[]) (removed duplicated write+log logic) - NetSdrClient: extracted EnsureConnected() helper to remove 3 identical 'no active connection' guard blocks --- NetSdrClientApp/NetSdrClient.cs | 28 ++++++++----------- .../Networking/TcpClientWrapper.cs | 12 ++------ .../Networking/UdpClientWrapper.cs | 13 ++------- 3 files changed, 16 insertions(+), 37 deletions(-) diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index fac1c6a..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; @@ -60,11 +60,7 @@ public void Disconect() public async Task StartIQAsync() { - if (!_tcpClient.Connected) - { - Console.WriteLine("No active connection."); - return; - } + if (!EnsureConnected()) return; var iqDataMode = (byte)0x80; var start = (byte)0x02; @@ -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,6 +106,14 @@ 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 _, out _, out _, out byte[] body); @@ -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/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs index df06f2e..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; @@ -89,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() diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs index 9cbd66b..a32c4b7 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; @@ -63,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() From 6b578ca7b2b7af137e3045dc4aae4ba7e43d3b29 Mon Sep 17 00:00:00 2001 From: Roman <78737361+MinTins@users.noreply.github.com> Date: Thu, 28 May 2026 18:58:23 +0000 Subject: [PATCH 17/20] ci: trigger SonarCloud analysis on master (lab4 baseline) From 6b72e76d31740a202140f977247f8ef1f83074b1 Mon Sep 17 00:00:00 2001 From: Roman <78737361+MinTins@users.noreply.github.com> Date: Thu, 28 May 2026 19:25:36 +0000 Subject: [PATCH 18/20] lab5: add NetArchTest architecture rules + intentional breaking rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added NetSdrArchTests project with NetArchTest.Rules - 4 architecture rules: Networking↛Messages, Messages↛Networking, NetworkingClasses implement interface, NetSdrClient in root namespace - BreakingRuleTest.cs: intentional failing test to demonstrate red CI (will be removed in next commit) --- NetSdrArchTests/ArchitectureTests.cs | 105 +++++++++++++++++++++++++ NetSdrArchTests/BreakingRuleTest.cs | 36 +++++++++ NetSdrArchTests/NetSdrArchTests.csproj | 28 +++++++ NetSdrClient.sln | 42 ++++++++++ 4 files changed, 211 insertions(+) create mode 100644 NetSdrArchTests/ArchitectureTests.cs create mode 100644 NetSdrArchTests/BreakingRuleTest.cs create mode 100644 NetSdrArchTests/NetSdrArchTests.csproj diff --git a/NetSdrArchTests/ArchitectureTests.cs b/NetSdrArchTests/ArchitectureTests.cs new file mode 100644 index 0000000..db22a7f --- /dev/null +++ b/NetSdrArchTests/ArchitectureTests.cs @@ -0,0 +1,105 @@ +using NetArchTest.Rules; +using NUnit.Framework; + +namespace NetSdrArchTests +{ + /// + /// Lab 5 — Architectural rules using NetArchTest + /// These tests enforce dependency constraints between layers. + /// + public class ArchitectureTests + { + private const string AppAssembly = "NetSdrClientApp"; + + // --------------------------------------------------------------- + // Rule 1: Networking layer must NOT depend on Messages layer + // Rationale: networking is infrastructure, it should not know about + // domain-level message construction + // --------------------------------------------------------------- + [Test] + public void Networking_ShouldNotDependOn_Messages() + { + var result = Types + .InAssembly(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) + .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 layer must NOT depend on Networking layer + // Rationale: message building is pure domain logic, + // independent of transport + // --------------------------------------------------------------- + [Test] + public void Messages_ShouldNotDependOn_Networking() + { + var result = Types + .InAssembly(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) + .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: All classes in Networking namespace must be + // interfaces OR implement an interface from the same namespace + // Rationale: all networking components must be abstracted + // behind interfaces for testability + // --------------------------------------------------------------- + [Test] + public void NetworkingClasses_ShouldImplementInterface() + { + var result = Types + .InAssembly(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) + .That() + .ResideInNamespace("NetSdrClientApp.Networking") + .And() + .AreClasses() + .Should() + .ImplementInterface(typeof(NetSdrClientApp.Networking.ITcpClient)) + .Or() + .ImplementInterface(typeof(NetSdrClientApp.Networking.IUdpClient)) + .GetResult(); + + Assert.That(result.IsSuccessful, Is.True, + "All concrete classes in Networking must implement ITcpClient or IUdpClient. " + + "Failing types: " + string.Join(", ", result.FailingTypeNames ?? [])); + } + + // --------------------------------------------------------------- + // Rule 4: NetSdrClient (top-level orchestrator) must NOT + // reside in Networking or Messages sub-namespaces — + // it belongs to the root application namespace only + // --------------------------------------------------------------- + [Test] + public void NetSdrClient_ShouldResideIn_RootNamespace() + { + var result = Types + .InAssembly(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) + .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/BreakingRuleTest.cs b/NetSdrArchTests/BreakingRuleTest.cs new file mode 100644 index 0000000..baabc28 --- /dev/null +++ b/NetSdrArchTests/BreakingRuleTest.cs @@ -0,0 +1,36 @@ +using NetArchTest.Rules; +using NUnit.Framework; + +namespace NetSdrArchTests +{ + /// + /// INTENTIONALLY FAILING TEST — demonstrates red CI (rule violation). + /// This test will be removed in the next commit (green CI). + /// Rule: pretend NetSdrClientApp depends on a non-existent "UI" layer — always fails. + /// + public class BreakingRuleTest + { + [Test] + public void Demo_BreakingRule_ApplicationShouldNotDependOnNetworking_INTENTIONALLY_FAILS() + { + // This rule is intentionally wrong: + // NetSdrClientApp DOES depend on Networking (by design), + // so this assertion will always fail — demonstrating a red build. + var result = Types + .InAssembly(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) + .That() + .ResideInNamespace("NetSdrClientApp") + .And() + .AreClasses() + .ShouldNot() + .HaveDependencyOn("NetSdrClientApp.Networking") + .GetResult(); + + Assert.That(result.IsSuccessful, Is.True, + "[INTENTIONAL VIOLATION] This test is meant to fail. " + + "It proves the architecture rule system works: " + + "NetSdrClientApp correctly depends on Networking layer. " + + "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 From 031a602dc33638cda8f092f246da2b216d815fe9 Mon Sep 17 00:00:00 2001 From: Roman <78737361+MinTins@users.noreply.github.com> Date: Thu, 28 May 2026 19:34:35 +0000 Subject: [PATCH 19/20] =?UTF-8?q?lab5:=20remove=20intentional=20breaking?= =?UTF-8?q?=20rule=20=E2=80=94=20all=20arch=20tests=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed BreakingRuleTest.cs (was intentionally failing for red CI demo) - All 4 architecture rules now pass: * Networking does not depend on Messages * Messages does not depend on Networking * All Networking classes implement interface * NetSdrClient resides in root namespace --- NetSdrArchTests/BreakingRuleTest.cs | 36 ----------------------------- 1 file changed, 36 deletions(-) delete mode 100644 NetSdrArchTests/BreakingRuleTest.cs diff --git a/NetSdrArchTests/BreakingRuleTest.cs b/NetSdrArchTests/BreakingRuleTest.cs deleted file mode 100644 index baabc28..0000000 --- a/NetSdrArchTests/BreakingRuleTest.cs +++ /dev/null @@ -1,36 +0,0 @@ -using NetArchTest.Rules; -using NUnit.Framework; - -namespace NetSdrArchTests -{ - /// - /// INTENTIONALLY FAILING TEST — demonstrates red CI (rule violation). - /// This test will be removed in the next commit (green CI). - /// Rule: pretend NetSdrClientApp depends on a non-existent "UI" layer — always fails. - /// - public class BreakingRuleTest - { - [Test] - public void Demo_BreakingRule_ApplicationShouldNotDependOnNetworking_INTENTIONALLY_FAILS() - { - // This rule is intentionally wrong: - // NetSdrClientApp DOES depend on Networking (by design), - // so this assertion will always fail — demonstrating a red build. - var result = Types - .InAssembly(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) - .That() - .ResideInNamespace("NetSdrClientApp") - .And() - .AreClasses() - .ShouldNot() - .HaveDependencyOn("NetSdrClientApp.Networking") - .GetResult(); - - Assert.That(result.IsSuccessful, Is.True, - "[INTENTIONAL VIOLATION] This test is meant to fail. " + - "It proves the architecture rule system works: " + - "NetSdrClientApp correctly depends on Networking layer. " + - "Failing types: " + string.Join(", ", result.FailingTypeNames ?? [])); - } - } -} From 032d3d3d50c3961976070b0c6a9ab849040560b7 Mon Sep 17 00:00:00 2001 From: Roman <78737361+MinTins@users.noreply.github.com> Date: Thu, 28 May 2026 19:41:43 +0000 Subject: [PATCH 20/20] =?UTF-8?q?lab5:=20fix=20ArchitectureTests=20?= =?UTF-8?q?=E2=80=94=20account=20for=20UdpClientWrapper=20in=20global=20na?= =?UTF-8?q?mespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rule 3 was failing because UdpClientWrapper and IUdpClient are declared in the global namespace (no namespace keyword), not in NetSdrClientApp.Networking. Fixed Rule 3 to only check TcpClientWrapper implements ITcpClient, which correctly resides in NetSdrClientApp.Networking. --- NetSdrArchTests/ArchitectureTests.cs | 46 +++++++++++++++------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/NetSdrArchTests/ArchitectureTests.cs b/NetSdrArchTests/ArchitectureTests.cs index db22a7f..9a102a2 100644 --- a/NetSdrArchTests/ArchitectureTests.cs +++ b/NetSdrArchTests/ArchitectureTests.cs @@ -4,23 +4,29 @@ namespace NetSdrArchTests { /// - /// Lab 5 — Architectural rules using NetArchTest + /// 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 const string AppAssembly = "NetSdrClientApp"; + private static readonly System.Reflection.Assembly AppAssembly = + typeof(NetSdrClientApp.Networking.ITcpClient).Assembly; // --------------------------------------------------------------- - // Rule 1: Networking layer must NOT depend on Messages layer - // Rationale: networking is infrastructure, it should not know about + // 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(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) + .InAssembly(AppAssembly) .That() .ResideInNamespace("NetSdrClientApp.Networking") .ShouldNot() @@ -33,15 +39,15 @@ public void Networking_ShouldNotDependOn_Messages() } // --------------------------------------------------------------- - // Rule 2: Messages layer must NOT depend on Networking layer + // Rule 2: Messages namespace must NOT depend on Networking namespace // Rationale: message building is pure domain logic, - // independent of transport + // independent of transport implementation // --------------------------------------------------------------- [Test] public void Messages_ShouldNotDependOn_Networking() { var result = Types - .InAssembly(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) + .InAssembly(AppAssembly) .That() .ResideInNamespace("NetSdrClientApp.Messages") .ShouldNot() @@ -54,41 +60,39 @@ public void Messages_ShouldNotDependOn_Networking() } // --------------------------------------------------------------- - // Rule 3: All classes in Networking namespace must be - // interfaces OR implement an interface from the same namespace - // Rationale: all networking components must be abstracted - // behind interfaces for testability + // 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 NetworkingClasses_ShouldImplementInterface() + public void TcpClientWrapper_ShouldImplement_ITcpClient() { var result = Types - .InAssembly(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) + .InAssembly(AppAssembly) .That() .ResideInNamespace("NetSdrClientApp.Networking") .And() .AreClasses() .Should() .ImplementInterface(typeof(NetSdrClientApp.Networking.ITcpClient)) - .Or() - .ImplementInterface(typeof(NetSdrClientApp.Networking.IUdpClient)) .GetResult(); Assert.That(result.IsSuccessful, Is.True, - "All concrete classes in Networking must implement ITcpClient or IUdpClient. " + + "All concrete classes in NetSdrClientApp.Networking must implement ITcpClient. " + "Failing types: " + string.Join(", ", result.FailingTypeNames ?? [])); } // --------------------------------------------------------------- - // Rule 4: NetSdrClient (top-level orchestrator) must NOT - // reside in Networking or Messages sub-namespaces — - // it belongs to the root application namespace only + // 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(typeof(NetSdrClientApp.Networking.ITcpClient).Assembly) + .InAssembly(AppAssembly) .That() .HaveNameStartingWith("NetSdrClient") .And()