From 94d06dd785919177e374ea3ddfc6515787c05b9a Mon Sep 17 00:00:00 2001 From: Piotr Osadkowski Date: Sat, 4 Jan 2025 23:51:36 +0100 Subject: [PATCH 1/4] Added support for null in GetSessionStateAsync SetSessionStateAsync Added Tests --- .../src/ServiceBusSessionMessageActions.cs | 16 +++-- .../ServiceBusSessionMessageActionsTests.cs | 59 +++++++++++++++++++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusSessionMessageActions.cs b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusSessionMessageActions.cs index 6f435a536..69ba2320e 100644 --- a/extensions/Worker.Extensions.ServiceBus/src/ServiceBusSessionMessageActions.cs +++ b/extensions/Worker.Extensions.ServiceBus/src/ServiceBusSessionMessageActions.cs @@ -43,7 +43,7 @@ protected ServiceBusSessionMessageActions() public virtual DateTimeOffset SessionLockedUntil { get; protected set; } /// - public virtual async Task GetSessionStateAsync( + public virtual async Task GetSessionStateAsync( CancellationToken cancellationToken = default) { var request = new GetSessionStateRequest() @@ -52,19 +52,25 @@ public virtual async Task GetSessionStateAsync( }; GetSessionStateResponse result = await _settlement.GetSessionStateAsync(request, cancellationToken: cancellationToken); - BinaryData binaryData = new BinaryData(result.SessionState.Memory); - return binaryData; + + if (result.SessionState is null || result.SessionState.IsEmpty) + { + return null; + } + + return new BinaryData(result.SessionState.Memory); } /// public virtual async Task SetSessionStateAsync( - BinaryData sessionState, + BinaryData? sessionState, CancellationToken cancellationToken = default) + { var request = new SetSessionStateRequest() { SessionId = _sessionId, - SessionState = ByteString.CopyFrom(sessionState.ToMemory().Span), + SessionState = sessionState is null ? ByteString.Empty : ByteString.CopyFrom(sessionState.ToMemory().Span), }; await _settlement.SetSessionStateAsync(request, cancellationToken: cancellationToken); diff --git a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs index d9a508568..7806872f4 100644 --- a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs +++ b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs @@ -49,6 +49,24 @@ public async Task CanRenewSessionLock() await messageActions.RenewSessionLockAsync(); } + [Fact] + public async Task CanSetAndGetSessionStateWithNull() + { + + string testString = "TestString"; + var client = new SessionMockSettlementClient("test", ByteString.CopyFromUtf8(testString)); + var message = ServiceBusModelFactory.ServiceBusReceivedMessage(lockTokenGuid: Guid.NewGuid(), sessionId: "test"); + var messageActions = new ServiceBusSessionMessageActions(client, message.SessionId, message.LockedUntil); + + var afterSetState = await messageActions.GetSessionStateAsync(); + Assert.Equal(testString, afterSetState.ToString()); + + await messageActions.SetSessionStateAsync(null); + var afterSetNullState = await messageActions.GetSessionStateAsync(); + Assert.Null(afterSetNullState); + + } + private class MockSettlementClient : Settlement.SettlementClient { private readonly string _sessionId; @@ -86,5 +104,46 @@ public override AsyncUnaryCall RenewSessionLockAsync(R return new AsyncUnaryCall(Task.FromResult(response), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); } } + + + + + private class SessionMockSettlementClient : Settlement.SettlementClient + { + private readonly string _sessionId; + private ByteString _sessionState; + public SessionMockSettlementClient(string sessionId, ByteString sessionState = null) : base() + { + _sessionId = sessionId; + _sessionState = sessionState ?? ByteString.Empty; + } + + public override AsyncUnaryCall GetSessionStateAsync(GetSessionStateRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) + { + Assert.Equal(_sessionId, request.SessionId); + return new AsyncUnaryCall(Task.FromResult(new GetSessionStateResponse { SessionState = _sessionState }), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); + } + + public override AsyncUnaryCall SetSessionStateAsync(SetSessionStateRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) + { + Assert.Equal(_sessionId, request.SessionId); + _sessionState = request.SessionState; + return new AsyncUnaryCall(Task.FromResult(new Empty()), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); + } + + public override AsyncUnaryCall ReleaseSessionAsync(ReleaseSessionRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) + { + Assert.Equal(_sessionId, request.SessionId); + return new AsyncUnaryCall(Task.FromResult(new Empty()), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); + } + + public override AsyncUnaryCall RenewSessionLockAsync(RenewSessionLockRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) + { + Assert.Equal(_sessionId, request.SessionId); + var response = new RenewSessionLockResponse(); + response.LockedUntil = Timestamp.FromDateTime(DateTime.UtcNow.AddSeconds(30)); + return new AsyncUnaryCall(Task.FromResult(response), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); + } + } } } From 1e4b979b21f59932d90ade854f745e2027cac2bf Mon Sep 17 00:00:00 2001 From: Piotr Osadkowski Date: Mon, 6 Jan 2025 13:12:02 +0100 Subject: [PATCH 2/4] updated release notes --- extensions/Worker.Extensions.ServiceBus/release_notes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/Worker.Extensions.ServiceBus/release_notes.md b/extensions/Worker.Extensions.ServiceBus/release_notes.md index 5b169b47d..cb8eeeab3 100644 --- a/extensions/Worker.Extensions.ServiceBus/release_notes.md +++ b/extensions/Worker.Extensions.ServiceBus/release_notes.md @@ -7,4 +7,5 @@ ### Microsoft.Azure.Functions.Worker.Extensions.ServiceBus 5.22.0 - Updated `Azure.Identity` reference to 1.12.0 -- Updated `Microsoft.Extensions.Azure` to 1.7.5 \ No newline at end of file +- Updated `Microsoft.Extensions.Azure` to 1.7.5 +- Added 'null' support in SetSessionState and GetSessionState methods (#2548) \ No newline at end of file From f1a5e61679785ca55a7dee484a029ddc69f3c7d9 Mon Sep 17 00:00:00 2001 From: Piotr Osadkowski Date: Thu, 9 Jan 2025 15:44:54 +0100 Subject: [PATCH 3/4] Changed to Moq from manual mock Formatting --- .../ServiceBusSessionMessageActionsTests.cs | 91 +++++++++---------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs index 7806872f4..59a6fa751 100644 --- a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs +++ b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs @@ -10,6 +10,7 @@ using Google.Protobuf.WellKnownTypes; using Grpc.Core; using Microsoft.Azure.ServiceBus.Grpc; +using Moq; using Xunit; namespace Microsoft.Azure.Functions.Worker.Extensions.Tests @@ -52,19 +53,50 @@ public async Task CanRenewSessionLock() [Fact] public async Task CanSetAndGetSessionStateWithNull() { - - string testString = "TestString"; - var client = new SessionMockSettlementClient("test", ByteString.CopyFromUtf8(testString)); - var message = ServiceBusModelFactory.ServiceBusReceivedMessage(lockTokenGuid: Guid.NewGuid(), sessionId: "test"); - var messageActions = new ServiceBusSessionMessageActions(client, message.SessionId, message.LockedUntil); - - var afterSetState = await messageActions.GetSessionStateAsync(); - Assert.Equal(testString, afterSetState.ToString()); + var sessionId = "test"; + var initialTestString = "TestInitialState"; + ByteString currentSessionState = ByteString.CopyFromUtf8(initialTestString); + + var mockClient = new Mock(); + + mockClient + .Setup(x => x.GetSessionStateAsync( + It.Is(r => r.SessionId == sessionId), + It.IsAny(), + It.IsAny(), + It.IsAny()) + ) + .Returns(() => CreateAsyncUnaryCall(new GetSessionStateResponse + { + SessionState = currentSessionState + })); + + mockClient + .Setup(x => x.SetSessionStateAsync( + It.Is(r => r.SessionId == sessionId), + It.IsAny(), + It.IsAny(), + It.IsAny()) + ) + .Returns((request, _, _, _) => + { + currentSessionState = request.SessionState; + return CreateAsyncUnaryCall(new Empty()); + }); + + var message = ServiceBusModelFactory.ServiceBusReceivedMessage(lockTokenGuid: Guid.NewGuid(), sessionId: sessionId); + var messageActions = new ServiceBusSessionMessageActions(settlement: mockClient.Object,sessionId: message.SessionId,sessionLockedUntil: message.LockedUntil); + + var initialState = await messageActions.GetSessionStateAsync(); + Assert.Equal(initialTestString, initialState.ToString()); await messageActions.SetSessionStateAsync(null); var afterSetNullState = await messageActions.GetSessionStateAsync(); Assert.Null(afterSetNullState); - + } + private static AsyncUnaryCall CreateAsyncUnaryCall(T response) where T : class + { + return new AsyncUnaryCall(Task.FromResult(response),Task.FromResult(new Metadata()),() => Status.DefaultSuccess,() => new Metadata(),() => { }); } private class MockSettlementClient : Settlement.SettlementClient @@ -104,46 +136,5 @@ public override AsyncUnaryCall RenewSessionLockAsync(R return new AsyncUnaryCall(Task.FromResult(response), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); } } - - - - - private class SessionMockSettlementClient : Settlement.SettlementClient - { - private readonly string _sessionId; - private ByteString _sessionState; - public SessionMockSettlementClient(string sessionId, ByteString sessionState = null) : base() - { - _sessionId = sessionId; - _sessionState = sessionState ?? ByteString.Empty; - } - - public override AsyncUnaryCall GetSessionStateAsync(GetSessionStateRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) - { - Assert.Equal(_sessionId, request.SessionId); - return new AsyncUnaryCall(Task.FromResult(new GetSessionStateResponse { SessionState = _sessionState }), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); - } - - public override AsyncUnaryCall SetSessionStateAsync(SetSessionStateRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) - { - Assert.Equal(_sessionId, request.SessionId); - _sessionState = request.SessionState; - return new AsyncUnaryCall(Task.FromResult(new Empty()), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); - } - - public override AsyncUnaryCall ReleaseSessionAsync(ReleaseSessionRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) - { - Assert.Equal(_sessionId, request.SessionId); - return new AsyncUnaryCall(Task.FromResult(new Empty()), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); - } - - public override AsyncUnaryCall RenewSessionLockAsync(RenewSessionLockRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) - { - Assert.Equal(_sessionId, request.SessionId); - var response = new RenewSessionLockResponse(); - response.LockedUntil = Timestamp.FromDateTime(DateTime.UtcNow.AddSeconds(30)); - return new AsyncUnaryCall(Task.FromResult(response), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { }); - } - } } } From 87132dfc87cd0b718758712d91ee55ecf7d279c9 Mon Sep 17 00:00:00 2001 From: Piotr Osadkowski Date: Thu, 9 Jan 2025 23:16:13 +0100 Subject: [PATCH 4/4] Redesigned tests --- .../ServiceBusSessionMessageActionsTests.cs | 59 ++++++++----------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs index 59a6fa751..7831001d4 100644 --- a/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs +++ b/test/Worker.Extensions.Tests/ServiceBus/ServiceBusSessionMessageActionsTests.cs @@ -50,53 +50,42 @@ public async Task CanRenewSessionLock() await messageActions.RenewSessionLockAsync(); } + [Fact] - public async Task CanSetAndGetSessionStateWithNull() + public async Task CanSetNullSessionState() { - var sessionId = "test"; - var initialTestString = "TestInitialState"; - ByteString currentSessionState = ByteString.CopyFromUtf8(initialTestString); + var mockClient = new Mock(); + var message = ServiceBusModelFactory.ServiceBusReceivedMessage(lockTokenGuid: Guid.NewGuid(), sessionId: "test"); + var messageActions = new ServiceBusSessionMessageActions(mockClient.Object, message.SessionId, message.LockedUntil); + + await messageActions.SetSessionStateAsync(null); + mockClient.Verify(x => x.SetSessionStateAsync( + It.Is(r => r.SessionId == message.SessionId && r.SessionState == ByteString.Empty), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Once); + } + [Fact] + public async Task CanGetNullSessionState() + { var mockClient = new Mock(); - - mockClient - .Setup(x => x.GetSessionStateAsync( - It.Is(r => r.SessionId == sessionId), - It.IsAny(), - It.IsAny(), - It.IsAny()) - ) - .Returns(() => CreateAsyncUnaryCall(new GetSessionStateResponse - { - SessionState = currentSessionState - })); mockClient - .Setup(x => x.SetSessionStateAsync( - It.Is(r => r.SessionId == sessionId), + .Setup(x => x.GetSessionStateAsync( + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()) ) - .Returns((request, _, _, _) => - { - currentSessionState = request.SessionState; - return CreateAsyncUnaryCall(new Empty()); - }); + .Returns(new AsyncUnaryCall(Task.FromResult(new GetSessionStateResponse() { SessionState = ByteString.Empty }), Task.FromResult(new Metadata()), () => Status.DefaultSuccess, () => new Metadata(), () => { })); - var message = ServiceBusModelFactory.ServiceBusReceivedMessage(lockTokenGuid: Guid.NewGuid(), sessionId: sessionId); - var messageActions = new ServiceBusSessionMessageActions(settlement: mockClient.Object,sessionId: message.SessionId,sessionLockedUntil: message.LockedUntil); - - var initialState = await messageActions.GetSessionStateAsync(); - Assert.Equal(initialTestString, initialState.ToString()); + var message = ServiceBusModelFactory.ServiceBusReceivedMessage(lockTokenGuid: Guid.NewGuid(), sessionId: "test"); + var messageActions = new ServiceBusSessionMessageActions(settlement: mockClient.Object, sessionId: message.SessionId, sessionLockedUntil: message.LockedUntil); - await messageActions.SetSessionStateAsync(null); - var afterSetNullState = await messageActions.GetSessionStateAsync(); - Assert.Null(afterSetNullState); - } - private static AsyncUnaryCall CreateAsyncUnaryCall(T response) where T : class - { - return new AsyncUnaryCall(Task.FromResult(response),Task.FromResult(new Metadata()),() => Status.DefaultSuccess,() => new Metadata(),() => { }); + var nullState = await messageActions.GetSessionStateAsync(); + Assert.Null(nullState); } private class MockSettlementClient : Settlement.SettlementClient