Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 61 additions & 15 deletions NetSdrClientAppTests/UdpClientWrapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
using System;
using System.Threading;
using System.Net;
using System.Net.Sockets; // Added for UdpClient, SocketException
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Linq;
using NetSdrClientApp.Networking;
using System.Reflection; // Required for accessing private fields
using System.Reflection;

namespace NetSdrClientAppTests.Networking
{
Expand All @@ -18,8 +18,9 @@
{
private Mock<IHashAlgorithm> _hashMock = null!;
private UdpClientWrapper _wrapper = null!;
private int _testPort; // Instance field for the port

// FIX: Using a random dynamic port to avoid "Only one usage of each socket address" error
// Helper to get an available dynamic port to avoid conflicts
private int GetAvailablePort()
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
Expand All @@ -34,9 +35,8 @@
public void SetUp()
{
_hashMock = new Mock<IHashAlgorithm>();
// Use a new available port for each test run
int testPort = GetAvailablePort();
_wrapper = new UdpClientWrapper(testPort, _hashMock.Object);
_testPort = GetAvailablePort(); // Get a unique port for the test fixture
_wrapper = new UdpClientWrapper(_testPort, _hashMock.Object);
}

[TearDown]
Expand Down Expand Up @@ -111,11 +111,8 @@
// 1. Verify that the cancellation was requested
Assert.That(cts.IsCancellationRequested, Is.True, "Cancellation token should be cancelled.");

// 2. Verify that UdpClient and CTS fields are set to null (Cleanup logic)
// 2. Verify that UdpClient field is nullified after Cleanup.
Assert.That(GetPrivateField<UdpClient>("_udpClient"), Is.Null, "Internal UdpClient should be nullified after Cleanup.");

// 3. Verify that the client is actually closed (important for the listener thread)
// We cannot verify .Close() with Moq, so checking the state is the next best thing.
}

[Test]
Expand Down Expand Up @@ -154,9 +151,7 @@
// 1. Verify HashAlgorithm dispose
_hashMock.Verify(h => h.Dispose(), Times.Once, "IHashAlgorithm should be disposed.");

// 2. Verify CTS dispose
// Since CTS is disposed internally, we can check if a second dispose throws,
// but the safer way is to check if it was cancelled by Cleanup.
// 2. Verify cancellation
Assert.That(cts.IsCancellationRequested, Is.True, "Dispose should call Cleanup, which cancels CTS.");

// 3. Verify idempotency
Expand All @@ -165,11 +160,63 @@
}

// ------------------------------------------------------------------
// TEST 5: START LISTENING (Placeholder logic improvement)
// TEST 5: START LISTENING (Asynchronous logic coverage - NEW TESTS)
// ------------------------------------------------------------------

[Test]
public async Task StartListeningAsync_ShouldReceiveDataAndRaiseEvent()
{
// Arrange
byte[] expectedData = Encoding.ASCII.GetBytes("TestPacket");
byte[] receivedData = Array.Empty<byte>();
var receivedTcs = new TaskCompletionSource<bool>();

_wrapper.MessageReceived += (sender, data) =>
{
receivedData = data;
receivedTcs.SetResult(true);
};

var listeningTask = _wrapper.StartListeningAsync();

// Allow a small delay for UdpClient to initialize and start listening
await Task.Delay(100);

// Act: Send data using a separate client
using (var sender = new UdpClient())
{
var targetEndpoint = new IPEndPoint(IPAddress.Loopback, _testPort);
await sender.SendAsync(expectedData, targetEndpoint);
}

// Assert: Wait for the event to be raised (or timeout after 1 second)
Assert.That(await receivedTcs.Task.WaitAsync(TimeSpan.FromSeconds(1)), Is.True, "MessageReceived event was not raised.");

Check warning on line 193 in NetSdrClientAppTests/UdpClientWrapperTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Call independent Assert statements from inside an Assert.Multiple

See more on https://sonarcloud.io/project/issues?id=YehorYurch5_NetSdrClient&issues=AZqyeI_ibmnrA-bVRNcz&open=AZqyeI_ibmnrA-bVRNcz&pullRequest=12
Assert.That(receivedData, Is.EqualTo(expectedData), "Received data does not match expected data.");
}

[Test]
public async Task StartListeningAsync_ShouldStopListeningOnCancellation()
{
// Arrange
var listeningTask = _wrapper.StartListeningAsync();

// Allow a small delay for UdpClient to initialize
await Task.Delay(100);

// Act: Stop listening which cancels the CTS and breaks the ReceiveAsync loop
_wrapper.StopListening();

// Assert
// 1. Task should complete within a short time (OperationCanceledException is expected internally)
Assert.That(async () => await listeningTask.WaitAsync(TimeSpan.FromSeconds(1)), Throws.Nothing,
"Listening task should complete gracefully (no exceptions thrown outside) on StopListening.");

// 2. Verify that UdpClient is null after cleanup
Assert.That(GetPrivateField<UdpClient>("_udpClient"), Is.Null, "UdpClient should be nullified after cancellation.");
}

[Test]
public async Task StartListeningAsync_ShouldHandleStartWhenAlreadyRunning()

Check warning on line 219 in NetSdrClientAppTests/UdpClientWrapperTests.cs

View workflow job for this annotation

GitHub Actions / Sonar Check

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
// Arrange: Setup private CTS to simulate "already running"
var cts = new CancellationTokenSource();
Expand All @@ -179,7 +226,6 @@
var task = _wrapper.StartListeningAsync(); // Should exit early because CTS is not cancelled

// Assert: The task should complete instantly (or near-instantly)
// It's hard to verify *instantly* without async issues, so we check state.
Assert.That(cts.IsCancellationRequested, Is.False, "Existing CTS should not be cancelled if StartListening exits early.");

// Clean up for TearDown
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Лабораторні з реінжинірингу (8×)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=YehorYurch5_NetSdrClient&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=YehorYurch5_NetSdrClient)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=coverage)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=bugs)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=ppanchen_NetSdrClient&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=ppanchen_NetSdrClient)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=YehorYurch5_NetSdrClient&metric=coverage)](https://sonarcloud.io/summary/new_code?id=YehorYurch5_NetSdrClient)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=YehorYurch5_NetSdrClient&metric=bugs)](https://sonarcloud.io/summary/new_code?id=YehorYurch5_NetSdrClient)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=YehorYurch5_NetSdrClient&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=YehorYurch5_NetSdrClient)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=YehorYurch5_NetSdrClient&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=YehorYurch5_NetSdrClient)
[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=YehorYurch5_NetSdrClient&metric=duplicated_lines_density)](https://sonarcloud.io/summary/new_code?id=YehorYurch5_NetSdrClient)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=YehorYurch5_NetSdrClient&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=YehorYurch5_NetSdrClient)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=YehorYurch5_NetSdrClient&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=YehorYurch5_NetSdrClient)


Цей репозиторій використовується для курсу **реінжиніринг ПЗ**.
Expand Down
Loading