From 73d8068d8e2aa47bd34045bf3d696106968a40c7 Mon Sep 17 00:00:00 2001 From: TekH Date: Wed, 20 May 2026 11:34:09 +0200 Subject: [PATCH] Add unit tests for RecActions API client Introduced a new test class `RecActionApiTests` to validate the behavior of the `RecActions` API client. - Added tests for `GetAsync`, `CreateAsync`, `UpdateAsync`, `DeleteAsync`, and `InvokeAsync` methods to ensure proper handling of valid and invalid inputs. - Verified dependency injection resolution for `RecClient` and `RecActions`. - Included assertions for HTTP status codes, request methods, and query parameters. - Handled edge cases such as missing test data with `Assert.Pass` or `Assert.Ignore`. - Utilized scoped clients for test isolation and resource management. These changes improve test coverage and ensure the reliability of the `RecActions` API client. --- tests/ReC.Tests/Client/RecActionApiTests.cs | 159 ++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 tests/ReC.Tests/Client/RecActionApiTests.cs diff --git a/tests/ReC.Tests/Client/RecActionApiTests.cs b/tests/ReC.Tests/Client/RecActionApiTests.cs new file mode 100644 index 0000000..0c56fae --- /dev/null +++ b/tests/ReC.Tests/Client/RecActionApiTests.cs @@ -0,0 +1,159 @@ +using System.Net; +using Microsoft.Extensions.Configuration; +using ReC.Application.Common.Dto; +using ReC.Application.Common.Procedures.UpdateProcedure.Dto; +using ReC.Application.RecActions.Commands; +using ReC.Client; +using ReC.Client.Api; +using ClientInvokeReferences = ReC.Client.Api.InvokeReferences; + +namespace ReC.Tests.Client; + +[TestFixture] +public class RecActionApiTests : RecClientTestBase +{ + [Test] + public void ReCClient_is_resolvable_through_dependency_injection() + { + var (client, scope) = CreateScopedClient(); + using var _ = scope; + + Assert.That(client, Is.Not.Null); + Assert.That(client.RecActions, Is.Not.Null); + } + + [Test] + public async Task GetAsync_without_filters_returns_deserialized_result_or_throws_not_found() + { + var (client, scope) = CreateScopedClient(); + using var _ = scope; + + try + { + var actions = await client.RecActions.GetAsync(); + Assert.That(actions, Is.Not.Null); + } + catch (ReCApiException ex) + { + Assert.That(ex.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + Assert.That(ex.Method, Is.EqualTo("GET")); + Assert.That(ex.RequestUri!.AbsolutePath, Does.EndWith("api/RecAction")); + } + } + + [Test] + public async Task GetAsync_with_profile_filter_returns_only_matching_actions() + { + var profileId = Configuration.GetValue("FakeProfileId"); + if (profileId is null or <= 0) + Assert.Ignore("FakeProfileId must be configured in appsettings.json for this test."); + + var (client, scope) = CreateScopedClient(); + using var _ = scope; + + RecActionViewDto[]? actions; + try + { + actions = await client.RecActions.GetAsync(profileId: profileId); + } + catch (ReCApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound) + { + Assert.Pass("NotFound is acceptable when test data is unavailable."); + return; + } + + Assert.That(actions, Is.Not.Null.And.Not.Empty); + Assert.That(actions, Has.All.Matches(a => a.ProfileId == profileId)); + } + + [Test] + public void GetAsync_with_unknown_profile_throws_not_found() + { + var (client, scope) = CreateScopedClient(); + using var _ = scope; + + var ex = Assert.ThrowsAsync(async () => + await client.RecActions.GetAsync(profileId: long.MaxValue)); + + Assert.That(ex, Is.Not.Null); + Assert.That(ex!.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + Assert.That(ex.Method, Is.EqualTo("GET")); + Assert.That(ex.RequestUri!.Query, Does.Contain("ProfileId=")); + } + + [Test] + public void CreateAsync_with_invalid_foreign_key_throws_ReCApiException() + { + var (client, scope) = CreateScopedClient(); + using var _ = scope; + + var payload = new InsertActionCommand + { + ProfileId = long.MaxValue, + EndpointId = long.MaxValue, + Active = true, + Sequence = 1 + }; + + var ex = Assert.ThrowsAsync(async () => await client.RecActions.CreateAsync(payload)); + Assert.That(ex, Is.Not.Null); + Assert.That((int)ex!.StatusCode, Is.GreaterThanOrEqualTo(400)); + Assert.That(ex.Method, Is.EqualTo("POST")); + } + + [Test] + public void UpdateAsync_with_unknown_id_throws_ReCApiException_with_method_PUT() + { + var (client, scope) = CreateScopedClient(); + using var _ = scope; + + var unknownId = long.MaxValue; + var payload = new UpdateActionDto + { + ProfileId = 1, + Active = false, + Sequence = 1 + }; + + var ex = Assert.ThrowsAsync(async () => await client.RecActions.UpdateAsync(unknownId, payload)); + Assert.That(ex, Is.Not.Null); + Assert.That(ex!.Method, Is.EqualTo("PUT")); + } + + [Test] + public void DeleteAsync_sends_payload_as_query_string_not_body() + { + var (client, scope) = CreateScopedClient(); + using var _ = scope; + + var payload = new DeleteActionCommand + { + Start = long.MaxValue - 1, + End = long.MaxValue, + Force = false + }; + + try + { + client.RecActions.DeleteAsync(payload).GetAwaiter().GetResult(); + } + catch (ReCApiException ex) + { + Assert.That(ex.Method, Is.EqualTo("DELETE")); + Assert.That(ex.RequestUri!.Query, Does.Contain("Start=").And.Contains("End=").And.Contains("Force=")); + } + } + + [Test] + public void InvokeAsync_with_unknown_profile_throws_ReCApiException() + { + var (client, scope) = CreateScopedClient(); + using var _ = scope; + + var ex = Assert.ThrowsAsync(async () => + await client.RecActions.InvokeAsync(long.MaxValue, new ClientInvokeReferences { BatchId = "test-batch" })); + + Assert.That(ex, Is.Not.Null); + Assert.That(ex!.Method, Is.EqualTo("POST")); + } +}