Asynchronous MultiThreaded SSH engine for Web (Net Core 6, Linux) - Part 9,10 (BackendAPI for NotificationController and my technique to testing this engine with xUnit).
9. BackendAPI for NotificationController.
This is last part of my component Asynchronous MultiThreaded SSH engine for Web (Net Core 6, Linux), code of this part is ordinary MVC code.
I have injected INotificationCacheService and use data collected in this service to send notification by SignalR hub.
This is core of logic - checking if client is still alive, adding long Result value from DB and sending notification to client.
131: <Jwt.Authorize>
132: <HttpGet>
133: Public Async Function FlushNotification() As Task(Of IActionResult)
134: Dim CurUsers = _UserService.GetCurrentUser(_httpContextAccessor.HttpContext.Request.Headers("JwtUserName")(0))
135: If CurUsers.IsAdmin Or CurUsers.UserName = "Notification" Then
136: Dim ListNotification As List(Of BashJobFinishedRequest)
137: Dim ListSignalRConnection As MyConcurrentDictionary(Of String, Integer)
138: Try
139: ListNotification = _NotificationCache.ListNotification
140: ListSignalRConnection = _NotificationCache.AllSignalRClientConnection()
141: For Each OneClient As KeyValuePair(Of String, Integer) In ListSignalRConnection.ToList
142: Dim NotificationForUser = ListNotification.Where(Function(X) X.SubscribeId = OneClient.Key).ToList
143: If NotificationForUser.Count > 0 Then
144: For Each OneNotif As BashJobFinishedRequest In NotificationForUser
145: _Log.LogWarning($"Notification {OneNotif.i} sending to client {OneClient.Key}")
146: Dim FullResult = Await _DB.RawSqlQueryAsync(Of BashJob)($"SELECT `i`,`Result`,`Error` FROM `cryptochestmax`.`BashJob` WHERE `i`={OneNotif.i};", Function(X) New BashJob With {
147: .i = X("i"),
148: .Result = X.CheckDBNull(Of String)(X("Result")),
149: .[Error] = X.CheckDBNull(Of String)(X("Error"))
150: })
151: Dim FullNotif As New ClientNotificationMessage With {
152: .i = OneNotif.i,
153: .CrDate = OneNotif.CrDate,
154: .toServer = OneNotif.toServer,
155: .toVm = OneNotif.toVm,
156: .toUser = OneNotif.toUser,
157: .Command = OneNotif.Command,
158: .SubscribeId = OneNotif.SubscribeId,
159: .Comment = OneNotif.Comment,
160: .LastUpdate = OneNotif.LastUpdate,
161: .Result = FullResult.Item1(0).Result,
162: .[Error] = FullResult.Item1(0).Error
163: }
164: Dim NotifyResultTSK = _NotificationCache.NotifyClient(OneClient.Key, FullNotif)
165: Await NotifyResultTSK
166: Dim DbOperationTSK = _NotificationCache.MarkNotificationAsSendedToSubscriberInDb(OneNotif.i)
167: Await DbOperationTSK
168: _NotificationCache.DelNotification(OneNotif)
169: _Log.LogWarning($"Notification {OneNotif.i} sended to client {OneClient.Key}, Time {Now}")
170: Next
171: End If
172: Next
173: For Each OneNotification As BashJobFinishedRequest In ListNotification
174: For Each OneSigConnection As KeyValuePair(Of String, Integer) In ListSignalRConnection
175: If OneNotification.SubscribeId = OneSigConnection.Key Then GoTo Nodel
176: Next
177: _Log.LogWarning($"Notification {OneNotification.i} mark as finished because SignalR client {OneNotification.SubscribeId} is absent, Time {Now}")
178: _NotificationCache.DelNotification(OneNotification)
179: Dim DbOperationErrTSK = _NotificationCache.MarkNotificationAsSendedToSubscriberInDb(OneNotification.i)
180: Await DbOperationErrTSK
181: Nodel:
182: Next
183: Return Ok(New With {.NotificationKey = _NotificationCache.PrintNotificationKey, .SignalRConnectionKeys = _NotificationCache.PrintSignalRConnectionKeys})
184: Catch ex As Exception
185: Return StatusCode(StatusCodes.Status500InternalServerError, New With {.Error = ex.Message, .NotificationKey = _NotificationCache.PrintNotificationKey})
186: End Try
187: Else
188: Return New JsonResult(Unauthorized())
189: End If
190: End Function
10. My technique to testing this engine with xUnit.
This component not simple and, of course, I was test all parts of this components one-by-one be DDD technique. Firstly, sending group notification, than notification to each clients. Started from test notification to notification with real data.
So, this is my way to testing this component.
1: Imports System.Net
2: Imports System.Text
3: Imports System.Text.RegularExpressions
4: Imports BackendAPI
5: Imports BackendAPI.KVM
6: Imports BackendAPI.Notification
7: Imports BackendAPI.WebApi.Controllers
8: Imports Microsoft.AspNetCore.Http.Connections
9: Imports Microsoft.AspNetCore.Http.Connections.Client
10: Imports Microsoft.AspNetCore.SignalR.Client
11: Imports Newtonsoft.Json
12: Imports Xunit
13: Imports Xunit.Abstractions
14:
15: Public Class NotificationTest
16:
17: Friend ReadOnly BaseUrl As String = "http://localhost:4000/"
18: Friend ReadOnly Request As MyWebClient
19:
20: Private ReadOnly Log As ITestOutputHelper
21: Const SignalRHubURL As String = "http://localhost:4000/NotificationHub"
22:
23: Public Sub New(_Log As ITestOutputHelper)
24: Request = New MyWebClient
25: Request.BaseAddress = BaseUrl
26: Request.Headers.Add("Content-Type", "application/json")
27: Log = _Log
28: 'BackendAPI.Program.Main(Nothing)
29: 'System.Diagnostics.Debugger.Launch()
30: End Sub
31:
32: <Theory>
33: <InlineData("SRV")> 'TST or SRV
34: Async Sub ConnectToNotificationHub(ApiType As String)
35: Dim AToken = GetAdminToken(Request)
36: Request.Headers.Add("Content-Type", "application/json")
37: Dim Utoken = GetUserToken(Request, "Lux", "test")
38: Dim Cn1 As HubConnection = StartClient(1, AToken)
39: Dim Cn2 As HubConnection = StartClient(3, Utoken)
40: Dim Cn3 As HubConnection = StartClient(3, Utoken)
41: Await Task.Delay(10000)
42: Dim ServerConnectionID As String() = ListAllClientConnectionToHub(AToken)
43: NotifyAllClients(AToken, ApiType)
44: NotifyOneClient(AToken, ServerConnectionID(0), ApiType)
45: NotifyOneClient(AToken, ServerConnectionID(1), ApiType)
46: NotifyOneClient(AToken, ServerConnectionID(2), ApiType)
47: Dim Tsk1 As Task = Cn1.StopAsync()
48: Await Tsk1
49: Dim Tsk2 As Task = Cn2.StopAsync()
50: Await Tsk2
51: Dim Tsk3 As Task = Cn3.StopAsync()
52: Await Tsk3
53: End Sub
54:
55: Function StartClient(UserID As Integer, UserToken As String) As HubConnection
56: Dim SignalRConnection As HubConnection = New HubConnectionBuilder().
57: WithUrl(SignalRHubURL, Sub(X) X.Headers.Add("Authorization", UserToken)).
58: WithAutomaticReconnect({TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(10)}).
59: Build
60: AddHandler SignalRConnection.Closed, Async Function(X)
61: Log.WriteLine($"ConnectionClosed {X?.Message}")
62: Await Task.Delay(New Random().Next(0, 5) * 1000)
63: Await SignalRConnection.StartAsync()
64: End Function
65: AddHandler SignalRConnection.Reconnected, Async Function(X)
66: Log.WriteLine($"Reconnected :{X}")
67: Await Task.CompletedTask
68: End Function
69: AddHandler SignalRConnection.Reconnecting, Async Function(X)
70: Log.WriteLine($"Reconnecting :{X?.Message}")
71: Await Task.CompletedTask
72: End Function
73: SignalRConnection.On("TestNotification", Sub(X) TestNotification(X))
74: SignalRConnection.On("NotifyClient", Sub(X) NotifyClient(X))
75: Dim Tsk = SignalRConnection.StartAsync()
76: Tsk.Wait()
77: Log.WriteLine($"Connection {SignalRHubURL}/{SignalRConnection.ConnectionId} is {SignalRConnection.State}, UserID={UserID}")
78: Return SignalRConnection
79: End Function
80:
81: Sub TestNotification(ParamArray X())
82: Log.WriteLine($"Recived TestNotification Message :{String.Join(",", X)}")
83: End Sub
84: Sub NotifyClient(ParamArray X())
85: Log.WriteLine($"Recived NotifyClient Message :{String.Join(",", X)}")
86: End Sub
87:
88:
89: Function ListAllClientConnectionToHub(Token As String) As String()
90: Request.Headers.Add("Content-Type", "application/json")
91: Request.Headers.Add("Authorization", "Bearer: " & Token)
92: Try
93: Dim Response = Request.DownloadString("/Notification/ListAllClientConnectionToHub")
94: Log.WriteLine(Response)
95: Return Response.Split(",")
96: Catch ex As WebException
97: Dim Resp As String = ""
98: Dim Stream = ex.Response?.GetResponseStream()
99: If Stream IsNot Nothing Then
100: Dim Sr = New IO.StreamReader(Stream)
101: Resp = Sr.ReadToEnd
102: End If
103: Log.WriteLine(Resp & vbCrLf & ex.Message)
104: End Try
105: End Function
106:
107: Sub NotifyAllClients(Token As String, ApiType As String)
108: Request.Headers.Add("Content-Type", "application/json")
109: Request.Headers.Add("Authorization", "Bearer: " & Token)
110: Try
111: Dim Response = Request.DownloadString($"/Notification/NotifyAllClients{ApiType}")
112: Log.WriteLine(Response)
113: Catch ex As WebException
114: Dim Resp As String = ""
115: Dim Stream = ex.Response?.GetResponseStream()
116: If Stream IsNot Nothing Then
117: Dim Sr = New IO.StreamReader(Stream)
118: Resp = Sr.ReadToEnd
119: End If
120: Log.WriteLine(Resp & vbCrLf & ex.Message)
121: End Try
122: End Sub
123:
124: Sub NotifyOneClient(Token As String, ConnectionID As String, ApiType As String)
125: Request.Headers.Add("Content-Type", "application/json")
126: Request.Headers.Add("Authorization", "Bearer: " & Token)
127: Try
128: Dim Response = Request.DownloadString($"/Notification/NotifyOneClient{ApiType}?ConnectionID={ConnectionID}")
129: Log.WriteLine(Response)
130: Catch ex As WebException
131: Dim Resp As String = ""
132: Dim Stream = ex.Response?.GetResponseStream()
133: If Stream IsNot Nothing Then
134: Dim Sr = New IO.StreamReader(Stream)
135: Resp = Sr.ReadToEnd
136: End If
137: Log.WriteLine(Resp & vbCrLf & ex.Message)
138: End Try
139: End Sub
140:
141: <Theory>
142: <InlineData(1, "XXXXXXXX", "pwd")>
143: Sub AddBashJob(ServerI As Integer, ServerDecryptPass As String, BashCmd As String)
144: Dim Token = GetAdminToken(Request)
145: Request.Headers.Add("Content-Type", "application/json")
146: Request.Headers.Add("Authorization", "Bearer: " & Token)
147: Dim PostPrm = New BashJobRequest With {
148: .toServer = ServerI,
149: .SshDecryptPass = ServerDecryptPass,
150: .Command = BashCmd,
151: .toUser = 1
152: }
153: Dim JsonSerializer = New JsonSerializer()
154: Dim PostData = JsonConvert.SerializeObject(PostPrm)
155: Try
156: Dim Response = Encoding.UTF8.GetString(Request.UploadData("/Notification/AddBashJob", Encoding.UTF8.GetBytes(PostData)))
157: Log.WriteLine(Response)
158: Catch ex As WebException
159: Dim Resp As String = ""
160: Dim Stream = ex.Response?.GetResponseStream()
161: If Stream IsNot Nothing Then
162: Dim Sr = New IO.StreamReader(Stream)
163: Resp = Sr.ReadToEnd
164: End If
165: Log.WriteLine(Resp & vbCrLf & ex.Message)
166: End Try
167: End Sub
168:
169: Sub AddBashJobWithSubScribeID(Token As String, ServerI As Integer, ServerDecryptPass As String, BashCmd As String, SubscribeID As String)
170: Request.Headers.Add("Content-Type", "application/json")
171: Request.Headers.Add("Authorization", "Bearer: " & Token)
172: Dim PostPrm = New BashJobRequest With {
173: .toServer = ServerI,
174: .SshDecryptPass = ServerDecryptPass,
175: .Command = BashCmd,
176: .toUser = 1,
177: .SubscribeId = SubscribeID
178: }
179: Dim JsonSerializer = New JsonSerializer()
180: Dim PostData = JsonConvert.SerializeObject(PostPrm)
181: Try
182: Dim Response = Encoding.UTF8.GetString(Request.UploadData("/Notification/AddBashJob", Encoding.UTF8.GetBytes(PostData)))
183: Log.WriteLine(Response)
184: Catch ex As WebException
185: Dim Resp As String = ""
186: Dim Stream = ex.Response?.GetResponseStream()
187: If Stream IsNot Nothing Then
188: Dim Sr = New IO.StreamReader(Stream)
189: Resp = Sr.ReadToEnd
190: End If
191: Log.WriteLine(Resp & vbCrLf & ex.Message)
192: End Try
193: End Sub
194:
195: Sub ListNotificationCacheWaitingToSendToSubscriber(Token As String)
196: Request.Headers.Add("Content-Type", "application/json")
197: Request.Headers.Add("Authorization", "Bearer: " & Token)
198: Try
199: Dim Response = Request.DownloadString("/Notification/ListNotificationCacheWaitingToSendToSubscriber")
200: Log.WriteLine(Response)
201: Catch ex As WebException
202: Dim Resp As String = ""
203: Dim Stream = ex.Response?.GetResponseStream()
204: If Stream IsNot Nothing Then
205: Dim Sr = New IO.StreamReader(Stream)
206: Resp = Sr.ReadToEnd
207: End If
208: Log.WriteLine(Resp & vbCrLf & ex.Message)
209: End Try
210: End Sub
211:
212: <Theory>
213: <InlineData(1, "XXXXXXXXXX", "pwd")>
214: Async Sub AddBashJobAndReceiveNotification(ServerI As Integer, ServerDecryptPass As String, BashCmd As String)
215: Request.Headers.Add("Content-Type", "application/json")
216: Dim AToken = GetAdminToken(Request)
217: Dim Cn1 As HubConnection = StartClient(1, AToken)
218: Await Task.Delay(10000)
219: Dim ServerConnectionID As String() = ListAllClientConnectionToHub(AToken)
220: Dim AddJob = Task.Run(Sub()
221: AddBashJobWithSubScribeID(AToken, ServerI, ServerDecryptPass, BashCmd, ServerConnectionID(0))
222: End Sub)
223: Await Task.Delay(10000)
224: Dim Tsk1 As Task = Cn1.StopAsync()
225: End Sub
226:
227:
228: End Class
By the way, for testing other components of this project I use more sophisticated method of testing with ClassData.
That's it about component Asynchronous MultiThreaded SSH engine for Web (Net Core 6, Linux).
|