Files
ReC/tests/ReC.Tests/Client/StaticReCClientTests.cs
TekH c63ecb7e45 Add tests for ReCClient static client initialization
Introduced `StaticReCClientTests` to validate the behavior of
the `ReCClient` static client, ensuring deterministic and
non-parallel execution due to process-wide state mutation.

Added tests to cover various scenarios:
- Null configuration callback throws `ArgumentNullException`.
- Missing `BaseAddress` or `ConfigureClient` throws.
- Conflicting `BaseAddress` and `ConfigureClient` throws.
- Successful static client build and resolution via `Create`.
- Subsequent `BuildStaticClient` calls throw exceptions.

Included helper types for `ConfigureServices` validation and
used `#pragma` directives to suppress warnings for obsolete
members. Ensured test order with `[Order]` attributes.
2026-05-21 09:29:17 +02:00

130 lines
4.8 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ReC.Client;
namespace ReC.Tests.Client;
// The static BuildStaticClient / Create entry-point mutates process-wide state and can only be
// initialized once per AppDomain. All assertions must therefore live in a single, ordered,
// non-parallel fixture so that the lifecycle is deterministic.
[TestFixture]
[NonParallelizable]
public class StaticReCClientTests
{
// Validation tests run first. They all throw BEFORE the static state is mutated,
// so they can run repeatedly without consuming the one-time build slot.
[Test, Order(10)]
public void BuildStaticClient_with_null_configure_throws_ArgumentNullException()
{
#pragma warning disable CS0618 // Type or member is obsolete
Assert.Throws<ArgumentNullException>(() => ReCClient.BuildStaticClient((Action<StaticBuildConfiguration>)null!));
#pragma warning restore CS0618
}
[Test, Order(20)]
public void BuildStaticClient_without_base_address_or_configure_client_throws()
{
#pragma warning disable CS0618
var ex = Assert.Throws<InvalidOperationException>(
() => ReCClient.BuildStaticClient(_ => { }));
#pragma warning restore CS0618
Assert.That(ex!.Message, Does.Contain(nameof(StaticBuildConfiguration.BaseAddress)));
Assert.That(ex.Message, Does.Contain(nameof(StaticBuildConfiguration.ConfigureClient)));
}
[Test, Order(30)]
public void BuildStaticClient_with_both_base_address_and_configure_client_throws()
{
#pragma warning disable CS0618
var ex = Assert.Throws<InvalidOperationException>(() => ReCClient.BuildStaticClient(cfg =>
{
cfg.BaseAddress = "https://example.invalid/";
cfg.ConfigureClient = http => http.BaseAddress = new Uri("https://example.invalid/");
}));
#pragma warning restore CS0618
Assert.That(ex!.Message, Does.Contain("mutually exclusive"));
}
// Successful build — consumes the single allowed BuildStaticClient slot for this test run.
[Test, Order(100)]
public void BuildStaticClient_with_configuration_callback_succeeds_and_Create_resolves_client()
{
var customLogger = NullLogger<ReCClient>.Instance;
var configureServicesInvoked = false;
#pragma warning disable CS0618
ReCClient.BuildStaticClient(cfg =>
{
cfg.BaseAddress = "https://example.invalid/";
cfg.ConfigureOptions = opt => opt.LogSuccessfulRequests = true;
cfg.Logger = customLogger;
cfg.ConfigureServices = services =>
{
configureServicesInvoked = true;
services.AddSingleton<IMarkerService, MarkerService>();
};
});
// First Create() triggers the Lazy<IServiceProvider> initialization.
var client = ReCClient.Create();
#pragma warning restore CS0618
Assert.That(client, Is.Not.Null);
Assert.That(client.RecActions, Is.Not.Null);
Assert.That(configureServicesInvoked, Is.True, "ConfigureServices callback should run when the provider is built.");
}
[Test, Order(110)]
public void Create_returns_a_client_using_the_existing_static_provider()
{
#pragma warning disable CS0618
var client = ReCClient.Create();
#pragma warning restore CS0618
Assert.That(client, Is.Not.Null);
}
// Any subsequent BuildStaticClient call (regardless of overload) must fail.
[Test, Order(200)]
public void Calling_BuildStaticClient_configuration_overload_a_second_time_throws()
{
#pragma warning disable CS0618
var ex = Assert.Throws<InvalidOperationException>(() =>
ReCClient.BuildStaticClient(cfg => cfg.BaseAddress = "https://other.invalid/"));
#pragma warning restore CS0618
Assert.That(ex!.Message, Does.Contain("already built"));
}
[Test, Order(210)]
public void Calling_legacy_BuildStaticClient_string_overload_after_build_throws()
{
#pragma warning disable CS0618
var ex = Assert.Throws<InvalidOperationException>(() =>
ReCClient.BuildStaticClient("https://other.invalid/"));
#pragma warning restore CS0618
Assert.That(ex!.Message, Does.Contain("already built"));
}
[Test, Order(220)]
public void Calling_legacy_BuildStaticClient_action_overload_after_build_throws()
{
#pragma warning disable CS0618
var ex = Assert.Throws<InvalidOperationException>(() =>
ReCClient.BuildStaticClient(http => http.BaseAddress = new Uri("https://other.invalid/")));
#pragma warning restore CS0618
Assert.That(ex!.Message, Does.Contain("already built"));
}
// Helper types used by the ConfigureServices assertion above.
private interface IMarkerService { }
private sealed class MarkerService : IMarkerService { }
}