diff --git a/samples/DtmSample/Controllers/WfTestController.cs b/samples/DtmSample/Controllers/WfTestController.cs index 0f1d71a..474efa3 100644 --- a/samples/DtmSample/Controllers/WfTestController.cs +++ b/samples/DtmSample/Controllers/WfTestController.cs @@ -6,12 +6,14 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; +using System.Diagnostics; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Exception = System.Exception; namespace DtmSample.Controllers { @@ -65,6 +67,52 @@ public async Task Simple(CancellationToken cancellationToken) } } + [HttpPost("wf-twice")] + public async Task SimpleTwice(CancellationToken cancellationToken) + { + try + { + string wfNameReturnNormal = $"wfNameReturnNormal-{Guid.NewGuid().ToString("N")[..8]}"; + _globalTransaction.Register(wfNameReturnNormal, async (wf, data) => await Task.FromResult(Encoding.UTF8.GetBytes("my result"))); + string wfNameReturnEmpty = $"wfNameReturnEmpty-{Guid.NewGuid().ToString("N")[..8]}"; + _globalTransaction.Register(wfNameReturnEmpty, async (wf, data) => await Task.FromResult(Encoding.UTF8.GetBytes(""))); + string wfNameReturnNull = $"wfNameReturnNull-{Guid.NewGuid().ToString("N")[..8]}"; + _globalTransaction.Register(wfNameReturnNull, (wf, data) => Task.FromResult(null)); + + string req = JsonSerializer.Serialize(new TransRequest("1", -30)); + + string gid; + byte[] result1, result2; + string resultStr1, resultStr2; + gid = wfNameReturnNormal + " " + Guid.NewGuid().ToString("N"); + result1 = await _globalTransaction.Execute(wfNameReturnNormal, gid, Encoding.UTF8.GetBytes(req), true); + result2 = await _globalTransaction.Execute(wfNameReturnNormal, gid, Encoding.UTF8.GetBytes(req), true); + resultStr1 = Encoding.UTF8.GetString(result1); + resultStr2 = Encoding.UTF8.GetString(result2); + if ("my result" != resultStr1) throw new Exception("\"my result\" != resultStr1"); + if (resultStr1 != resultStr2) throw new Exception("resultStr1 != resultStr2"); + + gid = wfNameReturnEmpty + " " + Guid.NewGuid().ToString("N"); + result1 = await _globalTransaction.Execute(wfNameReturnEmpty, gid, Encoding.UTF8.GetBytes(req), true); + result2 = await _globalTransaction.Execute(wfNameReturnEmpty, gid, Encoding.UTF8.GetBytes(req), true); + if (null != result1) throw new Exception("String.Empty != resultStr1"); + if (result1 != result2) throw new Exception("resultStr1 != resultStr2"); + + gid = wfNameReturnNull + " " + Guid.NewGuid().ToString("N"); + result1 = await _globalTransaction.Execute(wfNameReturnNull, gid, Encoding.UTF8.GetBytes(req), true); + result2 = await _globalTransaction.Execute(wfNameReturnNull, gid, Encoding.UTF8.GetBytes(req), true); + if (null != result1) throw new Exception("String.Empty != resultStr1"); + if (result1 != result2) throw new Exception("resultStr1 != resultStr2"); + + return Ok(TransResponse.BuildSucceedResponse()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Workflow Error"); + return Ok(TransResponse.BuildFailureResponse()); + } + } + [HttpPost("wf-saga")] public async Task Saga(CancellationToken cancellationToken) { diff --git a/src/Dtmworkflow/Workflow.Imp.cs b/src/Dtmworkflow/Workflow.Imp.cs index 669e24f..53e6e07 100644 --- a/src/Dtmworkflow/Workflow.Imp.cs +++ b/src/Dtmworkflow/Workflow.Imp.cs @@ -39,7 +39,9 @@ internal async Task Process(WfFunc2 handler, byte[] data) var status = reply.Transaction.Status; if (status == DtmCommon.Constant.StatusSucceed) { - var sRes = Convert.FromBase64String(reply.Transaction.Result); + var sRes = reply.Transaction.Result != null + ? Convert.FromBase64String(reply.Transaction.Result) + : null; return sRes; } else if (status == DtmCommon.Constant.StatusFailed) @@ -75,11 +77,15 @@ internal async Task Process(WfFunc2 handler, byte[] data) } if (err == null || err is DtmCommon.DtmFailureException) - { + { + // The DTM server does not distinguish between byte[0] and null, + // when execute the second time (eg. executeByQs), the previous query result returned is null. await this.Submit(res, err, default); } - - return res; + + // Before the DTM server side (v1.18.0) distinguish between byte[0] and null, + // in order to be compatible with the same return values for twice call, byte[] has been uniformly changed to return null. + return (res != null && res.Length == 0) ? null : res; } private async Task SaveResult(string branchId, string op, StepResult sr) diff --git a/tests/Dtmworkflow.Tests/WorkflowHttpTests.cs b/tests/Dtmworkflow.Tests/WorkflowHttpTests.cs index e4df8e9..4ee5f8c 100644 --- a/tests/Dtmworkflow.Tests/WorkflowHttpTests.cs +++ b/tests/Dtmworkflow.Tests/WorkflowHttpTests.cs @@ -255,8 +255,114 @@ public async void Commit_Should_Be_Executed() rollBackFunc.Verify(x => x.Invoke(It.IsAny()), Times.Never); commitFunc.Verify(x => x.Invoke(It.IsAny()), Times.Once); } + + [Fact] + public async Task Execute_Result_Should_Be_WfFunc2() + { + var factory = new Mock(); + var httpClient = new Mock(); + var grpcClient = new Mock(); + var httpBb = new Mock(); + + SetupPrepareWorkflow(httpClient, DtmCommon.Constant.StatusPrepared, null); + var wf = SetupWorkFlow(httpClient, grpcClient, httpBb); + + factory.Setup(x => x.NewWorkflow(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(wf.Object); + + var wfgt = new WorlflowGlobalTransaction(factory.Object, NullLoggerFactory.Instance); + + var wfName = nameof(Execute_Result_Should_Be_WfFunc2); + var gid = Guid.NewGuid().ToString("N"); + + wfgt.Register(wfName, (workflow, data) => Task.FromResult(Encoding.UTF8.GetBytes("return value from WfFunc2"))); + + var req = JsonSerializer.Serialize(new { userId = "1", amount = 30 }); + var res = await wfgt.Execute(wfName, gid, Encoding.UTF8.GetBytes(req), true); + + Assert.Equal("return value from WfFunc2", Encoding.UTF8.GetString(res)); + } + + [Fact] + public async Task Execute_Result_Should_Be_Previous() + { + var factory = new Mock(); + var httpClient = new Mock(); + var grpcClient = new Mock(); + var httpBb = new Mock(); + + SetupPrepareWorkflow(httpClient, DtmCommon.Constant.StatusSucceed, "return value from previous"); + var wf = SetupWorkFlow(httpClient, grpcClient, httpBb); + + factory.Setup(x => x.NewWorkflow(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(wf.Object); + + var wfgt = new WorlflowGlobalTransaction(factory.Object, NullLoggerFactory.Instance); + + var wfName = nameof(Execute_Result_Should_Be_Previous); + var gid = Guid.NewGuid().ToString("N"); + + wfgt.Register(wfName, (workflow, data) => Task.FromResult(Encoding.UTF8.GetBytes("return value from WfFunc2"))); + + var req = JsonSerializer.Serialize(new { userId = "1", amount = 30 }); + var res = await wfgt.Execute(wfName, gid, Encoding.UTF8.GetBytes(req), true); + + Assert.Equal("return value from previous", Encoding.UTF8.GetString(res)); + } + + [Fact] + public async Task Execute_Again_Result_Should_Be_Previous() + { + var factory = new Mock(); + var httpClient1 = new Mock(); + var httpClient2 = new Mock(); + var grpcClient = new Mock(); + var httpBb = new Mock(); + + // first + SetupPrepareWorkflow(httpClient1, DtmCommon.Constant.StatusPrepared, null); + var wf = SetupWorkFlow(httpClient1, grpcClient, httpBb); + factory.Setup(x => x.NewWorkflow(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(wf.Object); + var wfgt = new WorlflowGlobalTransaction(factory.Object, NullLoggerFactory.Instance); + var wfName = nameof(Execute_Again_Result_Should_Be_Previous); + var gid = Guid.NewGuid().ToString("N"); + wfgt.Register(wfName, (workflow, data) => Task.FromResult(Encoding.UTF8.GetBytes("return value from WfFunc2"))); + var req = JsonSerializer.Serialize(new { userId = "1", amount = 30 }); + var res = await wfgt.Execute(wfName, gid, Encoding.UTF8.GetBytes(req), true); + Assert.Equal("return value from WfFunc2", Encoding.UTF8.GetString(res)); + + // again + SetupPrepareWorkflow(httpClient2, DtmCommon.Constant.StatusSucceed, "return value from previous"); + wf = SetupWorkFlow(httpClient2, grpcClient, httpBb); + factory.Setup(x => x.NewWorkflow(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(wf.Object); + wfgt = new WorlflowGlobalTransaction(factory.Object, NullLoggerFactory.Instance); + gid = Guid.NewGuid().ToString("N"); + wfgt.Register(wfName, (workflow, data) => Task.FromResult(Encoding.UTF8.GetBytes("return value from WfFunc2"))); + req = JsonSerializer.Serialize(new { userId = "1", amount = 30 }); + res = await wfgt.Execute(wfName, gid, Encoding.UTF8.GetBytes(req), true); + Assert.Equal("return value from previous", Encoding.UTF8.GetString(res)); + } + + [Fact] + public async Task Execute_Again_Result_StringEmpty() + { + var factory = new Mock(); + var httpClient = new Mock(); + var grpcClient = new Mock(); + var httpBb = new Mock(); + + // again + SetupPrepareWorkflow(httpClient, DtmCommon.Constant.StatusSucceed, null); + var wf = SetupWorkFlow(httpClient, grpcClient, httpBb); + factory.Setup(x => x.NewWorkflow(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(wf.Object); + var wfgt = new WorlflowGlobalTransaction(factory.Object, NullLoggerFactory.Instance); + var wfName = nameof(Execute_Again_Result_StringEmpty); + var gid = Guid.NewGuid().ToString("N"); + wfgt.Register(wfName, (workflow, data) => Task.FromResult(Encoding.UTF8.GetBytes("return value from WfFunc2"))); + var req = JsonSerializer.Serialize(new { userId = "1", amount = 30 }); + var res = await wfgt.Execute(wfName, gid, Encoding.UTF8.GetBytes(req), true); + Assert.Null(res); + } - private void SetupPrepareWorkflow(Mock httpClient, string status, string result, List progressDtos = null) + private void SetupPrepareWorkflow(Mock httpClient, string status, string? result, List progressDtos = null) { var httpResp = new HttpResponseMessage(HttpStatusCode.OK); httpResp.Content = new StringContent(JsonSerializer.Serialize( @@ -265,9 +371,9 @@ private void SetupPrepareWorkflow(Mock httpClient, string status, st Transaction = new DtmTransactionDto { Status = status, - Result = Convert.ToBase64String(Encoding.UTF8.GetBytes(result)) + Result = result == null ? null : Convert.ToBase64String(Encoding.UTF8.GetBytes(result)) }, - Progresses = progressDtos + Progresses = progressDtos ?? [] })); httpClient.Setup(x => x.PrepareWorkflow(It.IsAny(), It.IsAny())).Returns(Task.FromResult(httpResp)); }