diff --git a/.github/workflows/build_and_it.yml b/.github/workflows/build_and_it.yml index 30895bf..e43a758 100644 --- a/.github/workflows/build_and_it.yml +++ b/.github/workflows/build_and_it.yml @@ -56,9 +56,9 @@ jobs: curl "127.0.0.1:36789/api/dtmsvr/newGid" - name: Setup Busi Service run: | - cd tests/BusiGrpcService + cd tests/BusiIntegrationService nohup dotnet run > /home/runner/work/client-csharp/client-csharp/logs/app.log 2>&1 & - - name: Run Integration Tests + - name: Run Integration Tests (Dtmgrpc) env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_NOLOGO: 1 @@ -66,6 +66,14 @@ jobs: run: | dotnet build tests/Dtmgrpc.IntegrationTests/Dtmgrpc.IntegrationTests.csproj dotnet test --framework=net8.0 tests/Dtmgrpc.IntegrationTests/Dtmgrpc.IntegrationTests.csproj + - name: Run Integration Tests (Dtmcli) + env: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 + DOTNET_CLI_HOME: ${{ runner.temp }}/dotnet-cli-home + run: | + dotnet build tests/Dtmcli.IntegrationTests/Dtmcli.IntegrationTests.csproj + dotnet test --framework=net8.0 tests/Dtmcli.IntegrationTests/Dtmcli.IntegrationTests.csproj - name: Upload logs if: always() uses: actions/upload-artifact@v4 diff --git a/DtmClient.sln b/DtmClient.sln index a18b3bd..5b296e3 100644 --- a/DtmClient.sln +++ b/DtmClient.sln @@ -25,7 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AFCF4E29-660 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ED5B84F1-876F-4617-A4F4-5C160319310C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BusiGrpcService", "tests\BusiGrpcService\BusiGrpcService.csproj", "{FFF41358-6974-4E87-840D-C61E1B6BEDC3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BusiIntegrationService", "tests\BusiIntegrationService\BusiIntegrationService.csproj", "{FFF41358-6974-4E87-840D-C61E1B6BEDC3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dtmgrpc.IntegrationTests", "tests\Dtmgrpc.IntegrationTests\Dtmgrpc.IntegrationTests.csproj", "{AFDB3DCD-55CE-402F-A1B0-FCD4737FE314}" EndProject @@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dtmgrpc.StrongName", "src\s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dtmworkflow.StrongName", "src\strong-name\Dtmworkflow.StrongName\Dtmworkflow.StrongName.csproj", "{96634D84-A11E-448C-8033-CC643F96558A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dtmcli.IntegrationTests", "tests\Dtmcli.IntegrationTests\Dtmcli.IntegrationTests.csproj", "{500E31D7-71A3-4DE6-8CFF-8AEB416A968F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -125,6 +127,10 @@ Global {96634D84-A11E-448C-8033-CC643F96558A}.Debug|Any CPU.Build.0 = Debug|Any CPU {96634D84-A11E-448C-8033-CC643F96558A}.Release|Any CPU.ActiveCfg = Release|Any CPU {96634D84-A11E-448C-8033-CC643F96558A}.Release|Any CPU.Build.0 = Release|Any CPU + {500E31D7-71A3-4DE6-8CFF-8AEB416A968F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {500E31D7-71A3-4DE6-8CFF-8AEB416A968F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {500E31D7-71A3-4DE6-8CFF-8AEB416A968F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {500E31D7-71A3-4DE6-8CFF-8AEB416A968F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -149,6 +155,7 @@ Global {3196931A-26B1-4C48-BA66-003356153C34} = {A8DF688D-43CE-4227-B5FD-F65760DE7967} {60051A80-A987-43B7-A5D1-284B8FD330D4} = {A8DF688D-43CE-4227-B5FD-F65760DE7967} {96634D84-A11E-448C-8033-CC643F96558A} = {A8DF688D-43CE-4227-B5FD-F65760DE7967} + {500E31D7-71A3-4DE6-8CFF-8AEB416A968F} = {ED5B84F1-876F-4617-A4F4-5C160319310C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A872C3EE-D0B6-4943-8C7D-9ADD28AC029A} diff --git a/tests/BusiGrpcService/Program.cs b/tests/BusiGrpcService/Program.cs deleted file mode 100644 index be2cc0d..0000000 --- a/tests/BusiGrpcService/Program.cs +++ /dev/null @@ -1,25 +0,0 @@ -using BusiGrpcService.Services; -using Dtmgrpc; -using Microsoft.AspNetCore.Server.Kestrel.Core; - -var builder = WebApplication.CreateBuilder(args); - -builder.WebHost.ConfigureKestrel(options => -{ - // Setup a HTTP/2 endpoint without TLS. - options.ListenLocalhost(5005, o => o.Protocols = HttpProtocols.Http2); -}); - -builder.Services.AddGrpc(); -builder.Services.AddDtmGrpc(x => -{ - x.DtmGrpcUrl = "http://localhost:36790"; -}); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -app.MapGrpcService(); -app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); - -app.Run(); diff --git a/tests/BusiGrpcService/appsettings.json b/tests/BusiGrpcService/appsettings.json deleted file mode 100644 index 95303fd..0000000 --- a/tests/BusiGrpcService/appsettings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning", - "Dtm": "Debug" - } - }, - "AllowedHosts": "*", - "Kestrel": { - "EndpointDefaults": { - "Protocols": "Http2" - } - } -} diff --git a/tests/BusiGrpcService/BusiGrpcService.csproj b/tests/BusiIntegrationService/BusiIntegrationService.csproj similarity index 85% rename from tests/BusiGrpcService/BusiGrpcService.csproj rename to tests/BusiIntegrationService/BusiIntegrationService.csproj index b896af4..61f2f91 100644 --- a/tests/BusiGrpcService/BusiGrpcService.csproj +++ b/tests/BusiIntegrationService/BusiIntegrationService.csproj @@ -4,6 +4,7 @@ net8.0 enable + BusiIntegrationService @@ -12,6 +13,7 @@ + diff --git a/tests/BusiIntegrationService/Controllers/BusiApiController.cs b/tests/BusiIntegrationService/Controllers/BusiApiController.cs new file mode 100644 index 0000000..94b28f1 --- /dev/null +++ b/tests/BusiIntegrationService/Controllers/BusiApiController.cs @@ -0,0 +1,78 @@ +using System.Text.Json; +using BusiIntegrationService.Dtos; +using Microsoft.AspNetCore.Mvc; + +namespace BusiIntegrationService.Controllers +{ + [ApiController] + [Route("http/busi.Busi")] + public class BusiApiController : ControllerBase + { + private readonly ILogger _logger; + private readonly Dtmcli.IBranchBarrierFactory _barrierFactory; + private readonly Dtmgrpc.IBranchBarrierFactory _grpcBarrierFactory; + + public BusiApiController(ILogger logger, Dtmcli.IBranchBarrierFactory barrierFactory, Dtmgrpc.IBranchBarrierFactory grpcBarrierFactory) + { + _logger = logger; + _barrierFactory = barrierFactory; + _grpcBarrierFactory = grpcBarrierFactory; + } + + [HttpGet("Test")] + public async Task Test() + { + return this.Ok(nameof(this.Test)); + } + + [HttpPost("TransIn")] + public async Task TransIn([FromBody] BusiRequest request) + { + _logger.LogInformation("TransIn req={req}", JsonSerializer.Serialize(request)); + + if (DateTime.Now < request.EffectTime) + return this.StatusCode(425, new { error = "Early" }); + + if (string.IsNullOrWhiteSpace(request.TransInResult) || request.TransInResult.Equals("SUCCESS")) + { + await Task.CompletedTask; + return Ok(); + } + else if (request.TransInResult.Equals("FAILURE")) + { + return StatusCode(422, new { error = "FAILURE" }); // 422 Unprocessable Entity for business failure + } + else if (request.TransInResult.Equals("ONGOING")) + { + return StatusCode(425, new { error = "ONGOING" }); // 425 Too Early for ongoing state + } + + return StatusCode(500, new { error = $"unknown result {request.TransInResult}" }); + } + + [HttpPost("TransOut")] + public async Task TransOut([FromBody] BusiRequest request) + { + _logger.LogInformation("TransOut req={req}", JsonSerializer.Serialize(request)); + + if (DateTime.Now < request.EffectTime) + return this.StatusCode(425, new { error = "Early" }); + + if (string.IsNullOrWhiteSpace(request.TransOutResult) || request.TransOutResult.Equals("SUCCESS")) + { + await Task.CompletedTask; + return Ok(); + } + else if (request.TransOutResult.Equals("FAILURE")) + { + return StatusCode(422, new { error = "FAILURE" }); // 422 Unprocessable Entity for business failure + } + else if (request.TransOutResult.Equals("ONGOING")) + { + return StatusCode(425, new { error = "ONGOING" }); // 425 Too Early for ongoing state + } + + return StatusCode(500, new { error = $"unknown result {request.TransOutResult}" }); + } + } +} \ No newline at end of file diff --git a/tests/BusiIntegrationService/Dtos/BusiRequest.cs b/tests/BusiIntegrationService/Dtos/BusiRequest.cs new file mode 100644 index 0000000..6ce0949 --- /dev/null +++ b/tests/BusiIntegrationService/Dtos/BusiRequest.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace BusiIntegrationService.Dtos +{ + public class BusiRequest + { + [JsonPropertyName("amount")] + public long Amount { get; set; } + + [JsonPropertyName("transOutResult")] + public string TransOutResult { get; set; } = string.Empty; + + [JsonPropertyName("transInResult")] + public string TransInResult { get; set; } = string.Empty; + + [JsonPropertyName("effectTime")] public DateTime EffectTime { get; set; } + } + + public class BusiReply + { + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/tests/BusiIntegrationService/Program.cs b/tests/BusiIntegrationService/Program.cs new file mode 100644 index 0000000..1604a09 --- /dev/null +++ b/tests/BusiIntegrationService/Program.cs @@ -0,0 +1,36 @@ +using BusiIntegrationService; +using BusiIntegrationService.Services; +using Dtmcli; +using Dtmgrpc; +using Microsoft.AspNetCore.Server.Kestrel.Core; + +// Enable HTTP/2 support for unencrypted HTTP connections (required for gRPC over HTTP) +AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddGrpc(options => +{ + // Configure gRPC to allow unencrypted HTTP/2 connections (for local development) + options.EnableDetailedErrors = true; +}); +builder.Services.AddDtmGrpc(x => +{ + x.DtmGrpcUrl = "http://localhost:36790"; +}); +builder.Services.AddDtmcli(option => +{ + option.DtmUrl = "http://localhost:36789"; +}); + +// Add controllers for HTTP API +builder.Services.AddControllers(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +app.MapGrpcService(); +app.MapControllers(); // Map the HTTP API controllers +app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + +app.Run(); \ No newline at end of file diff --git a/tests/BusiGrpcService/Properties/launchSettings.json b/tests/BusiIntegrationService/Properties/launchSettings.json similarity index 68% rename from tests/BusiGrpcService/Properties/launchSettings.json rename to tests/BusiIntegrationService/Properties/launchSettings.json index 5dd0f5f..0a17a79 100644 --- a/tests/BusiGrpcService/Properties/launchSettings.json +++ b/tests/BusiIntegrationService/Properties/launchSettings.json @@ -1,13 +1,12 @@ { "profiles": { - "BusiGrpcService": { + "BusiIntegrationService": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, - "applicationUrl": "http://localhost:5251;https://localhost:7251", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } -} +} \ No newline at end of file diff --git a/tests/BusiGrpcService/Services/BusiApiService.cs b/tests/BusiIntegrationService/Services/BusiApiService.cs similarity index 94% rename from tests/BusiGrpcService/Services/BusiApiService.cs rename to tests/BusiIntegrationService/Services/BusiApiService.cs index a8f1370..7a56c19 100644 --- a/tests/BusiGrpcService/Services/BusiApiService.cs +++ b/tests/BusiIntegrationService/Services/BusiApiService.cs @@ -8,7 +8,7 @@ using DtmCommon; using DtmSERedisBarrier; -namespace BusiGrpcService.Services +namespace BusiIntegrationService.Services { public class BusiApiService : Busi.BusiBase { @@ -27,6 +27,9 @@ public BusiApiService(ILogger logger, Dtmgrpc.IDtmgRPCClient cli public override async Task TransIn(BusiReq request, ServerCallContext context) { _logger.LogInformation("TransIn req={req}", JsonSerializer.Serialize(request)); + + if (request.EffectTime != null && DateTime.UtcNow < request.EffectTime.ToDateTime()) + throw new Grpc.Core.RpcException(new Status(StatusCode.FailedPrecondition, "ONGOING")); if (string.IsNullOrWhiteSpace(request.TransInResult) || request.TransInResult.Equals("SUCCESS")) { @@ -49,6 +52,9 @@ public override async Task TransInTcc(BusiReq request, ServerCallContext { _logger.LogInformation("TransIn req={req}", JsonSerializer.Serialize(request)); + if (request.EffectTime != null && request.EffectTime.ToDateTime() < DateTime.Now) + throw new Grpc.Core.RpcException(new Status(StatusCode.FailedPrecondition, "ONGOING")); + if (string.IsNullOrWhiteSpace(request.TransInResult) || request.TransInResult.Equals("SUCCESS")) { await Task.CompletedTask; @@ -87,6 +93,10 @@ public override async Task TransInRevert(BusiReq request, ServerCallConte public override async Task TransOut(BusiReq request, ServerCallContext context) { _logger.LogInformation("TransOut req={req}", JsonSerializer.Serialize(request)); + + if (request.EffectTime != null && DateTime.UtcNow < request.EffectTime.ToDateTime()) + throw new Grpc.Core.RpcException(new Status(StatusCode.FailedPrecondition, "ONGOING")); + await Task.CompletedTask; return new Empty(); } diff --git a/tests/BusiGrpcService/appsettings.Development.json b/tests/BusiIntegrationService/appsettings.Development.json similarity index 100% rename from tests/BusiGrpcService/appsettings.Development.json rename to tests/BusiIntegrationService/appsettings.Development.json diff --git a/tests/BusiIntegrationService/appsettings.json b/tests/BusiIntegrationService/appsettings.json new file mode 100644 index 0000000..531dd56 --- /dev/null +++ b/tests/BusiIntegrationService/appsettings.json @@ -0,0 +1,22 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Dtm": "Debug" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "Endpoints": { + "myHttp": { + "Url": "http://localhost:5005", + "Protocols": "Http2" + }, + "myGRPC": { + "Url": "http://localhost:5006", + "Protocols": "Http1" + } + } + } +} \ No newline at end of file diff --git a/tests/Dtmcli.IntegrationTests/BusiRequest.cs b/tests/Dtmcli.IntegrationTests/BusiRequest.cs new file mode 100644 index 0000000..c5b8bd4 --- /dev/null +++ b/tests/Dtmcli.IntegrationTests/BusiRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.Json.Serialization; + +namespace Dtmcli.IntegrationTests; + +public class BusiRequest +{ + [JsonPropertyName("amount")] public long Amount { get; set; } + + [JsonPropertyName("transOutResult")] public string TransOutResult { get; set; } = string.Empty; + + [JsonPropertyName("transInResult")] public string TransInResult { get; set; } = string.Empty; + [JsonPropertyName("effectTime")] public DateTime EffectTime { get; set; } +} + +public class BusiReply +{ + [JsonPropertyName("message")] public string Message { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/tests/Dtmcli.IntegrationTests/Dtmcli.IntegrationTests.csproj b/tests/Dtmcli.IntegrationTests/Dtmcli.IntegrationTests.csproj new file mode 100644 index 0000000..41a5ba0 --- /dev/null +++ b/tests/Dtmcli.IntegrationTests/Dtmcli.IntegrationTests.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + diff --git a/tests/Dtmcli.IntegrationTests/ITTestHelper.cs b/tests/Dtmcli.IntegrationTests/ITTestHelper.cs new file mode 100644 index 0000000..3d70854 --- /dev/null +++ b/tests/Dtmcli.IntegrationTests/ITTestHelper.cs @@ -0,0 +1,67 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Dtmcli.IntegrationTests; + +public class ITTestHelper +{ + public static string DTMHttpUrl = "http://localhost:36789"; + public static string BuisHttpUrl = "http://localhost:5006/http"; + private static System.Net.Http.HttpClient _client = new System.Net.Http.HttpClient(); + + public static async Task GetTranStatus(string gid) + { + var resp = await _client.GetAsync($"{DTMHttpUrl}/api/dtmsvr/query?gid={gid}").ConfigureAwait(false); + + if (resp.IsSuccessStatusCode) + { + var content = await resp.Content.ReadAsStringAsync(); + var res = System.Text.Json.JsonSerializer.Deserialize(content); + return res.Transaction.Status; + } + + return string.Empty; + } + + public class QueryResult + { + public class TransBranchStore + { + } + + public class TransGlobalStore + { + [System.Text.Json.Serialization.JsonPropertyName("status")] + public string Status { get; set; } + } + + + [System.Text.Json.Serialization.JsonPropertyName("branches")] + public List Branches { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("transaction")] + public TransGlobalStore Transaction { get; set; } + } + + public static BusiRequest GenBusiReq(bool outFailed, bool inFailed, int amount = 30) + { + return new BusiRequest + { + Amount = amount, + TransOutResult = outFailed ? "FAILURE" : "", + TransInResult = inFailed ? "FAILURE" : "" + }; + } + + public static ServiceProvider AddDtmHttp(int dtmTimout = 10000) + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddHttpClient(); + services.AddDtmcli(option => { option.DtmUrl = DTMHttpUrl; }); + var provider = services.BuildServiceProvider(); + return provider; + } +} \ No newline at end of file diff --git a/tests/Dtmcli.IntegrationTests/MsgHttpTest.cs b/tests/Dtmcli.IntegrationTests/MsgHttpTest.cs new file mode 100644 index 0000000..fc3e1c6 --- /dev/null +++ b/tests/Dtmcli.IntegrationTests/MsgHttpTest.cs @@ -0,0 +1,62 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Dtmcli.IntegrationTests; + +public class MsgHttpTest +{ + [Fact] + public async Task Submit_Should_Succeed() + { + var provider = ITTestHelper.AddDtmHttp(); + var transFactory = provider.GetRequiredService(); + + var gid = "msgTestGid" + Guid.NewGuid().ToString(); + var msg = transFactory.NewMsg(gid); + msg.EnableWaitResult(); + var req = ITTestHelper.GenBusiReq(false, false); + var busiUrl = ITTestHelper.BuisHttpUrl; + msg.Add(busiUrl + "/busi.Busi/TransOut", req) + .Add(busiUrl + "/busi.Busi/TransIn", req); + + await msg.Prepare(busiUrl + "/busi.Busi/QueryPrepared_404"); + await msg.Submit(); + + var status = await ITTestHelper.GetTranStatus(gid); + Assert.Equal("succeed", status); + } + + [Fact] + public async Task Submit_With_EffectTime_Should_Succeed_Later() + { + var provider = ITTestHelper.AddDtmHttp(); + var transFactory = provider.GetRequiredService(); + + var gid = "msgTestGid" + Guid.NewGuid().ToString(); + DateTime effectTime = DateTime.Now.AddSeconds(10); + var msg = transFactory.NewMsg(gid); + var req = ITTestHelper.GenBusiReq(false, false); + req.EffectTime = effectTime; + var busiUrl = ITTestHelper.BuisHttpUrl; + msg.Add(busiUrl + "/busi.Busi/TransOut", req) + .Add(busiUrl + "/busi.Busi/TransIn", req); + + await msg.Prepare(busiUrl + "/busi.Busi/QueryPrepared_404"); + await msg.Submit(); + + // Since the downstream execution is delayed by 10 seconds, it will be 'submitted' after 2 seconds and 'succeed' after 15 seconds + await Task.Delay(TimeSpan.FromSeconds(0)); + var status = await ITTestHelper.GetTranStatus(gid); + Assert.Equal("submitted", status); + + await Task.Delay(TimeSpan.FromSeconds(2)); + status = await ITTestHelper.GetTranStatus(gid); + Assert.Equal("submitted", status); + + await Task.Delay(TimeSpan.FromSeconds(13)); + status = await ITTestHelper.GetTranStatus(gid); + Assert.Equal("succeed", status); + } +} \ No newline at end of file diff --git a/tests/Dtmgrpc.IntegrationTests/MsgGrpcTest.cs b/tests/Dtmgrpc.IntegrationTests/MsgGrpcTest.cs index fed4cdb..38d0d0c 100644 --- a/tests/Dtmgrpc.IntegrationTests/MsgGrpcTest.cs +++ b/tests/Dtmgrpc.IntegrationTests/MsgGrpcTest.cs @@ -20,6 +20,7 @@ public async Task Submit_Should_Succeed() var gid = "msgTestGid" + Guid.NewGuid().ToString(); var msg = transFactory.NewMsgGrpc(gid); + msg.EnableWaitResult(); var req = ITTestHelper.GenBusiReq(false, false); var busiGrpc = ITTestHelper.BuisgRPCUrl; msg.Add(busiGrpc + "/busi.Busi/TransOut", req) @@ -27,8 +28,7 @@ public async Task Submit_Should_Succeed() await msg.Prepare(busiGrpc + "/busi.Busi/QueryPrepared"); await msg.Submit(); - - await Task.Delay(2000); + var status = await ITTestHelper.GetTranStatus(gid); Assert.Equal("succeed", status); } @@ -63,6 +63,35 @@ await branchBarrier.Call(conn, () => var status = await ITTestHelper.GetTranStatus(gid); Assert.Equal("succeed", status); } + + [Fact] + public async Task Submit_With_EffectTime_Should_Succeed_Later() + { + var provider = ITTestHelper.AddDtmGrpc(); + var transFactory = provider.GetRequiredService(); + + var gid = "msgTestGid" + Guid.NewGuid().ToString(); + DateTime effectTime = DateTime.Now.AddSeconds(10); + var msg = transFactory.NewMsgGrpc(gid); + var req = ITTestHelper.GenBusiReq(false, false); + req.EffectTime = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(effectTime.ToUniversalTime()); + var busiGrpc = ITTestHelper.BuisgRPCUrl; + msg.Add(busiGrpc + "/busi.Busi/TransOut", req) + .Add(busiGrpc + "/busi.Busi/TransIn", req); + + await msg.Prepare(busiGrpc + "/busi.Busi/QueryPrepared"); + await msg.Submit(); + + // Since the downstream execution is delayed by 10 seconds, it will be 'submitted' after 2 seconds and 'succeed' after 15 seconds + await Task.Delay(TimeSpan.FromSeconds(2)); + var status = await ITTestHelper.GetTranStatus(gid); + Assert.Equal("submitted", status); + + await Task.Delay(TimeSpan.FromSeconds(13)); + status = await ITTestHelper.GetTranStatus(gid); + Assert.Equal("succeed", status); + } + private static readonly int TransOutUID = 1; diff --git a/tests/protos/busi.proto b/tests/protos/busi.proto index 932ef1e..ee0b85a 100644 --- a/tests/protos/busi.proto +++ b/tests/protos/busi.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package busi; import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; option csharp_namespace = "busi"; option go_package = "./busi"; @@ -11,6 +12,7 @@ message BusiReq { int64 Amount = 1; string TransOutResult = 2; string TransInResult = 3; + google.protobuf.Timestamp EffectTime = 4; } message BusiReply {