using System.Net; using System.Text; using System.Text.Json; using Microsoft.Extensions.Logging; using Dapr.Client; using Moq; using MaksIT.Results; using MaksIT.Dapr.Services; using MaksIT.Dapr.Services.WorkLease; namespace MaksIT.Dapr.Tests; public class DaprWorkLeaseServiceTests { private static Mock CreateStateMock() => new(); [Fact] public async Task TryAcquireAsync_Succeeds_WhenKeyMissing() { var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((null, null))); state .Setup(s => s.TrySaveStateAsync( "store", "work", It.IsAny(), null, It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result.Ok(true)); var service = new DaprWorkLeaseService(state.Object); var result = await service.TryAcquireAsync("store", "work", "pod-a", TimeSpan.FromMinutes(1)); Assert.True(result.IsSuccess); Assert.True(result.Value); } [Fact] public async Task TryAcquireAsync_Fails_WhenHeldByOtherAndNotExpired() { var lease = new DaprWorkLease("pod-b", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMinutes(5), 3); var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((lease, "1"))); var service = new DaprWorkLeaseService(state.Object); var result = await service.TryAcquireAsync("store", "work", "pod-a", TimeSpan.FromMinutes(1)); Assert.True(result.IsSuccess); Assert.False(result.Value); } [Fact] public async Task TryAcquireAsync_TakesOver_WhenLeaseExpired_AndBumpsGeneration() { var lease = new DaprWorkLease("pod-b", DateTimeOffset.UtcNow.AddMinutes(-10), DateTimeOffset.UtcNow.AddMinutes(-5), 7); DaprWorkLease? saved = null; var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((lease, "1"))); state .Setup(s => s.TrySaveStateAsync( "store", "work", It.IsAny(), "1", It.IsAny(), It.IsAny?>(), It.IsAny())) .Callback?, CancellationToken>( (_, _, value, _, _, _, _) => saved = value) .ReturnsAsync(Result.Ok(true)); var service = new DaprWorkLeaseService(state.Object); var result = await service.TryAcquireAsync("store", "work", "pod-a", TimeSpan.FromMinutes(1)); Assert.True(result.IsSuccess); Assert.True(result.Value); Assert.Equal(8, saved!.Generation); } [Fact] public async Task TryAcquireAsync_ReturnsBadRequest_WhenTtlNonPositive() { var service = new DaprWorkLeaseService(Mock.Of()); var result = await service.TryAcquireAsync("store", "work", "pod-a", TimeSpan.Zero); Assert.False(result.IsSuccess); } [Fact] public async Task TryRenewAsync_Succeeds_WhenSameHolder() { var lease = new DaprWorkLease("pod-a", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMinutes(1), 2); var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((lease, "2"))); state .Setup(s => s.TrySaveStateAsync( "store", "work", It.IsAny(), "2", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result.Ok(true)); var service = new DaprWorkLeaseService(state.Object); var result = await service.TryRenewAsync("store", "work", "pod-a", TimeSpan.FromMinutes(5)); Assert.True(result.IsSuccess); Assert.True(result.Value); } [Fact] public async Task TryRenewAsync_Fails_WhenDifferentHolder() { var lease = new DaprWorkLease("pod-b", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMinutes(1)); var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((lease, "2"))); var service = new DaprWorkLeaseService(state.Object); var result = await service.TryRenewAsync("store", "work", "pod-a", TimeSpan.FromMinutes(5)); Assert.True(result.IsSuccess); Assert.False(result.Value); } [Fact] public async Task ReleaseAsync_Deletes_WhenSameHolder() { var lease = new DaprWorkLease("pod-a", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMinutes(1)); var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((lease, "3"))); state .Setup(s => s.DeleteStateAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result.Ok()); var service = new DaprWorkLeaseService(state.Object); var result = await service.ReleaseAsync("store", "work", "pod-a"); Assert.True(result.IsSuccess); state.Verify(s => s.DeleteStateAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny()), Times.Once); } [Fact] public async Task ReleaseAsync_ReturnsConflict_WhenDifferentHolder() { var lease = new DaprWorkLease("pod-b", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMinutes(1)); var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((lease, "3"))); var service = new DaprWorkLeaseService(state.Object); var result = await service.ReleaseAsync("store", "work", "pod-a"); Assert.False(result.IsSuccess); } [Fact] public async Task ReleaseAsync_ReturnsOk_WhenKeyMissing() { var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((null, null))); var service = new DaprWorkLeaseService(state.Object); var result = await service.ReleaseAsync("store", "work", "pod-a"); Assert.True(result.IsSuccess); } [Fact] public async Task GetAsync_ReturnsLease_WhenPresent() { var lease = new DaprWorkLease("pod-a", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMinutes(1), 4); var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((lease, "1"))); var service = new DaprWorkLeaseService(state.Object); var result = await service.GetAsync("store", "work"); Assert.True(result.IsSuccess); Assert.Equal("pod-a", result.Value!.HolderId); Assert.Equal(4, result.Value.Generation); } [Fact] public async Task TryHoldAsync_ReturnsHold_WhenAcquired() { var state = CreateStateMock(); DaprWorkLease? saved = null; state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(() => Result<(DaprWorkLease? Value, string? ETag)>.Ok((saved, saved is null ? null : "1"))); state .Setup(s => s.TrySaveStateAsync( "store", "work", It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny?>(), It.IsAny())) .Callback?, CancellationToken>( (_, _, value, _, _, _, _) => saved = value) .ReturnsAsync(Result.Ok(true)); state .Setup(s => s.DeleteStateAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result.Ok()); var service = new DaprWorkLeaseService(state.Object); var holdResult = await service.TryHoldAsync("store", "work", "pod-a", TimeSpan.FromMinutes(1), autoRenew: false); Assert.True(holdResult.IsSuccess); Assert.NotNull(holdResult.Value); Assert.Equal(1, holdResult.Value!.Generation); await holdResult.Value.DisposeAsync(); state.Verify(s => s.DeleteStateAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny()), Times.Once); } [Fact] public async Task TryHoldAsync_ReturnsNull_WhenBusy() { var lease = new DaprWorkLease("pod-b", DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddMinutes(5), 1); var state = CreateStateMock(); state .Setup(s => s.GetStateAndETagAsync( "store", "work", It.IsAny(), It.IsAny?>(), It.IsAny())) .ReturnsAsync(Result<(DaprWorkLease? Value, string? ETag)>.Ok((lease, "1"))); var service = new DaprWorkLeaseService(state.Object); var holdResult = await service.TryHoldAsync("store", "work", "pod-a", TimeSpan.FromMinutes(1), autoRenew: false); Assert.True(holdResult.IsSuccess); Assert.Null(holdResult.Value); } } public class DaprWorkLeaseBootstrapTests { [Fact] public async Task RunBootstrapUnderLeaseAsync_RunsBootstrap_WhenLeaseAcquired() { var leases = new Mock(); var hold = new DaprWorkLeaseHold(leases.Object, "store", "boot", "pod-a", TimeSpan.FromMinutes(1), 1, autoRenew: false); leases .Setup(l => l.TryHoldAsync("store", "boot", "pod-a", It.IsAny(), true, It.IsAny())) .ReturnsAsync(Result.Ok(hold)); leases .Setup(l => l.ReleaseAsync("store", "boot", "pod-a", It.IsAny())) .ReturnsAsync(Result.Ok()); var ran = false; var result = await DaprWorkLeaseBootstrap.RunBootstrapUnderLeaseAsync( leases.Object, "store", "boot", "pod-a", TimeSpan.FromMinutes(1), _ => { ran = true; return Task.FromResult(Result.Ok()); }, _ => Task.FromResult(Result.Ok(true))); Assert.True(result.IsSuccess); Assert.True(ran); } [Fact] public async Task RunBootstrapUnderLeaseAsync_WaitsForReady_WhenFollower() { var leases = new Mock(); leases .Setup(l => l.TryHoldAsync("store", "boot", "pod-b", It.IsAny(), true, It.IsAny())) .ReturnsAsync(Result.Ok(null)); var polls = 0; var result = await DaprWorkLeaseBootstrap.RunBootstrapUnderLeaseAsync( leases.Object, "store", "boot", "pod-b", TimeSpan.FromMinutes(1), _ => Task.FromResult(Result.Ok()), _ => { polls++; return Task.FromResult(Result.Ok(polls >= 2)); }, followerPollInterval: TimeSpan.FromMilliseconds(1)); Assert.True(result.IsSuccess); Assert.True(polls >= 2); } } public class DaprStateStoreETagTests { [Fact] public async Task TrySaveStateAsync_ReturnsOkFalse_WhenClientReturnsFalse() { var clientMock = new Mock(); clientMock .Setup(x => x.TrySaveStateAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .ReturnsAsync(false); var service = new DaprStateStoreService(Mock.Of>(), clientMock.Object); var result = await service.TrySaveStateAsync("store", "key", "value", "etag"); Assert.True(result.IsSuccess); Assert.False(result.Value); } } public class DaprInvocationServiceTests { [Fact] public async Task InvokeAsync_ReturnsBadRequest_WhenAppIdEmpty() { var service = new DaprInvocationService(Mock.Of>(), Mock.Of()); var result = await service.InvokeAsync(" ", "method"); Assert.False(result.IsSuccess); } [Fact] public async Task InvokeAsync_PostsViaInvokableHttpClient_AndReturnsOk() { var handler = new RecordingHandler(new HttpResponseMessage(HttpStatusCode.OK)); using var http = new HttpClient(handler) { BaseAddress = new Uri("http://orders/") }; var client = new Mock(); client.Setup(c => c.CreateInvokableHttpClient("orders")).Returns(http); client.SetupGet(c => c.JsonSerializerOptions).Returns(new JsonSerializerOptions(JsonSerializerDefaults.Web)); using var service = new DaprInvocationService(Mock.Of>(), client.Object); var result = await service.InvokeAsync("orders", "create", new { Id = 1 }); Assert.True(result.IsSuccess); Assert.Equal(HttpMethod.Post, handler.LastMethod); Assert.Equal(new Uri("http://orders/create"), handler.LastUri); } [Fact] public async Task InvokeAsync_DeserializesJsonResponse() { var handler = new RecordingHandler(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("""{"total":42}""", Encoding.UTF8, "application/json") }); using var http = new HttpClient(handler) { BaseAddress = new Uri("http://orders/") }; var client = new Mock(); client.Setup(c => c.CreateInvokableHttpClient("orders")).Returns(http); client.SetupGet(c => c.JsonSerializerOptions).Returns(new JsonSerializerOptions(JsonSerializerDefaults.Web)); using var service = new DaprInvocationService(Mock.Of>(), client.Object); var result = await service.InvokeAsync("orders", "total"); Assert.True(result.IsSuccess); Assert.Equal(42, result.Value?.Total); } private sealed class OrderTotal { public int Total { get; set; } } private sealed class RecordingHandler(HttpResponseMessage response) : HttpMessageHandler { public HttpMethod? LastMethod { get; private set; } public Uri? LastUri { get; private set; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { LastMethod = request.Method; LastUri = request.RequestUri; return Task.FromResult(response); } } } public class DaprSidecarServiceTests { [Fact] public async Task CheckHealthAsync_ReturnsOk_WhenHealthy() { var client = new Mock(); client.Setup(c => c.CheckHealthAsync(It.IsAny())).ReturnsAsync(true); var service = new DaprSidecarService(Mock.Of>(), client.Object); var result = await service.CheckHealthAsync(); Assert.True(result.IsSuccess); Assert.True(result.Value); } } public class DaprSecretServiceTests { [Fact] public async Task GetAsync_ReturnsBadRequest_WhenStoreEmpty() { var service = new DaprSecretService(Mock.Of>(), Mock.Of()); var result = await service.GetAsync(" ", "key"); Assert.False(result.IsSuccess); } } public class DaprBindingServiceTests { [Fact] public async Task InvokeAsync_ReturnsBadRequest_WhenBindingEmpty() { var service = new DaprBindingService(Mock.Of>(), Mock.Of()); var result = await service.InvokeAsync(" ", "create", new { }); Assert.False(result.IsSuccess); } }