Skip to content

Commit 9926030

Browse files
authored
bugfix(DtmSample): fix MsgMsSqlQueryPrepared return http status 200 with unrecognized error body. (#86)
* 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
1 parent 40711b8 commit 9926030

File tree

5 files changed

+172
-19
lines changed

5 files changed

+172
-19
lines changed

samples/DtmSample/AppSettings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
{
33
public class AppSettings
44
{
5+
public string DtmUrl { get; set; }
6+
57
public string BusiUrl { get; set; }
68

79
public string SqlBarrierConn { get; set; }
810

11+
12+
public string SqlBarrierErrorConn { get; set; }
13+
914
public string MongoBarrierConn { get; set; }
1015
}
1116
}

samples/DtmSample/Controllers/MsgTestController.cs

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Dtmcli;
1+
using System;
2+
using Dtmcli;
23
using DtmMongoBarrier;
34
using DtmSample.Dtos;
45
using Microsoft.AspNetCore.Mvc;
@@ -36,6 +37,9 @@ public MsgTestController(ILogger<MsgTestController> logger, IOptions<AppSettings
3637
private MySqlConnection GetMysqlConn() => new(_settings.SqlBarrierConn);
3738

3839
private SqlConnection GetMssqlConn() => new(_settings.SqlBarrierConn);
40+
41+
private SqlConnection GetBadMssqlConn() => new(_settings.SqlBarrierErrorConn);
42+
3943

4044
private MySqlConnection GetErrConn() => new("");
4145

@@ -115,6 +119,40 @@ await msg.DoAndSubmitDB(_settings.BusiUrl + "/msg-mssqlqueryprepared", conn, asy
115119
return Ok(TransResponse.BuildSucceedResponse());
116120
}
117121

122+
123+
/// <summary>
124+
/// MSG DoAndSubmitDB (mssql). db connection error, DTM server Status should be prepared.
125+
/// </summary>
126+
/// <param name="cancellationToken"></param>
127+
/// <returns></returns>
128+
[HttpPost("msg-db-mssql-db-connection-error")]
129+
public async Task<IActionResult> MsgDbMsSql_DbConnectionError(CancellationToken cancellationToken)
130+
{
131+
var gid = await _dtmClient.GenGid(cancellationToken);
132+
133+
var msg = _transFactory.NewMsg(gid)
134+
.Add(_settings.BusiUrl + "/TransOut", new TransRequest("1", -30))
135+
.Add(_settings.BusiUrl + "/TransIn", new TransRequest("2", 30));
136+
137+
try
138+
{
139+
using (SqlConnection conn = GetBadMssqlConn())
140+
{
141+
await msg.DoAndSubmitDB(_settings.BusiUrl + "/msg-mssqlqueryprepared", conn, async tx => { await Task.CompletedTask; });
142+
}
143+
}
144+
catch (SqlException)
145+
{
146+
Thread.Sleep(5 * 1000);
147+
_logger.LogInformation("{}/admin/global-transactions/detail/{}, status should be prepared", _settings.DtmUrl, gid);
148+
throw;
149+
}
150+
151+
_logger.LogInformation("result gid is {0}", gid);
152+
return Ok(TransResponse.BuildSucceedResponse());
153+
}
154+
155+
118156
/// <summary>
119157
/// MSG DoAndSubmit (mongo)
120158
/// </summary>
@@ -184,7 +222,22 @@ public async Task<IActionResult> MsgMySqlQueryPrepared(CancellationToken cancell
184222
}
185223
}
186224

225+
/// <summary>
226+
/// MSG QueryPrepared(mongo)
227+
/// </summary>
228+
/// <param name="cancellationToken"></param>
229+
/// <returns></returns>
230+
[HttpGet("msg-mongoqueryprepared")]
231+
public async Task<IActionResult> MsgMongoQueryPrepared(CancellationToken cancellationToken)
232+
{
233+
var bb = _factory.CreateBranchBarrier(Request.Query);
234+
_logger.LogInformation("bb {0}", bb);
187235

236+
MongoDB.Driver.IMongoClient cli = new MongoDB.Driver.MongoClient(_settings.MongoBarrierConn);
237+
var res = await bb.MongoQueryPrepared(cli);
238+
return Ok(new { dtm_result = res });
239+
}
240+
188241
/// <summary>
189242
/// MSG QueryPrepared(mssql)
190243
///
@@ -211,28 +264,23 @@ public async Task<IActionResult> MsgMsSqlQueryPrepared(CancellationToken cancell
211264
{
212265
var bb = _factory.CreateBranchBarrier(Request.Query);
213266
_logger.LogInformation("bb {0}", bb);
214-
using (SqlConnection conn = GetMssqlConn())
215-
{
216-
var res = await bb.QueryPrepared(conn);
217267

218-
return Ok(new { dtm_result = res });
268+
string ret;
269+
await using (SqlConnection conn = GetMssqlConn())
270+
{
271+
ret = await bb.QueryPrepared(conn);
219272
}
273+
274+
ret = Dtmcli.DtmImp.Utils.OrString(ret, DtmCommon.Constant.ResultSuccess);
275+
Exception error = Dtmcli.DtmImp.Utils.String2DtmError(ret);
276+
277+
return WrapHandler(error);
220278
}
221279

222-
/// <summary>
223-
/// MSG QueryPrepared(mongo)
224-
/// </summary>
225-
/// <param name="cancellationToken"></param>
226-
/// <returns></returns>
227-
[HttpGet("msg-mongoqueryprepared")]
228-
public async Task<IActionResult> MsgMongoQueryPrepared(CancellationToken cancellationToken)
280+
private IActionResult WrapHandler(Exception error)
229281
{
230-
var bb = _factory.CreateBranchBarrier(Request.Query);
231-
_logger.LogInformation("bb {0}", bb);
232-
233-
MongoDB.Driver.IMongoClient cli = new MongoDB.Driver.MongoClient(_settings.MongoBarrierConn);
234-
var res = await bb.MongoQueryPrepared(cli);
235-
return Ok(new { dtm_result = res });
282+
(int status, object res) = Dtmcli.DtmImp.Utils.Result2HttpJson(error);
283+
return StatusCode(status, res);
236284
}
237285

238286
/// <summary>

samples/DtmSample/appsettings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"SqlDbType": "sqlserver",
2525
"BarrierSqlTableName": "dbo.barrier",
2626
"SqlBarrierConn": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=dtm_barrier;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
27+
"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",
2728
"MongoBarrierConn": "mongodb://localhost:27017"
2829
}
2930
}

src/Dtmcli/DtmImp/Utils.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,61 @@ public static void CheckStatus(HttpStatusCode status, string dtmResult)
3939
throw new DtmException(string.Format(CheckStatusMsgFormat, status.ToString(), dtmResult));
4040
}
4141
}
42+
43+
/// <summary>
44+
/// OrString return the first not null or not empty string
45+
/// </summary>
46+
/// <param name="ss"></param>
47+
/// <returns></returns>
48+
public static string OrString(params string[] ss)
49+
{
50+
foreach (var s in ss)
51+
{
52+
if (!string.IsNullOrEmpty(s))
53+
return s;
54+
}
55+
56+
return "";
57+
}
58+
59+
/// <summary>
60+
/// translate string to dtm error
61+
/// </summary>
62+
/// <param name="str"></param>
63+
/// <returns></returns>
64+
public static Exception String2DtmError(string str)
65+
{
66+
if (str == DtmCommon.Constant.ResultSuccess || str == string.Empty)
67+
return null;
68+
if (str == string.Empty)
69+
return null;
70+
if (str == DtmCommon.Constant.ResultFailure)
71+
return new DtmCommon.DtmFailureException();
72+
if (str == DtmCommon.Constant.ResultOngoing)
73+
return new DtmCommon.DtmOngingException();
74+
return new Exception(str);
75+
}
76+
77+
/// <summary>
78+
/// translate object to http response
79+
/// 409 => ErrFailure; Code 425 => ErrOngoing; Code 500 => InternalServerError
80+
/// </summary>
81+
/// <param name="result"></param>
82+
/// <returns></returns>
83+
public static (int httpStatusCode, object res) Result2HttpJson(object result)
84+
{
85+
if (result is not Exception err)
86+
{
87+
return ((int)(HttpStatusCode.OK), result);
88+
}
89+
90+
var res = new { error = err.Message };
91+
if (err is DtmFailureException)
92+
return ((int)HttpStatusCode.Conflict, res);
93+
if (err is DtmOngingException)
94+
return (425 /*HttpStatusCode.TooEarly*/, res);
95+
96+
return ((int)HttpStatusCode.InternalServerError, res);
97+
}
4298
}
4399
}

