diff --git a/samples/DtmSample/Controllers/MsgTestController.cs b/samples/DtmSample/Controllers/MsgTestController.cs index c8a1874..5f01d4e 100644 --- a/samples/DtmSample/Controllers/MsgTestController.cs +++ b/samples/DtmSample/Controllers/MsgTestController.cs @@ -276,5 +276,29 @@ public async Task MsgWithTopic(CancellationToken cancellationToke return Ok(TransResponse.BuildSucceedResponse()); } + + /// + /// query + /// + /// + /// + [HttpGet("query")] + public async Task Query(string gid, CancellationToken cancellationToken) + { + TransGlobal trans = await _dtmClient.Query(gid, cancellationToken); + return Ok(trans); + } + + /// + /// query status + /// + /// + /// + [HttpGet("query-status")] + public async Task QueryStatus(string gid, CancellationToken cancellationToken) + { + string status = await _dtmClient.QueryStatus(gid, cancellationToken); + return Ok(status); + } } } diff --git a/src/Dtmcli/Constant.cs b/src/Dtmcli/Constant.cs index 1a738de..3bc3959 100644 --- a/src/Dtmcli/Constant.cs +++ b/src/Dtmcli/Constant.cs @@ -56,6 +56,11 @@ internal static class Request internal const string BRANCH_COMPENSATE = "compensate"; internal const string URL_NewGid = "/api/dtmsvr/newGid"; + + /// + /// query single + /// + internal const string URL_Query = "/api/dtmsvr/query"; } } } diff --git a/src/Dtmcli/DtmClient.cs b/src/Dtmcli/DtmClient.cs index 5537c9f..7a811e3 100644 --- a/src/Dtmcli/DtmClient.cs +++ b/src/Dtmcli/DtmClient.cs @@ -1,4 +1,5 @@ -using DtmCommon; +using System; +using DtmCommon; using Microsoft.Extensions.Options; using System.Collections.Generic; using System.Net.Http; @@ -46,7 +47,7 @@ public HttpClient GetHttpClient(string name) { return _httpClientFactory.CreateClient(name); } - + public async Task PrepareWorkflow(TransBase tb, CancellationToken cancellationToken) { var url = string.Concat(_dtmOptions.DtmUrl.TrimEnd(Slash), Constant.Request.URLBASE_PREFIX, "prepareWorkflow"); @@ -133,6 +134,46 @@ public TransBase TransBaseFromQuery(Microsoft.AspNetCore.Http.IQueryCollection q } #endif + /// + /// Query single global transaction + /// + /// global id + /// + /// + public async Task Query(string gid, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(gid)) throw new ArgumentNullException(nameof(gid)); + + var url = string.Concat(_dtmOptions.DtmUrl.TrimEnd(Slash), Constant.Request.URL_Query, $"?gid={gid}"); + var client = _httpClientFactory.CreateClient(Constant.DtmClientHttpName); + var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false); + var dtmContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + DtmImp.Utils.CheckStatus(response.StatusCode, dtmContent); + return JsonSerializer.Deserialize(dtmContent, _jsonOptions); + } + + /// + /// Query single global transaction status + /// + /// + /// + /// + /// + public async Task QueryStatus(string gid, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(gid)) throw new ArgumentNullException(nameof(gid)); + + var url = string.Concat(_dtmOptions.DtmUrl.TrimEnd(Slash), Constant.Request.URL_Query, $"?gid={gid}"); + var client = _httpClientFactory.CreateClient(Constant.DtmClientHttpName); + var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false); + var dtmContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + DtmImp.Utils.CheckStatus(response.StatusCode, dtmContent); + var graph = JsonSerializer.Deserialize(dtmContent, _jsonOptions); + return graph.Transaction == null + ? string.Empty + : graph.Transaction.Status; + } + public class DtmGid { [JsonPropertyName("gid")] diff --git a/src/Dtmcli/IDtmClient.cs b/src/Dtmcli/IDtmClient.cs index b47f485..9f02d1a 100644 --- a/src/Dtmcli/IDtmClient.cs +++ b/src/Dtmcli/IDtmClient.cs @@ -23,5 +23,9 @@ public interface IDtmClient #if NET5_0_OR_GREATER TransBase TransBaseFromQuery(Microsoft.AspNetCore.Http.IQueryCollection query); #endif + + Task Query(string gid, CancellationToken cancellationToken); + + Task QueryStatus(string gid, CancellationToken cancellationToken); } } diff --git a/src/Dtmcli/TransGlobal.cs b/src/Dtmcli/TransGlobal.cs new file mode 100644 index 0000000..03f3002 --- /dev/null +++ b/src/Dtmcli/TransGlobal.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Dtmcli; + +/// +/// query status only +/// +internal class TransGlobalForStatus +{ + [JsonPropertyName("transaction")] public DtmTransactionForStatus Transaction { get; set; } + + public class DtmTransactionForStatus + { + [JsonPropertyName("status")] public string Status { get; set; } + } +} + +// convert from json to c# code, json sample: saga succeed http (dtm-labs/dtm/qs/main.go) +public class TransGlobal +{ + [JsonPropertyName("branches")] public List Branches { get; set; } + + [JsonPropertyName("transaction")] public DtmTransaction Transaction { get; set; } + + public class DtmTransaction + { + [JsonPropertyName("id")] public int Id { get; set; } + + [JsonPropertyName("create_time")] public DateTimeOffset CreateTime { get; set; } + + [JsonPropertyName("update_time")] public DateTimeOffset UpdateTime { get; set; } + + [JsonPropertyName("gid")] public string Gid { get; set; } + + [JsonPropertyName("trans_type")] public string TransType { get; set; } + + [JsonPropertyName("steps")] public List Steps { get; set; } + + [JsonPropertyName("payloads")] public List Payloads { get; set; } + + [JsonPropertyName("status")] public string Status { get; set; } + + // [JsonPropertyName("query_prepared")] public string QueryPrepared { get; set; } + + [JsonPropertyName("protocol")] public string Protocol { get; set; } + + [JsonPropertyName("finish_time")] public DateTimeOffset FinishTime { get; set; } + + [JsonPropertyName("options")] public string Options { get; set; } + + [JsonPropertyName("next_cron_interval")] + public int NextCronInterval { get; set; } + + [JsonPropertyName("next_cron_time")] public DateTimeOffset NextCronTime { get; set; } + + // [JsonPropertyName("wait_result")] public bool WaitResult { get; set; } + + [JsonPropertyName("concurrent")] public bool Concurrent { get; set; } + } + + public class DtmBranch + { + [JsonPropertyName("id")] public int Id { get; set; } + + [JsonPropertyName("create_time")] public DateTimeOffset CreateTime { get; set; } + + [JsonPropertyName("update_time")] public DateTimeOffset UpdateTime { get; set; } + + [JsonPropertyName("gid")] public string Gid { get; set; } + + [JsonPropertyName("url")] public string Url { get; set; } + + [JsonPropertyName("bin_data")] public string BinData { get; set; } + + [JsonPropertyName("branch_id")] public string BranchId { get; set; } + + [JsonPropertyName("op")] public string Op { get; set; } + + [JsonPropertyName("status")] public string Status { get; set; } + } + + public class TransactionStep + { + [JsonPropertyName("action")] public string Action { get; set; } + [JsonPropertyName("Compensate")] public string Compensate { get; set; } + } +} \ No newline at end of file diff --git a/tests/Dtmcli.Tests/DtmClientTests.cs b/tests/Dtmcli.Tests/DtmClientTests.cs index 2ca5c66..4c55192 100644 --- a/tests/Dtmcli.Tests/DtmClientTests.cs +++ b/tests/Dtmcli.Tests/DtmClientTests.cs @@ -1,4 +1,5 @@ -using Xunit; +using System; +using Xunit; using System.Net; using Moq; using System.Net.Http; @@ -136,6 +137,275 @@ public void TransBaseFromQuery_Should_Succeed() Assert.Equal(dict["branch_id"], tb.BranchIDGen.BranchID); } #endif + + [Fact] + public async Task Query_Should_Succeed() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + /* + json sample: saga succeed http (dtm-labs/dtm/qs/main.go) + { + "branches": [ + { + "id": 0, + "create_time": "2024-12-16T13:14:14.741109826+08:00", + "update_time": "2024-12-16T13:14:14.741109826+08:00", + "gid": "HZDVvoKbeCvABvXtVgPoyG", + "url": "http://localhost:8082/api/busi_start/TransOutCompensate", + "bin_data": "eyJhbW91bnQiOjMwfQ==", + "branch_id": "01", + "op": "compensate", + "status": "prepared" + }, + { + "id": 0, + "create_time": "2024-12-16T13:14:14.741109826+08:00", + "update_time": "2024-12-16T13:14:14.746022823+08:00", + "gid": "HZDVvoKbeCvABvXtVgPoyG", + "url": "http://localhost:8082/api/busi_start/TransOut", + "bin_data": "eyJhbW91bnQiOjMwfQ==", + "branch_id": "01", + "op": "action", + "status": "succeed", + "finish_time": "2024-12-16T13:14:14.746022823+08:00" + }, + { + "id": 0, + "create_time": "2024-12-16T13:14:14.741109826+08:00", + "update_time": "2024-12-16T13:14:14.741109826+08:00", + "gid": "HZDVvoKbeCvABvXtVgPoyG", + "url": "http://localhost:8082/api/busi_start/TransInCompensate", + "bin_data": "eyJhbW91bnQiOjMwfQ==", + "branch_id": "02", + "op": "compensate", + "status": "prepared" + }, + { + "id": 0, + "create_time": "2024-12-16T13:14:14.741109826+08:00", + "update_time": "2024-12-16T13:14:14.748793116+08:00", + "gid": "HZDVvoKbeCvABvXtVgPoyG", + "url": "http://localhost:8082/api/busi_start/TransIn", + "bin_data": "eyJhbW91bnQiOjMwfQ==", + "branch_id": "02", + "op": "action", + "status": "succeed", + "finish_time": "2024-12-16T13:14:14.748793116+08:00" + } + ], + "transaction": { + "id": 7, + "create_time": "2024-12-16T13:14:14.741109826+08:00", + "update_time": "2024-12-16T13:14:14.750753431+08:00", + "gid": "HZDVvoKbeCvABvXtVgPoyG", + "trans_type": "saga", + "steps": [ + { + "action": "http://localhost:8082/api/busi_start/TransOut", + "compensate": "http://localhost:8082/api/busi_start/TransOutCompensate" + }, + { + "action": "http://localhost:8082/api/busi_start/TransIn", + "compensate": "http://localhost:8082/api/busi_start/TransInCompensate" + } + ], + "payloads": [ + "{\"amount\":30}", + "{\"amount\":30}" + ], + "status": "succeed", + "protocol": "http", + "finish_time": "2024-12-16T13:14:14.750753431+08:00", + "options": "{\"concurrent\":false}", + "next_cron_interval": 10, + "next_cron_time": "2024-12-16T13:14:24.741068145+08:00", + "concurrent": false + } + } + */ + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, + "{\"branches\":[{\"id\":0,\"create_time\":\"2024-12-16T13:14:14.741109826+08:00\",\"update_time\":\"2024-12-16T13:14:14.741109826+08:00\",\"gid\":\"HZDVvoKbeCvABvXtVgPoyG\",\"url\":\"http://localhost:8082/api/busi_start/TransOutCompensate\",\"bin_data\":\"eyJhbW91bnQiOjMwfQ==\",\"branch_id\":\"01\",\"op\":\"compensate\",\"status\":\"prepared\"},{\"id\":0,\"create_time\":\"2024-12-16T13:14:14.741109826+08:00\",\"update_time\":\"2024-12-16T13:14:14.746022823+08:00\",\"gid\":\"HZDVvoKbeCvABvXtVgPoyG\",\"url\":\"http://localhost:8082/api/busi_start/TransOut\",\"bin_data\":\"eyJhbW91bnQiOjMwfQ==\",\"branch_id\":\"01\",\"op\":\"action\",\"status\":\"succeed\",\"finish_time\":\"2024-12-16T13:14:14.746022823+08:00\"},{\"id\":0,\"create_time\":\"2024-12-16T13:14:14.741109826+08:00\",\"update_time\":\"2024-12-16T13:14:14.741109826+08:00\",\"gid\":\"HZDVvoKbeCvABvXtVgPoyG\",\"url\":\"http://localhost:8082/api/busi_start/TransInCompensate\",\"bin_data\":\"eyJhbW91bnQiOjMwfQ==\",\"branch_id\":\"02\",\"op\":\"compensate\",\"status\":\"prepared\"},{\"id\":0,\"create_time\":\"2024-12-16T13:14:14.741109826+08:00\",\"update_time\":\"2024-12-16T13:14:14.748793116+08:00\",\"gid\":\"HZDVvoKbeCvABvXtVgPoyG\",\"url\":\"http://localhost:8082/api/busi_start/TransIn\",\"bin_data\":\"eyJhbW91bnQiOjMwfQ==\",\"branch_id\":\"02\",\"op\":\"action\",\"status\":\"succeed\",\"finish_time\":\"2024-12-16T13:14:14.748793116+08:00\"}],\"transaction\":{\"id\":7,\"create_time\":\"2024-12-16T13:14:14.741109826+08:00\",\"update_time\":\"2024-12-16T13:14:14.750753431+08:00\",\"gid\":\"HZDVvoKbeCvABvXtVgPoyG\",\"trans_type\":\"saga\",\"steps\":[{\"action\":\"http://localhost:8082/api/busi_start/TransOut\",\"compensate\":\"http://localhost:8082/api/busi_start/TransOutCompensate\"},{\"action\":\"http://localhost:8082/api/busi_start/TransIn\",\"compensate\":\"http://localhost:8082/api/busi_start/TransInCompensate\"}],\"payloads\":[\"{\\\"amount\\\":30}\",\"{\\\"amount\\\":30}\"],\"status\":\"succeed\",\"protocol\":\"http\",\"finish_time\":\"2024-12-16T13:14:14.750753431+08:00\",\"options\":\"{\\\"concurrent\\\":false}\",\"next_cron_interval\":10,\"next_cron_time\":\"2024-12-16T13:14:24.741068145+08:00\",\"concurrent\":false}}"); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + var client = new DtmClient(factory.Object, options); + TransGlobal globalTrans = await client.Query(gid: "HZDVvoKbeCvABvXtVgPoyG", new CancellationToken()); + + // 生成globalTrans.Transaction对象的所有属性断言 + Assert.Equal(7, globalTrans.Transaction.Id); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.741109826+08:00"), globalTrans.Transaction.CreateTime); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.750753431+08:00"), globalTrans.Transaction.UpdateTime); + Assert.Equal("HZDVvoKbeCvABvXtVgPoyG", globalTrans.Transaction.Gid); + Assert.Equal("saga", globalTrans.Transaction.TransType); + Assert.Equal(2, globalTrans.Transaction.Steps.Count); + Assert.Equal("http://localhost:8082/api/busi_start/TransOut", globalTrans.Transaction.Steps[0].Action); + Assert.Equal("http://localhost:8082/api/busi_start/TransOutCompensate", globalTrans.Transaction.Steps[0].Compensate); + Assert.Equal("http://localhost:8082/api/busi_start/TransIn", globalTrans.Transaction.Steps[1].Action); + Assert.Equal("http://localhost:8082/api/busi_start/TransInCompensate", globalTrans.Transaction.Steps[1].Compensate); + Assert.Equal(2, globalTrans.Transaction.Payloads.Count); + Assert.Equal("{\"amount\":30}", globalTrans.Transaction.Payloads[0]); + Assert.Equal("{\"amount\":30}", globalTrans.Transaction.Payloads[1]); + Assert.Equal("succeed", globalTrans.Transaction.Status); + Assert.Equal("http", globalTrans.Transaction.Protocol); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.750753431+08:00"), globalTrans.Transaction.FinishTime); + Assert.Equal("{\"concurrent\":false}", globalTrans.Transaction.Options); + Assert.Equal(10, globalTrans.Transaction.NextCronInterval); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:24.741068145+08:00"), globalTrans.Transaction.NextCronTime); + Assert.False(globalTrans.Transaction.Concurrent); + + Assert.Equal(4, globalTrans.Branches.Count); + // 1 + Assert.Equal(0, globalTrans.Branches[0].Id); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.741109826+08:00"), globalTrans.Branches[0].CreateTime); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.741109826+08:00"), globalTrans.Branches[0].UpdateTime); + Assert.Equal("HZDVvoKbeCvABvXtVgPoyG", globalTrans.Branches[0].Gid); + Assert.Equal("http://localhost:8082/api/busi_start/TransOutCompensate", globalTrans.Branches[0].Url); + Assert.Equal("eyJhbW91bnQiOjMwfQ==", globalTrans.Branches[0].BinData); + Assert.Equal("01", globalTrans.Branches[0].BranchId); + Assert.Equal("compensate", globalTrans.Branches[0].Op); + Assert.Equal("prepared", globalTrans.Branches[0].Status); + // 2 + Assert.Equal(0, globalTrans.Branches[0].Id); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.741109826+08:00"), globalTrans.Branches[1].CreateTime); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.746022823+08:00"), globalTrans.Branches[1].UpdateTime); + Assert.Equal("HZDVvoKbeCvABvXtVgPoyG", globalTrans.Branches[1].Gid); + Assert.Equal("http://localhost:8082/api/busi_start/TransOut", globalTrans.Branches[1].Url); + Assert.Equal("eyJhbW91bnQiOjMwfQ==", globalTrans.Branches[1].BinData); + Assert.Equal("01", globalTrans.Branches[1].BranchId); + Assert.Equal("action", globalTrans.Branches[1].Op); + Assert.Equal("succeed", globalTrans.Branches[1].Status); + // 3 + Assert.Equal(0, globalTrans.Branches[2].Id); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.741109826+08:00"), globalTrans.Branches[2].CreateTime); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.741109826+08:00"), globalTrans.Branches[2].UpdateTime); + Assert.Equal("HZDVvoKbeCvABvXtVgPoyG", globalTrans.Branches[2].Gid); + Assert.Equal("http://localhost:8082/api/busi_start/TransInCompensate", globalTrans.Branches[2].Url); + Assert.Equal("eyJhbW91bnQiOjMwfQ==", globalTrans.Branches[2].BinData); + Assert.Equal("02", globalTrans.Branches[2].BranchId); + Assert.Equal("compensate", globalTrans.Branches[2].Op); + Assert.Equal("prepared", globalTrans.Branches[2].Status); + // 4 + Assert.Equal(0, globalTrans.Branches[3].Id); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.741109826+08:00"), globalTrans.Branches[3].CreateTime); + Assert.Equal(DateTimeOffset.Parse("2024-12-16T13:14:14.748793116+08:00"), globalTrans.Branches[3].UpdateTime); + Assert.Equal("HZDVvoKbeCvABvXtVgPoyG", globalTrans.Branches[3].Gid); + Assert.Equal("http://localhost:8082/api/busi_start/TransIn", globalTrans.Branches[3].Url); + Assert.Equal("eyJhbW91bnQiOjMwfQ==", globalTrans.Branches[3].BinData); + Assert.Equal("02", globalTrans.Branches[3].BranchId); + Assert.Equal("action", globalTrans.Branches[3].Op); + Assert.Equal("succeed", globalTrans.Branches[3].Status); + } + + [Fact] + public async Task Query_Gid_NullOrEmpty() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient()); + var client = new DtmClient(factory.Object, options); + await Assert.ThrowsAsync(async () => await client.Query(gid: null, new CancellationToken())); + await Assert.ThrowsAsync(async () => await client.Query(gid: string.Empty, new CancellationToken())); + } + [Fact] + public async Task Query_Not_Exist_Gid() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + /* + { + "branches": [], + "transaction": null + } + */ + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, "{\n \"branches\": [],\n \"transaction\": null\n}\n"); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + var client = new DtmClient(factory.Object, options); + TransGlobal globalTrans = await client.Query(gid: "my-gid", new CancellationToken()); + Assert.NotNull(globalTrans); + Assert.Null(globalTrans.Transaction); + } + + [Fact] + public async Task Query_Should_Throw_Exception() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.InternalServerError, ""); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + var client = new DtmClient(factory.Object, options); + await Assert.ThrowsAsync(async () => + { + await client.Query(gid: "my-gid", new CancellationToken()); + }); + } + + + + [Fact] + public async Task QueryStatus_Should_Succeed() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + /* + { + "branches": [], + "transaction": { + "id": 7, + "gid": "mV9RGqZCV2mdn9YA6T2TPC", + "trans_type": "msg", + "status": "prepared", + "protocol": "http" + } + } + */ + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, "{\n \"branches\": [],\n \"transaction\": {\n \"id\": 7,\n \"gid\": \"mV9RGqZCV2mdn9YA6T2TPC\",\n \"trans_type\": \"msg\",\n \"status\": \"prepared\",\n \"protocol\": \"http\"\n }\n} "); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + var client = new DtmClient(factory.Object, options); + string status = await client.QueryStatus(gid: "my-gid", new CancellationToken()); + Assert.Equal("prepared", status); + } + + [Fact] + public async Task QueryStatus_Gid_NullOrEmpty() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient()); + var client = new DtmClient(factory.Object, options); + await Assert.ThrowsAsync(async () => await client.QueryStatus(gid: null, new CancellationToken())); + await Assert.ThrowsAsync(async () => await client.QueryStatus(gid: string.Empty, new CancellationToken())); + } + + [Fact] + public async Task QueryStatus_Not_Exist_Gid() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + /* + { + "branches": [], + "transaction": null + } + */ + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.OK, "{\n \"branches\": [],\n \"transaction\": null\n}\n"); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + var client = new DtmClient(factory.Object, options); + string status = await client.QueryStatus(gid: "my-gid", new CancellationToken()); + Assert.Empty(status); + } + + [Fact] + public async Task QueryStatus_Should_Throw_Exception() + { + var factory = new Mock(); + var options = Microsoft.Extensions.Options.Options.Create(new DtmCommon.DtmOptions { DtmUrl = "http://localhost:8080" }); + var mockHttpMessageHandler = new ClientMockHttpMessageHandler(HttpStatusCode.InternalServerError, ""); + factory.Setup(x => x.CreateClient(It.IsAny())).Returns(new HttpClient(mockHttpMessageHandler)); + var client = new DtmClient(factory.Object, options); + await Assert.ThrowsAsync(async () => + { + await client.QueryStatus(gid: "my-gid", new CancellationToken()); + }); + } } internal class ClientMockHttpMessageHandler : DelegatingHandler