From 9e3f8e33b0ce9650e6a1bf9e6d7aba988a78dcae Mon Sep 17 00:00:00 2001 From: catcherwong Date: Thu, 19 May 2022 16:21:38 +0800 Subject: [PATCH 1/2] test: add more tests --- .github/workflows/build.yml | 20 ++- .gitignore | 1 + src/Dtmcli.Tests/DtmClientTests.cs | 160 ++++++++++++++++++ src/Dtmcli.Tests/DtmTransFactoryTest.cs | 38 +++++ src/Dtmcli.Tests/MsgTests.cs | 17 +- src/Dtmcli.Tests/SagaTests.cs | 6 +- .../ServiceCollectionExtensionsTests.cs | 32 +++- src/Dtmcli.Tests/TccTests.cs | 21 +++ src/Dtmcli.Tests/UtilsTests.cs | 66 ++++++++ src/Dtmcli/DtmImp/Utils.cs | 4 +- src/Dtmcli/Dtmcli.csproj | 2 +- 11 files changed, 360 insertions(+), 7 deletions(-) create mode 100644 src/Dtmcli.Tests/DtmClientTests.cs create mode 100644 src/Dtmcli.Tests/DtmTransFactoryTest.cs create mode 100644 src/Dtmcli.Tests/UtilsTests.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0fd6454..22a7ef0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,4 +51,22 @@ jobs: - name: Run tests on net6.0 run: | - dotnet test --framework=net6.0 src/Dtmcli.Tests/Dtmcli.Tests.csproj \ No newline at end of file + dotnet test --framework=net6.0 src/Dtmcli.Tests/Dtmcli.Tests.csproj --collect:"XPlat Code Coverage" + + - name: Prepare Codecov + run: | + mkdir ${{ github.workspace }}/coverage/ + + cp ${{ github.workspace }}/src/Dtmcli.Tests/TestResults/*/coverage.cobertura.xml ${{ github.workspace }}/coverage/ + + ls ${{ github.workspace }}/coverage/ + + - name: Upload coverage to Codecov + if: ${{ matrix.os == 'ubuntu-latest' }} + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true + files: ${{ github.workspace }}/coverage/coverage.cobertura.xml + name: codecov-umbrella + verbose: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 139baac..7b5b376 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.xap *.user /TestResults +TestResults/ *.suo *.cache *.docstates diff --git a/src/Dtmcli.Tests/DtmClientTests.cs b/src/Dtmcli.Tests/DtmClientTests.cs new file mode 100644 index 0000000..2ca5c66 --- /dev/null +++ b/src/Dtmcli.Tests/DtmClientTests.cs @@ -0,0 +1,160 @@ +using Xunit; +using System.Net; +using Moq; +using System.Net.Http; +using System.Threading.Tasks; +using System.Threading; + +namespace Dtmcli.Tests +{ + public class DtmClientTests + { + [Fact] + public async void GenGid_Should_Succeed() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, "{\"dtm_result\":\"SUCCESS\",\"gid\":\"123\"}"); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + + var client = new DtmClient(factory.Object, options); + + var res = await client.GenGid(new CancellationToken()); + + Assert.Equal("123", res); + } + + [Fact] + public async void GenGid_Should_Throw_Failure_Exception() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.BadGateway, ""); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + + var client = new DtmClient(factory.Object, options); + + await Assert.ThrowsAsync( async()=> await client.GenGid(new CancellationToken())); + } + + [Fact] + public async void TransRegisterBranch_Should_Succeed() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, ""); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + + var client = new DtmClient(factory.Object, options); + + var tb = new DtmCommon.TransBase() { Gid = "123", TransType = "tcc" }; + + await client.TransRegisterBranch(tb, null, "OP", new CancellationToken()); + } + + [Fact] + public async void TransRegisterBranch_With_Added_Should_Succeed() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, ""); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + + var client = new DtmClient(factory.Object, options); + + var tb = new DtmCommon.TransBase() { Gid = "123", TransType = "tcc" }; + var added = new System.Collections.Generic.Dictionary() { { "a", "b" } }; + + await client.TransRegisterBranch(tb, added, "OP", new CancellationToken()); + } + + [Fact] + public async void TransRequestBranch_Should_Succeed() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, ""); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + + var client = new DtmClient(factory.Object, options); + + var tb = new DtmCommon.TransBase() + { + Gid = "123", + TransType = "tcc", + }; + + await client.TransRequestBranch(tb, HttpMethod.Post, new { }, "00", "try", "http://www.baidu.com?a=1", new CancellationToken()); + } + + [Fact] + public async void TransRequestBranch_With_BranchHeaders_Should_Succeed() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, ""); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + + var client = new DtmClient(factory.Object, options); + + var tb = new DtmCommon.TransBase() + { + Gid = "123", + TransType = "tcc", + BranchHeaders = new System.Collections.Generic.Dictionary { { "a", "b" } } + }; + + await client.TransRequestBranch(tb, HttpMethod.Post, new { }, "00", "try", "http://www.baidu.com", new CancellationToken()); + } + +#if NET5_0_OR_GREATER + [Fact] + public void TransBaseFromQuery_Should_Succeed() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, ""); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + + var client = new DtmClient(factory.Object, options); + + var dict = new System.Collections.Generic.Dictionary() + { + { "branch_id","11" }, + { "gid","1111" }, + { "op","try" }, + { "trans_type","tcc" }, + }; + + var qs = new Microsoft.AspNetCore.Http.QueryCollection(dict); + + var tb = client.TransBaseFromQuery(qs); + + Assert.Equal(dict["op"], tb.Op); + Assert.Equal(dict["gid"], tb.Gid); + Assert.Equal(dict["trans_type"], tb.TransType); + Assert.Equal(dict["branch_id"], tb.BranchIDGen.BranchID); + } +#endif + } + + internal class ClientMockHttpMessageHandler : DelegatingHandler + { + private readonly HttpStatusCode _code; + private readonly string _msg; + public ClientMockHttpMessageHandler(HttpStatusCode code, string msg) + { + this._code = code; + this._msg = msg; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var content = new StringContent(_msg); + var resp = new HttpResponseMessage(_code); + resp.Content = content; + + return Task.FromResult(resp); + } + } +} \ No newline at end of file diff --git a/src/Dtmcli.Tests/DtmTransFactoryTest.cs b/src/Dtmcli.Tests/DtmTransFactoryTest.cs new file mode 100644 index 0000000..96a4071 --- /dev/null +++ b/src/Dtmcli.Tests/DtmTransFactoryTest.cs @@ -0,0 +1,38 @@ +using Moq; +using Xunit; + +namespace Dtmcli.Tests +{ + public class DtmTransFactoryTest + { + [Fact] + public void NewMsg_Should_Succeed() + { + var dtmClient = new Mock(); + var bbFactory = new Mock(); + + var tFactory = new DtmTransFactory(dtmClient.Object, bbFactory.Object); + + var gid = "TestMsgNormal"; + + var msg = tFactory.NewMsg(gid); + + Assert.NotNull(msg); + } + + [Fact] + public void NewSaga_Should_Succeed() + { + var dtmClient = new Mock(); + var bbFactory = new Mock(); + + var tFactory = new DtmTransFactory(dtmClient.Object, bbFactory.Object); + + var gid = "TestSagaNormal"; + + var saga = tFactory.NewSaga(gid); + + Assert.NotNull(saga); + } + } +} \ No newline at end of file diff --git a/src/Dtmcli.Tests/MsgTests.cs b/src/Dtmcli.Tests/MsgTests.cs index a885298..0f0f8db 100644 --- a/src/Dtmcli.Tests/MsgTests.cs +++ b/src/Dtmcli.Tests/MsgTests.cs @@ -52,7 +52,8 @@ public async void Submit_Should_Succeed() { { "bh1", "123" }, { "bh2", "456" }, - }); + }) + .SetPassthroughHeaders(new List { "bh1" }); await msg.Prepare(busi + "/query"); await msg.Submit(); @@ -60,6 +61,19 @@ public async void Submit_Should_Succeed() Assert.True(true); } + [Fact] + public async void DoAndSubmit_Should_Throw_Exception_When_BB_InValid() + { + var dtmClient = new Mock(); + var bbFactory = new Mock(); + + bbFactory.Setup(x => x.CreateBranchBarrier(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), null)) + .Returns(new BranchBarrier("", "", "", "", null, null)); + + var msg = new Msg(dtmClient.Object, bbFactory.Object, "123"); + await Assert.ThrowsAnyAsync(async () => await msg.DoAndSubmit("", x => Task.CompletedTask)); + } + [Fact] public async void DoAndSubmitDB_Should_Throw_Exception_When_Transbase_InValid() { @@ -197,6 +211,7 @@ protected override async Task SendAsync(HttpRequestMessage Assert.Contains("bh2", transBase.BranchHeaders.Keys); Assert.Equal(2, transBase.Payloads.Count); Assert.Equal(2, transBase.Steps.Count); + Assert.Contains("bh1", transBase.PassthroughHeaders); var content = new StringContent("{\"dtm_result\":\"SUCCESS\"}"); diff --git a/src/Dtmcli.Tests/SagaTests.cs b/src/Dtmcli.Tests/SagaTests.cs index 9d9072a..9de503f 100644 --- a/src/Dtmcli.Tests/SagaTests.cs +++ b/src/Dtmcli.Tests/SagaTests.cs @@ -44,9 +44,12 @@ public async void Submit_Should_Succeed() { { "bh1", "123" }, { "bh2", "456" }, - }); + }) + .SetPassthroughHeaders(new List { "bh1" }); await sage.Submit(); + + Assert.NotNull(sage.GetTransBase()); } [Fact] @@ -121,6 +124,7 @@ protected override async Task SendAsync(HttpRequestMessage Assert.Contains("bh2", transBase.BranchHeaders.Keys); Assert.Equal(4, transBase.Payloads.Count); Assert.Equal(4, transBase.Steps.Count); + Assert.Contains("bh1", transBase.PassthroughHeaders); var content = new StringContent("{\"dtm_result\":\"SUCCESS\"}"); diff --git a/src/Dtmcli.Tests/ServiceCollectionExtensionsTests.cs b/src/Dtmcli.Tests/ServiceCollectionExtensionsTests.cs index 63071af..fef09c8 100644 --- a/src/Dtmcli.Tests/ServiceCollectionExtensionsTests.cs +++ b/src/Dtmcli.Tests/ServiceCollectionExtensionsTests.cs @@ -18,6 +18,8 @@ public void AddDtmcli_With_Action_Should_Succeed() services.AddDtmcli(x => { x.DtmUrl = dtmUrl; + x.DtmTimeout = 8000; + x.BranchTimeout = 8000; }); var provider = services.BuildServiceProvider(); @@ -39,13 +41,15 @@ public void AddDtmcli_Without_Action_Should_Throw_Exception() } [Fact] - public void AddDtmcli_With_IConfiguration_Should_Succeed() + public async void AddDtmcli_With_IConfiguration_Should_Succeed() { var dtmUrl = "http://localhost:36789"; var dict = new Dictionary { { "dtm:DtmUrl", dtmUrl }, + { "dtm:DtmTimeout", "1000" }, + { "dtm:BranchTimeout", "8000" }, }; var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); @@ -61,6 +65,32 @@ public void AddDtmcli_With_IConfiguration_Should_Succeed() var dtmClient = provider.GetRequiredService(); Assert.NotNull(dtmClient); + + // for real test + await Assert.ThrowsAnyAsync(async () => await dtmClient.GenGid(default)); + await dtmClient.TransRequestBranch(new TransBase(), System.Net.Http.HttpMethod.Get, null, "", "", "https://www.baidu.com", default); + } + + [Fact] + public void AddDtmcli_With_IConfiguration_And_Empty_Option_Should_Succeed() + { + var dtmUrl = "http://localhost:36789"; + + var dict = new Dictionary + { + { "dtmx:DtmUrl", dtmUrl }, + }; + + var config = new ConfigurationBuilder().AddInMemoryCollection(dict).Build(); + + var services = new ServiceCollection(); + services.AddDtmcli(config, "dtm"); + + var provider = services.BuildServiceProvider(); + + var dtmOptionsAccs = provider.GetService>(); + var dtmOptions = dtmOptionsAccs.Value; + Assert.NotEqual(dtmUrl, dtmOptions.DtmUrl); } } } \ No newline at end of file diff --git a/src/Dtmcli.Tests/TccTests.cs b/src/Dtmcli.Tests/TccTests.cs index ce54a73..55efba7 100644 --- a/src/Dtmcli.Tests/TccTests.cs +++ b/src/Dtmcli.Tests/TccTests.cs @@ -13,6 +13,25 @@ public class TccTests { [Fact] public async void Execute_Should_Submit() + { + var dtmClient = new Mock(); + dtmClient.Setup(x => x.GenGid(It.IsAny())).Returns(Task.FromResult("tcc_gid_gid")); + TestHelper.MockTransCallDtm(dtmClient, Constant.Request.OPERATION_PREPARE, false); + TestHelper.MockTransRegisterBranch(dtmClient, Constant.Request.OPERATION_REGISTERBRANCH, false); + TestHelper.MockTransRequestBranch(dtmClient, System.Net.HttpStatusCode.OK); + + var globalTrans = new TccGlobalTransaction(dtmClient.Object, NullLoggerFactory.Instance); + var res = await globalTrans.Excecute(async (tcc) => + { + var res1 = await tcc.CallBranch(new { }, "http://localhost:9999/TransOutTry", "http://localhost:9999/TransOutConfirm", "http://localhost:9999/TransOutCancel", default); + var res2 = await tcc.CallBranch(new { }, "http://localhost:9999/TransInTry", "http://localhost:9999/TransInConfirm", "http://localhost:9999/TransInCancel", default); + }); + + Assert.Equal("tcc_gid_gid", res); + } + + [Fact] + public async void Execute_With_GId_Should_Submit() { var dtmClient = new Mock(); TestHelper.MockTransCallDtm(dtmClient, Constant.Request.OPERATION_PREPARE, false); @@ -92,6 +111,7 @@ public async void Set_TransOptions_Should_Succeed() { "bh1", "123" }, { "bh2", "456" }, }); + tcc.SetPassthroughHeaders(new List { "bh1" }); }, async (tcc) => { var res1 = await tcc.CallBranch(new { }, "http://localhost:9999/TransOutTry", "http://localhost:9999/TransOutConfirm", "http://localhost:9999/TransOutCancel", default); @@ -104,6 +124,7 @@ public async void Set_TransOptions_Should_Succeed() Assert.Equal(100, transBase.TimeoutToFail); Assert.Contains("bh1", transBase.BranchHeaders.Keys); Assert.Contains("bh2", transBase.BranchHeaders.Keys); + Assert.Contains("bh1", transBase.PassthroughHeaders); }); Assert.Equal(gid, res); diff --git a/src/Dtmcli.Tests/UtilsTests.cs b/src/Dtmcli.Tests/UtilsTests.cs new file mode 100644 index 0000000..836ef61 --- /dev/null +++ b/src/Dtmcli.Tests/UtilsTests.cs @@ -0,0 +1,66 @@ +using Xunit; +using System.Net; +using System.Net.Http; + +namespace Dtmcli.Tests +{ + public class UtilsTests + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public async void RespAsErrorCompatible_Should_Return_Null(bool isNull) + { + HttpResponseMessage resp = new HttpResponseMessage(); + resp.StatusCode = HttpStatusCode.OK; + resp.Content = isNull ? null : new StringContent("123"); + + var res = await DtmImp.Utils.RespAsErrorCompatible(resp); + Assert.Null(res); + } + + [Fact] + public async void RespAsErrorCompatible_Should_Throw_Unkown_Exception() + { + HttpResponseMessage resp = new HttpResponseMessage(); + resp.StatusCode = HttpStatusCode.BadGateway; + resp.Content = new StringContent("string"); + + var res = await DtmImp.Utils.RespAsErrorCompatible(resp); + Assert.Equal("string", res.Message); + } + + [Fact] + public async void RespAsErrorCompatible_Should_Throw_Ongoing_Exception() + { + HttpResponseMessage resp = new HttpResponseMessage(); + resp.Content = new StringContent("ONGOING"); + + var res = await DtmImp.Utils.RespAsErrorCompatible(resp); + Assert.IsType(res); + Assert.Equal("ONGOING", res.Message); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "")] + [InlineData(HttpStatusCode.OK, "FAILURE")] + public async void RespAsErrorCompatible_Should_Throw_Failure_Exception(HttpStatusCode code, string msg) + { + HttpResponseMessage resp = new HttpResponseMessage(); + resp.StatusCode = code; + resp.Content = new StringContent(msg); + + var res = await DtmImp.Utils.RespAsErrorCompatible(resp); + Assert.IsType(res); + Assert.Equal("FAILURE", res.Message); + } + + [Theory] + [InlineData(HttpStatusCode.Conflict, "123")] + [InlineData(HttpStatusCode.OK, "FAILURE")] + public void CheckStatus_Should_Throw_Failure_Exception(HttpStatusCode code, string msg) + { + Assert.Throws(() => DtmImp.Utils.CheckStatus(code, msg)); + } + } +} \ No newline at end of file diff --git a/src/Dtmcli/DtmImp/Utils.cs b/src/Dtmcli/DtmImp/Utils.cs index 892bc82..3c78b51 100644 --- a/src/Dtmcli/DtmImp/Utils.cs +++ b/src/Dtmcli/DtmImp/Utils.cs @@ -9,11 +9,11 @@ namespace Dtmcli.DtmImp public static class Utils { private const int StatusTooEarly = 425; - private const string CheckStatusMsgFormat = "http response status: {status}, Message :{dtmResult}"; + private const string CheckStatusMsgFormat = "http response status: {0}, Message :{1}"; public static async Task RespAsErrorCompatible(HttpResponseMessage resp) { - var str = await resp.Content?.ReadAsStringAsync() ?? string.Empty; + var str = resp.Content != null ? await resp.Content.ReadAsStringAsync() : string.Empty; // System.Net.HttpStatusCode do not contain StatusTooEarly if ((int)resp.StatusCode == StatusTooEarly || str.Contains(DtmCommon.Constant.ResultOngoing)) diff --git a/src/Dtmcli/Dtmcli.csproj b/src/Dtmcli/Dtmcli.csproj index 519b96b..2b5bf4a 100644 --- a/src/Dtmcli/Dtmcli.csproj +++ b/src/Dtmcli/Dtmcli.csproj @@ -8,7 +8,7 @@ Dtmcli a c# client for distributed transaction framework dtm. 分布式事务管理器dtm的c#客户端 dtm,csharp,distributed transaction,tcc,saga,msg - 1.1.1 + 1.1.2 geffzhang false From 60f8680d25b6ce9409bf15ba34fc7f9b30c9a874 Mon Sep 17 00:00:00 2001 From: catcherwong Date: Thu, 19 May 2022 22:44:45 +0800 Subject: [PATCH 2/2] docs: add badge of codecov --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6cc3a8..f968003 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ English | [简体中文](./README-cn.md) It has supported distributed transaction patterns of Saga pattern, TCC pattern and 2-phase message pattern. -![Build_And_Test](https://github.com/dtm-labs/dtmcli-csharp/actions/workflows/build.yml/badge.svg) +![Build_And_Test](https://github.com/dtm-labs/dtmcli-csharp/actions/workflows/build.yml/badge.svg) [![codecov](https://codecov.io/gh/dtm-labs/dtmcli-csharp/branch/main/graph/badge.svg?token=Y2BOSQ5QKO)](https://codecov.io/gh/dtm-labs/dtmcli-csharp) ![](https://img.shields.io/nuget/v/Dtmcli.svg) ![](https://img.shields.io/nuget/vpre/Dtmcli.svg) ![](https://img.shields.io/nuget/dt/Dtmcli) ![](https://img.shields.io/github/license/dtm-labs/dtmcli-csharp)