From 99260304ed64948102691c8e02bdec9373a60e1b Mon Sep 17 00:00:00 2001 From: wooln Date: Wed, 18 Dec 2024 09:04:30 +0800 Subject: [PATCH] bugfix(DtmSample): fix MsgMsSqlQueryPrepared return http status 200 with unrecognized error body. (#86) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bugfix(DtmSample): fix MsgMsSqlQueryPrepared return http status 200 with unrecognized error body. - http status code 200 with unrecognized body will be as normal! - Add Result2HttpJson method for consistent error handling and HTTP response formatting - Update MsgMssqlQueryPrepared action to use the new method * test(DtmSample): add test case for MSG MSSQL DB connection error, expected status should be prepared * refactor(QueryPrepared): Add OrString、String2DtmError、Result2HttpJson methods to Utils.cs - Add OrString、String2DtmError、Result2HttpJson methods to Utils.cs - Refactor sample controller Msg QueryPrepared, by invoking new methods in Utils.cs --- samples/DtmSample/AppSettings.cs | 5 ++ .../Controllers/MsgTestController.cs | 84 +++++++++++++++---- samples/DtmSample/appsettings.json | 1 + src/Dtmcli/DtmImp/Utils.cs | 56 +++++++++++++ tests/Dtmcli.Tests/UtilsTests.cs | 45 +++++++++- 5 files changed, 172 insertions(+), 19 deletions(-) diff --git a/samples/DtmSample/AppSettings.cs b/samples/DtmSample/AppSettings.cs index 688e7e1..43bccbf 100644 --- a/samples/DtmSample/AppSettings.cs +++ b/samples/DtmSample/AppSettings.cs @@ -2,10 +2,15 @@ { public class AppSettings { + public string DtmUrl { get; set; } + public string BusiUrl { get; set; } public string SqlBarrierConn { get; set; } + + public string SqlBarrierErrorConn { get; set; } + public string MongoBarrierConn { get; set; } } } diff --git a/samples/DtmSample/Controllers/MsgTestController.cs b/samples/DtmSample/Controllers/MsgTestController.cs index 5f01d4e..613ae58 100644 --- a/samples/DtmSample/Controllers/MsgTestController.cs +++ b/samples/DtmSample/Controllers/MsgTestController.cs @@ -1,4 +1,5 @@ -using Dtmcli; +using System; +using Dtmcli; using DtmMongoBarrier; using DtmSample.Dtos; using Microsoft.AspNetCore.Mvc; @@ -36,6 +37,9 @@ public MsgTestController(ILogger logger, IOptions new(_settings.SqlBarrierConn); private SqlConnection GetMssqlConn() => new(_settings.SqlBarrierConn); + + private SqlConnection GetBadMssqlConn() => new(_settings.SqlBarrierErrorConn); + private MySqlConnection GetErrConn() => new(""); @@ -115,6 +119,40 @@ await msg.DoAndSubmitDB(_settings.BusiUrl + "/msg-mssqlqueryprepared", conn, asy return Ok(TransResponse.BuildSucceedResponse()); } + + /// + /// MSG DoAndSubmitDB (mssql). db connection error, DTM server Status should be prepared. + /// + /// + /// + [HttpPost("msg-db-mssql-db-connection-error")] + public async Task MsgDbMsSql_DbConnectionError(CancellationToken cancellationToken) + { + var gid = await _dtmClient.GenGid(cancellationToken); + + var msg = _transFactory.NewMsg(gid) + .Add(_settings.BusiUrl + "/TransOut", new TransRequest("1", -30)) + .Add(_settings.BusiUrl + "/TransIn", new TransRequest("2", 30)); + + try + { + using (SqlConnection conn = GetBadMssqlConn()) + { + await msg.DoAndSubmitDB(_settings.BusiUrl + "/msg-mssqlqueryprepared", conn, async tx => { await Task.CompletedTask; }); + } + } + catch (SqlException) + { + Thread.Sleep(5 * 1000); + _logger.LogInformation("{}/admin/global-transactions/detail/{}, status should be prepared", _settings.DtmUrl, gid); + throw; + } + + _logger.LogInformation("result gid is {0}", gid); + return Ok(TransResponse.BuildSucceedResponse()); + } + + /// /// MSG DoAndSubmit (mongo) /// @@ -184,7 +222,22 @@ public async Task MsgMySqlQueryPrepared(CancellationToken cancell } } + /// + /// MSG QueryPrepared(mongo) + /// + /// + /// + [HttpGet("msg-mongoqueryprepared")] + public async Task MsgMongoQueryPrepared(CancellationToken cancellationToken) + { + var bb = _factory.CreateBranchBarrier(Request.Query); + _logger.LogInformation("bb {0}", bb); + MongoDB.Driver.IMongoClient cli = new MongoDB.Driver.MongoClient(_settings.MongoBarrierConn); + var res = await bb.MongoQueryPrepared(cli); + return Ok(new { dtm_result = res }); + } + /// /// MSG QueryPrepared(mssql) /// @@ -211,28 +264,23 @@ public async Task MsgMsSqlQueryPrepared(CancellationToken cancell { var bb = _factory.CreateBranchBarrier(Request.Query); _logger.LogInformation("bb {0}", bb); - using (SqlConnection conn = GetMssqlConn()) - { - var res = await bb.QueryPrepared(conn); - return Ok(new { dtm_result = res }); + string ret; + await using (SqlConnection conn = GetMssqlConn()) + { + ret = await bb.QueryPrepared(conn); } + + ret = Dtmcli.DtmImp.Utils.OrString(ret, DtmCommon.Constant.ResultSuccess); + Exception error = Dtmcli.DtmImp.Utils.String2DtmError(ret); + + return WrapHandler(error); } - /// - /// MSG QueryPrepared(mongo) - /// - /// - /// - [HttpGet("msg-mongoqueryprepared")] - public async Task MsgMongoQueryPrepared(CancellationToken cancellationToken) + private IActionResult WrapHandler(Exception error) { - var bb = _factory.CreateBranchBarrier(Request.Query); - _logger.LogInformation("bb {0}", bb); - - MongoDB.Driver.IMongoClient cli = new MongoDB.Driver.MongoClient(_settings.MongoBarrierConn); - var res = await bb.MongoQueryPrepared(cli); - return Ok(new { dtm_result = res }); + (int status, object res) = Dtmcli.DtmImp.Utils.Result2HttpJson(error); + return StatusCode(status, res); } /// diff --git a/samples/DtmSample/appsettings.json b/samples/DtmSample/appsettings.json index 5916f0c..50bd3dd 100644 --- a/samples/DtmSample/appsettings.json +++ b/samples/DtmSample/appsettings.json @@ -24,6 +24,7 @@ "SqlDbType": "sqlserver", "BarrierSqlTableName": "dbo.barrier", "SqlBarrierConn": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=dtm_barrier;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", + "SqlBarrierErrorConn": "Data Source=.;Initial Catalog=dtm_barrier;User ID=sa;Password=my_error_password;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False", "MongoBarrierConn": "mongodb://localhost:27017" } } \ No newline at end of file diff --git a/src/Dtmcli/DtmImp/Utils.cs b/src/Dtmcli/DtmImp/Utils.cs index e231ea4..0b374e9 100644 --- a/src/Dtmcli/DtmImp/Utils.cs +++ b/src/Dtmcli/DtmImp/Utils.cs @@ -39,5 +39,61 @@ public static void CheckStatus(HttpStatusCode status, string dtmResult) throw new DtmException(string.Format(CheckStatusMsgFormat, status.ToString(), dtmResult)); } } + + /// + /// OrString return the first not null or not empty string + /// + /// + /// + public static string OrString(params string[] ss) + { + foreach (var s in ss) + { + if (!string.IsNullOrEmpty(s)) + return s; + } + + return ""; + } + + /// + /// translate string to dtm error + /// + /// + /// + public static Exception String2DtmError(string str) + { + if (str == DtmCommon.Constant.ResultSuccess || str == string.Empty) + return null; + if (str == string.Empty) + return null; + if (str == DtmCommon.Constant.ResultFailure) + return new DtmCommon.DtmFailureException(); + if (str == DtmCommon.Constant.ResultOngoing) + return new DtmCommon.DtmOngingException(); + return new Exception(str); + } + + /// + /// translate object to http response + /// 409 => ErrFailure; Code 425 => ErrOngoing; Code 500 => InternalServerError + /// + /// + /// + public static (int httpStatusCode, object res) Result2HttpJson(object result) + { + if (result is not Exception err) + { + return ((int)(HttpStatusCode.OK), result); + } + + var res = new { error = err.Message }; + if (err is DtmFailureException) + return ((int)HttpStatusCode.Conflict, res); + if (err is DtmOngingException) + return (425 /*HttpStatusCode.TooEarly*/, res); + + return ((int)HttpStatusCode.InternalServerError, res); + } } } diff --git a/tests/Dtmcli.Tests/UtilsTests.cs b/tests/Dtmcli.Tests/UtilsTests.cs index 546d450..282241f 100644 --- a/tests/Dtmcli.Tests/UtilsTests.cs +++ b/tests/Dtmcli.Tests/UtilsTests.cs @@ -1,6 +1,10 @@ -using Xunit; +using System; +using Xunit; using System.Net; using System.Net.Http; +using System.Text.Json.Serialization; +using DtmCommon; +using Newtonsoft.Json; namespace Dtmcli.Tests { @@ -62,5 +66,44 @@ public void CheckStatus_Should_Throw_Failure_Exception(HttpStatusCode code, stri { Assert.Throws(() => DtmImp.Utils.CheckStatus(code, msg)); } + + [Fact] + public void OrString() + { + Assert.Equal("", DtmImp.Utils.OrString()); + Assert.Equal("A", DtmImp.Utils.OrString("", "A")); + Assert.Equal("A", DtmImp.Utils.OrString("", "A", "B")); + Assert.Equal("A", DtmImp.Utils.OrString("A", "B")); + } + + [Fact] + public void String2DtmError() + { + Assert.IsType(DtmImp.Utils.String2DtmError(null)); + Assert.Null(DtmImp.Utils.String2DtmError(string.Empty)); + Assert.Null(DtmImp.Utils.String2DtmError("SUCCESS")); + Assert.IsType(DtmImp.Utils.String2DtmError("FAILURE")); + Assert.IsType(DtmImp.Utils.String2DtmError("ONGOING")); + Assert.IsType(DtmImp.Utils.String2DtmError("Object ...")); + } + + [Fact] + public void Result2HttpJson() + { + Assert.Equal(200, DtmImp.Utils.Result2HttpJson(null).httpStatusCode); + Assert.Equal(409, DtmImp.Utils.Result2HttpJson(new DtmFailureException()).httpStatusCode); + Assert.Equal(425, DtmImp.Utils.Result2HttpJson(new DtmOngingException()).httpStatusCode); + Assert.Equal(500, DtmImp.Utils.Result2HttpJson(new DtmDuplicatedException()).httpStatusCode); + + Assert.Equal(500, DtmImp.Utils.Result2HttpJson(new Exception("message context A")).httpStatusCode); + Assert.Contains("message context A", JsonConvert.SerializeObject(DtmImp.Utils.Result2HttpJson(new Exception("message context A")).res)); + + Assert.Equal(200, DtmImp.Utils.Result2HttpJson("normal text").httpStatusCode); + Assert.Equal("normal text", DtmImp.Utils.Result2HttpJson("normal text").res); + + var obj = new { A = "hello", B = "world" }; + Assert.Equal(200, DtmImp.Utils.Result2HttpJson(obj).httpStatusCode); + Assert.Equal(obj, DtmImp.Utils.Result2HttpJson(obj).res); + } } } \ No newline at end of file