tests/Dtmcli.Tests/UtilsTests.cs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
using Xunit;
1+
using System;
2+
using Xunit;
23
using System.Net;
34
using System.Net.Http;
5+
using System.Text.Json.Serialization;
6+
using DtmCommon;
7+
using Newtonsoft.Json;
48

59
namespace Dtmcli.Tests
610
{
@@ -62,5 +66,44 @@ public void CheckStatus_Should_Throw_Failure_Exception(HttpStatusCode code, stri
6266
{
6367
Assert.Throws<DtmCommon.DtmException>(() => DtmImp.Utils.CheckStatus(code, msg));
6468
}
69+
70+
[Fact]
71+
public void OrString()
72+
{
73+
Assert.Equal("", DtmImp.Utils.OrString());
74+
Assert.Equal("A", DtmImp.Utils.OrString("", "A"));
75+
Assert.Equal("A", DtmImp.Utils.OrString("", "A", "B"));
76+
Assert.Equal("A", DtmImp.Utils.OrString("A", "B"));
77+
}
78+
79+
[Fact]
80+
public void String2DtmError()
81+
{
82+
Assert.IsType<Exception>(DtmImp.Utils.String2DtmError(null));
83+
Assert.Null(DtmImp.Utils.String2DtmError(string.Empty));
84+
Assert.Null(DtmImp.Utils.String2DtmError("SUCCESS"));
85+
Assert.IsType<DtmFailureException>(DtmImp.Utils.String2DtmError("FAILURE"));
86+
Assert.IsType<DtmOngingException>(DtmImp.Utils.String2DtmError("ONGOING"));
87+
Assert.IsType<Exception>(DtmImp.Utils.String2DtmError("Object ..."));
88+
}
89+
90+
[Fact]
91+
public void Result2HttpJson()
92+
{
93+
Assert.Equal(200, DtmImp.Utils.Result2HttpJson(null).httpStatusCode);
94+
Assert.Equal(409, DtmImp.Utils.Result2HttpJson(new DtmFailureException()).httpStatusCode);
95+
Assert.Equal(425, DtmImp.Utils.Result2HttpJson(new DtmOngingException()).httpStatusCode);
96+
Assert.Equal(500, DtmImp.Utils.Result2HttpJson(new DtmDuplicatedException()).httpStatusCode);
97+
98+
Assert.Equal(500, DtmImp.Utils.Result2HttpJson(new Exception("message context A")).httpStatusCode);
99+
Assert.Contains("message context A", JsonConvert.SerializeObject(DtmImp.Utils.Result2HttpJson(new Exception("message context A")).res));
100+
101+
Assert.Equal(200, DtmImp.Utils.Result2HttpJson("normal text").httpStatusCode);
102+
Assert.Equal("normal text", DtmImp.Utils.Result2HttpJson("normal text").res);
103+
104+
var obj = new { A = "hello", B = "world" };
105+
Assert.Equal(200, DtmImp.Utils.Result2HttpJson(obj).httpStatusCode);
106+
Assert.Equal(obj, DtmImp.Utils.Result2HttpJson(obj).res);
107+
}
65108
}
66109
}

0 commit comments

Comments
 (0)