diff --git a/.github/workflows/coverlet.msbuild b/.github/workflows/coverlet.msbuild new file mode 100644 index 00000000..5f923fc2 --- /dev/null +++ b/.github/workflows/coverlet.msbuild @@ -0,0 +1,3 @@ +dotnet add NetSdrClientAppTests package coverlet.msbuild +dotnet add NetSdrClientAppTests package Microsoft.NET.Test.Sdk +dotnet test NetSdrClientAppTests -c Release /p:CollectCoverage=true /p:CoverletOutput=TestResults/coverage.xml /p:CoverletOutputFormat=opencover diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index e7840696..d23eba70 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -1,83 +1,92 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow helps you trigger a SonarCloud analysis of your code and populates -# GitHub Code Scanning alerts with the vulnerabilities found. -# Free for open source project. - -# 1. Login to SonarCloud.io using your GitHub account - -# 2. Import your project on SonarCloud -# * Add your GitHub organization first, then add your repository as a new project. -# * Please note that many languages are eligible for automatic analysis, -# which means that the analysis will start automatically without the need to set up GitHub Actions. -# * This behavior can be changed in Administration > Analysis Method. -# -# 3. Follow the SonarCloud in-product tutorial -# * a. Copy/paste the Project Key and the Organization Key into the args parameter below -# (You'll find this information in SonarCloud. Click on "Information" at the bottom left) -# -# * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN -# (On SonarCloud, click on your avatar on top-right > My account > Security -# or go directly to https://sonarcloud.io/account/security/) - -# Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/) -# or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9) - -name: SonarCloud analysis +name: SonarCloud Analysis on: push: branches: [ "master" ] pull_request: branches: [ "master" ] - workflow_dispatch: - -permissions: - pull-requests: read # allows SonarCloud to decorate PRs with analysis results jobs: - sonar-check: - name: Sonar Check - runs-on: windows-latest # безпечно для будь-яких .NET проектів + build-and-analyze: + name: Build and Analyze + runs-on: windows-latest + steps: - - uses: actions/checkout@v4 - with: { fetch-depth: 0 } - - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.x' - - # 1) BEGIN: SonarScanner for .NET - - name: SonarScanner Begin - run: | - dotnet tool install --global dotnet-sonarscanner - echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH - dotnet sonarscanner begin ` - /k:"ppanchen_NetSdrClient" ` - /o:"ppanchen" ` - /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ` - /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" ` + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '6.0.x' + + - name: Install SonarScanner + run: | + dotnet tool install --global dotnet-sonarscanner + echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH + + - name: Install Coverlet + run: dotnet tool install --global coverlet.console + + - name: Build and test with SonarCloud + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + # Start SonarScanner + dotnet sonarscanner begin ` + /k:"Yegres546_NetSdrClient" ` + /o:"yegres546" ` + /d:sonar.token="$env:SONAR_TOKEN" ` + /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" ` /d:sonar.cpd.cs.minimumTokens=40 ` /d:sonar.cpd.cs.minimumLines=5 ` - /d:sonar.exclusions=**/bin/**,**/obj/**,**/sonarcloud.yml ` + /d:sonar.exclusions="**/bin/**,**/obj/**,**/TestResults/**,**/*.Tests.cs" ` + /d:sonar.coverage.exclusions="**Test*.cs" ` /d:sonar.qualitygate.wait=true - 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 - # 3) END: SonarScanner - - name: SonarScanner End - run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" - shell: pwsh + + # Build the solution + dotnet build --configuration Release + + # Run tests with coverage + dotnet test --configuration Release ` + --no-build ` + --verbosity normal ` + --collect:"XPlat Code Coverage" ` + --results-directory ./TestResults ` + -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover + + # End SonarScanner + dotnet sonarscanner end /d:sonar.token="$env:SONAR_TOKEN" +name: Architecture Rules Validation + +on: + push: + branches: [ "lab5" ] + pull_request: + branches: [ "lab5" ] + +jobs: + architecture-tests: + name: Architecture Rules Validation + runs-on: windows-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '6.0.x' + + - name: Install dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore --configuration Release + + - name: Run Architecture Tests + run: dotnet test --configuration Release --no-build --verbosity normal --filter "Category=Architecture" + + - name: Run All Tests + run: dotnet test --configuration Release --no-build --verbosity normal diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs index 5966c579..7f47378b 100644 --- a/EchoTcpServer/Program.cs +++ b/EchoTcpServer/Program.cs @@ -47,7 +47,7 @@ public async Task StartAsync() Console.WriteLine("Server shutdown."); } - private async Task HandleClientAsync(TcpClient client, CancellationToken token) + private static async Task HandleClientAsync(TcpClient client, CancellationToken token) { using (NetworkStream stream = client.GetStream()) { @@ -112,21 +112,22 @@ public static async Task Main(string[] args) } } - -public class UdpTimedSender : IDisposable +namespace EchoServerNamespace { - private readonly string _host; - private readonly int _port; - private readonly UdpClient _udpClient; - private Timer _timer; - - public UdpTimedSender(string host, int port) + public class UdpTimedSender : IDisposable { - _host = host; - _port = port; - _udpClient = new UdpClient(); - } + 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) @@ -170,4 +171,4 @@ public void Dispose() StopSending(); _udpClient.Dispose(); } -} \ No newline at end of file +} diff --git a/Infrastructure/DatabaseService.cs b/Infrastructure/DatabaseService.cs new file mode 100644 index 00000000..2fcf4980 --- /dev/null +++ b/Infrastructure/DatabaseService.cs @@ -0,0 +1,10 @@ +namespace NetSdrClient.Infrastructure +{ + public class DatabaseService + { + public void SaveData(object data) + { + // Database operations + } + } +} diff --git a/Interfaces/IDeviceService.cs b/Interfaces/IDeviceService.cs new file mode 100644 index 00000000..b19adbd7 --- /dev/null +++ b/Interfaces/IDeviceService.cs @@ -0,0 +1,8 @@ +namespace NetSdrClient.Interfaces +{ + public interface IDeviceService + { + void Connect(); + void Disconnect(); + } +} diff --git a/NetSdrClient.Tests/ArchitectureTests.cs b/NetSdrClient.Tests/ArchitectureTests.cs new file mode 100644 index 00000000..a664fae4 --- /dev/null +++ b/NetSdrClient.Tests/ArchitectureTests.cs @@ -0,0 +1,148 @@ +using NetArchTest.Rules; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NetSdrClient.Tests +{ + [TestClass] + public class ArchitectureTests + { + private const string ApplicationNamespace = "NetSdrClient"; + private const string ModelsNamespace = "NetSdrClient.Models"; + private const string ServicesNamespace = "NetSdrClient.Services"; + private const string InterfacesNamespace = "NetSdrClient.Interfaces"; + private const string InfrastructureNamespace = "NetSdrClient.Infrastructure"; + private const string UINamespace = "NetSdrClient.UI"; + + [TestMethod] + public void ServicesLayer_ShouldNotDependOnUI() + { + // Arrange + var assembly = typeof(Services.SDRClient).Assembly; + + // Act + var result = Types + .InAssembly(assembly) + .That() + .ResideInNamespace(ServicesNamespace) + .ShouldNot() + .HaveDependencyOn(UINamespace) + .GetResult(); + + // Assert + Assert.IsTrue(result.IsSuccessful, + $"Services layer should not depend on UI: {string.Join(", ", result.FailingTypes)}"); + } + + [TestMethod] + public void Models_ShouldNotReferenceServices() + { + // Arrange + var assembly = typeof(Models.SDRDevice).Assembly; + + // Act + var result = Types + .InAssembly(assembly) + .That() + .ResideInNamespace(ModelsNamespace) + .ShouldNot() + .HaveDependencyOn(ServicesNamespace) + .GetResult(); + + // Assert + Assert.IsTrue(result.IsSuccessful, + $"Models should not depend on Services: {string.Join(", ", result.FailingTypes)}"); + } + + [TestMethod] + public void Interfaces_ShouldNotHaveDependencies() + { + // Arrange + var assembly = typeof(Services.SDRClient).Assembly; + + // Act + var result = Types + .InAssembly(assembly) + .That() + .ResideInNamespace(InterfacesNamespace) + .Should() + .NotHaveDependencyOnAny( + ServicesNamespace, + ModelsNamespace, + InfrastructureNamespace, + UINamespace) + .GetResult(); + + // Assert + Assert.IsTrue(result.IsSuccessful, + $"Interfaces should not have dependencies: {string.Join(", ", result.FailingTypes)}"); + } + + [TestMethod] + public void AllClasses_ShouldHaveNamesEndingWithService_IfInServicesNamespace() + { + // Arrange + var assembly = typeof(Services.SDRClient).Assembly; + + // Act + var result = Types + .InAssembly(assembly) + .That() + .ResideInNamespace(ServicesNamespace) + .And() + .AreClasses() + .Should() + .HaveNameEndingWith("Service") + .Or() + .HaveNameEndingWith("Client") + .Or() + .HaveNameEndingWith("Handler") + .GetResult(); + + // Assert + Assert.IsTrue(result.IsSuccessful, + $"Services should have proper naming: {string.Join(", ", result.FailingTypes)}"); + } + + [TestMethod] + public void Models_ShouldBeSealed() + { + // Arrange + var assembly = typeof(Models.SDRDevice).Assembly; + + // Act + var result = Types + .InAssembly(assembly) + .That() + .ResideInNamespace(ModelsNamespace) + .And() + .AreClasses() + .Should() + .BeSealed() + .GetResult(); + + // Assert + Assert.IsTrue(result.IsSuccessful, + $"Models should be sealed: {string.Join(", ", result.FailingTypes)}"); + } + + [TestMethod] + public void Services_ShouldNotDependOnInfrastructureDirectly() + { + // Arrange + var assembly = typeof(Services.SDRClient).Assembly; + + // Act + var result = Types + .InAssembly(assembly) + .That() + .ResideInNamespace(ServicesNamespace) + .ShouldNot() + .HaveDependencyOn(InfrastructureNamespace) + .GetResult(); + + // Assert + Assert.IsTrue(result.IsSuccessful, + $"Services should not depend directly on Infrastructure: {string.Join(", ", result.FailingTypes)}"); + } + } +} diff --git a/NetSdrClient.Tests/NetSdrClient.Tests.csproj b/NetSdrClient.Tests/NetSdrClient.Tests.csproj new file mode 100644 index 00000000..7697a375 --- /dev/null +++ b/NetSdrClient.Tests/NetSdrClient.Tests.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + enable + enable + false + + + + + + + + + + + + + + + diff --git a/NetSdrClient.Tests/NetworkServiceTests.cs b/NetSdrClient.Tests/NetworkServiceTests.cs new file mode 100644 index 00000000..3d29fd9e --- /dev/null +++ b/NetSdrClient.Tests/NetworkServiceTests.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NetSdrClient.Services; + +namespace NetSdrClient.Tests +{ + [TestClass] + public class NetworkServiceTests + { + [TestMethod] + public void NetworkService_Constructor_ShouldInitialize() + { + // Arrange & Act + var service = new NetworkService(); + + // Assert + Assert.IsNotNull(service); + } + + [TestMethod] + public void IsValidIpAddress_WithValidIp_ShouldReturnTrue() + { + // Arrange + var service = new NetworkService(); + var validIp = "192.168.1.1"; + + // Act + var result = service.IsValidIpAddress(validIp); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void IsValidIpAddress_WithInvalidIp_ShouldReturnFalse() + { + // Arrange + var service = new NetworkService(); + var invalidIp = "999.999.999.999"; + + // Act + var result = service.IsValidIpAddress(invalidIp); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void IsValidPort_WithValidPort_ShouldReturnTrue() + { + // Arrange + var service = new NetworkService(); + var validPort = 8080; + + // Act + var result = service.IsValidPort(validPort); + + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public void IsValidPort_WithInvalidPort_ShouldReturnFalse() + { + // Arrange + var service = new NetworkService(); + var invalidPort = 99999; + + // Act + var result = service.IsValidPort(invalidPort); + + // Assert + Assert.IsFalse(result); + } + } +} diff --git a/NetSdrClient.Tests/ProtocolHandlerTests.cs b/NetSdrClient.Tests/ProtocolHandlerTests.cs new file mode 100644 index 00000000..60b17bc4 --- /dev/null +++ b/NetSdrClient.Tests/ProtocolHandlerTests.cs @@ -0,0 +1,63 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NetSdrClient.Services; + +namespace NetSdrClient.Tests +{ + [TestClass] + public class ProtocolHandlerTests + { + [TestMethod] + public void ProtocolHandler_Constructor_ShouldInitialize() + { + // Arrange & Act + var handler = new ProtocolHandler(); + + // Assert + Assert.IsNotNull(handler); + } + + [TestMethod] + public void CreateCommand_ShouldReturnValidCommand() + { + // Arrange + var handler = new ProtocolHandler(); + var expectedCommand = "CONNECT"; + + // Act + var result = handler.CreateCommand(expectedCommand); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains(expectedCommand)); + } + + [TestMethod] + public void ParseResponse_WithValidData_ShouldReturnParsedResponse() + { + // Arrange + var handler = new ProtocolHandler(); + var testData = "OK:CONNECTED"; + + // Act + var result = handler.ParseResponse(testData); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("CONNECTED")); + } + + [TestMethod] + public void ParseResponse_WithNullData_ShouldReturnErrorMessage() + { + // Arrange + var handler = new ProtocolHandler(); + + // Act + var result = handler.ParseResponse(null); + + // Assert + Assert.IsNotNull(result); + Assert.IsTrue(result.Contains("ERROR")); + } + } +} diff --git a/NetSdrClient.Tests/SDRClientTests.cs b/NetSdrClient.Tests/SDRClientTests.cs new file mode 100644 index 00000000..05cb679d --- /dev/null +++ b/NetSdrClient.Tests/SDRClientTests.cs @@ -0,0 +1,97 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NetSdrClient.Models; +using NetSdrClient.Services; +using System.Linq; + +namespace NetSdrClient.Tests +{ + [TestClass] + public class SDRClientTests + { + [TestMethod] + public void SDRClient_Constructor_ShouldInitializeDevicesList() + { + // Arrange & Act + var client = new SDRClient(); + + // Assert + Assert.IsNotNull(client.Devices); + Assert.AreEqual(0, client.Devices.Count); + } + + [TestMethod] + public void AddDevice_ShouldAddDeviceToList() + { + // Arrange + var client = new SDRClient(); + var device = new SDRDevice { Id = 1, Name = "Test Device" }; + + // Act + client.AddDevice(device); + + // Assert + Assert.AreEqual(1, client.Devices.Count); + Assert.AreEqual(device, client.Devices[0]); + } + + [TestMethod] + public void RemoveDevice_ShouldRemoveDeviceFromList() + { + // Arrange + var client = new SDRClient(); + var device = new SDRDevice { Id = 1, Name = "Test Device" }; + client.AddDevice(device); + + // Act + var result = client.RemoveDevice(1); + + // Assert + Assert.IsTrue(result); + Assert.AreEqual(0, client.Devices.Count); + } + + [TestMethod] + public void RemoveDevice_WithInvalidId_ShouldReturnFalse() + { + // Arrange + var client = new SDRClient(); + + // Act + var result = client.RemoveDevice(999); + + // Assert + Assert.IsFalse(result); + } + + [TestMethod] + public void GetDevice_ShouldReturnCorrectDevice() + { + // Arrange + var client = new SDRClient(); + var device1 = new SDRDevice { Id = 1, Name = "Device 1" }; + var device2 = new SDRDevice { Id = 2, Name = "Device 2" }; + client.AddDevice(device1); + client.AddDevice(device2); + + // Act + var result = client.GetDevice(2); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual("Device 2", result.Name); + } + + [TestMethod] + public void GetDevice_WithInvalidId_ShouldReturnNull() + { + // Arrange + var client = new SDRClient(); + + // Act + var result = client.GetDevice(999); + + // Assert + Assert.IsNull(result); + } + } +} diff --git a/NetSdrClient.Tests/SDRDeviceTests.cs b/NetSdrClient.Tests/SDRDeviceTests.cs new file mode 100644 index 00000000..89295dd1 --- /dev/null +++ b/NetSdrClient.Tests/SDRDeviceTests.cs @@ -0,0 +1,55 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NetSdrClient.Models; + +namespace NetSdrClient.Tests +{ + [TestClass] + public class SDRDeviceTests + { + [TestMethod] + public void SDRDevice_Constructor_ShouldInitializeProperties() + { + // Arrange & Act + var device = new SDRDevice(); + + // Assert + Assert.IsNotNull(device); + Assert.AreEqual(0, device.Id); + Assert.IsNull(device.Name); + Assert.IsNull(device.Description); + Assert.IsFalse(device.IsConnected); + } + + [TestMethod] + public void SDRDevice_Properties_ShouldSetAndGetCorrectly() + { + // Arrange + var device = new SDRDevice(); + + // Act + device.Id = 1; + device.Name = "Test Device"; + device.Description = "Test Description"; + device.IsConnected = true; + + // Assert + Assert.AreEqual(1, device.Id); + Assert.AreEqual("Test Device", device.Name); + Assert.AreEqual("Test Description", device.Description); + Assert.IsTrue(device.IsConnected); + } + + [TestMethod] + public void SDRDevice_ToString_ShouldReturnName() + { + // Arrange + var device = new SDRDevice { Name = "Test SDR" }; + + // Act + var result = device.ToString(); + + // Assert + Assert.AreEqual("Test SDR", result); + } + } +} diff --git a/NetSdrClient/Services/BadService.cs b/NetSdrClient/Services/BadService.cs new file mode 100644 index 00000000..51da3ba7 --- /dev/null +++ b/NetSdrClient/Services/BadService.cs @@ -0,0 +1,20 @@ +using NetSdrClient.Models; +using NetSdrClient.UI; // Навмисне порушення - Services залежить від UI + +namespace NetSdrClient.Services +{ + public class BadService + { + private readonly UIComponent _uiComponent; // Порушення! + + public BadService() + { + _uiComponent = new UIComponent(); + } + + public void DoSomething() + { + _uiComponent.ShowMessage("This violates architecture rules!"); + } + } +} diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs index b0a7c058..d4466ac5 100644 --- a/NetSdrClientApp/NetSdrClient.cs +++ b/NetSdrClientApp/NetSdrClient.cs @@ -9,7 +9,29 @@ using System.Threading.Tasks; using static NetSdrClientApp.Messages.NetSdrMessageHelper; using static System.Runtime.InteropServices.JavaScript.JSType; +using NetSdrClient.Models; +using NetSdrClient.Interfaces; +namespace NetSdrClient.Services +{ + public class SDRClient : IDeviceService + { + public List Devices { get; private set; } + + // Реалізація інтерфейсу + public void Connect() + { + // Connection logic + } + + public void Disconnect() + { + // Disconnection logic + } + + // Інші методи... + } +} namespace NetSdrClientApp { public class NetSdrClient diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj index 3cbc46af..e1f5e652 100644 --- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj +++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj @@ -1,17 +1,18 @@ - net8.0 enable enable - false true - - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + @@ -19,11 +20,10 @@ - + - diff --git a/UI/UIComponent.cs b/UI/UIComponent.cs new file mode 100644 index 00000000..347ef573 --- /dev/null +++ b/UI/UIComponent.cs @@ -0,0 +1,10 @@ +namespace NetSdrClient.UI +{ + public class UIComponent + { + public void ShowMessage(string message) + { + // UI logic here - this should not be called from Services + } + } +} diff --git a/coverlet.msbuild b/coverlet.msbuild new file mode 100644 index 00000000..5f923fc2 --- /dev/null +++ b/coverlet.msbuild @@ -0,0 +1,3 @@ +dotnet add NetSdrClientAppTests package coverlet.msbuild +dotnet add NetSdrClientAppTests package Microsoft.NET.Test.Sdk +dotnet test NetSdrClientAppTests -c Release /p:CollectCoverage=true /p:CoverletOutput=TestResults/coverage.xml /p:CoverletOutputFormat=opencover diff --git a/coverlet.runsettings b/coverlet.runsettings new file mode 100644 index 00000000..9815d295 --- /dev/null +++ b/coverlet.runsettings @@ -0,0 +1,14 @@ + + + + + + + opencover + [NetSdrClient]* + [NetSdrClient.Tests]* + + + + +