1+ namespace ServiceControl . AcceptanceTests . Recoverability . ExternalIntegration
2+ {
3+ using System . Collections . Generic ;
4+ using System . Linq ;
5+ using System . Threading . Tasks ;
6+ using AcceptanceTesting ;
7+ using AcceptanceTesting . EndpointTemplates ;
8+ using Contracts ;
9+ using NServiceBus ;
10+ using NServiceBus . AcceptanceTesting ;
11+ using NUnit . Framework ;
12+ using ServiceControl . MessageFailures ;
13+ using ServiceControl . MessageFailures . Api ;
14+ using JsonSerializer = System . Text . Json . JsonSerializer ;
15+
16+ class When_a_reedit_solves_a_failed_msg : AcceptanceTest
17+ {
18+ [ Test ]
19+ public async Task Should_publish_notification ( )
20+ {
21+ var context = await Define < EditMessageResolutionContext > ( )
22+ . WithEndpoint < MessageReceiver > ( b => b . When ( async ( bus , c ) =>
23+ {
24+ await bus . Subscribe < MessageFailureResolvedByRetry > ( ) ;
25+ } ) . When ( c => c . SendLocal ( new EditResolutionMessage ( ) { MessageAttempt = 0 } ) )
26+ . DoNotFailOnErrorMessages ( ) )
27+ . Done ( async ctx =>
28+ {
29+ if ( ! ctx . OriginalMessageHandled )
30+ {
31+ return false ;
32+ }
33+
34+ if ( ! ctx . FirstEdit )
35+ {
36+ var failedMessagedId = await this . GetOnlyFailedUnresolvedMessageId ( ) ;
37+ if ( failedMessagedId == null )
38+ {
39+ return false ;
40+ }
41+
42+ ctx . OriginalMessageFailureId = failedMessagedId ;
43+ ctx . FirstEdit = true ;
44+
45+ string editedMessage =
46+ JsonSerializer . Serialize ( new EditResolutionMessage { MessageAttempt = 1 } ) ;
47+
48+ SingleResult < FailedMessage > failedMessage =
49+ await this . TryGet < FailedMessage > ( $ "/api/errors/{ ctx . OriginalMessageFailureId } ") ;
50+
51+ var editModel = new EditMessageModel
52+ {
53+ MessageBody = editedMessage ,
54+ MessageHeaders = failedMessage . Item . ProcessingAttempts . Last ( ) . Headers
55+ } ;
56+ await this . Post ( $ "/api/edit/{ ctx . OriginalMessageFailureId } ", editModel ) ;
57+ return false ;
58+ }
59+
60+ if ( ! ctx . SecondEdit )
61+ {
62+ var failedMessagedId = await this . GetOnlyFailedUnresolvedMessageId ( ) ;
63+ if ( failedMessagedId == null )
64+ {
65+ return false ;
66+ }
67+
68+ ctx . SecondMessageFailureId = failedMessagedId ;
69+ ctx . SecondEdit = true ;
70+
71+ string editedMessage =
72+ JsonSerializer . Serialize ( new EditResolutionMessage { MessageAttempt = 2 } ) ;
73+
74+ SingleResult < FailedMessage > failedMessage =
75+ await this . TryGet < FailedMessage > ( $ "/api/errors/{ ctx . SecondMessageFailureId } ") ;
76+
77+ var editModel = new EditMessageModel
78+ {
79+ MessageBody = editedMessage ,
80+ MessageHeaders = failedMessage . Item . ProcessingAttempts . Last ( ) . Headers
81+ } ;
82+ await this . Post ( $ "/api/edit/{ ctx . SecondMessageFailureId } ", editModel ) ;
83+ return false ;
84+ }
85+
86+ if ( ! ctx . EditedMessageHandled )
87+ {
88+ return false ;
89+ }
90+
91+ if ( ! ctx . MessageResolved )
92+ {
93+ return false ;
94+ }
95+
96+ return true ;
97+ } ) . Run ( ) ;
98+
99+ Assert . Multiple ( ( ) =>
100+ {
101+ Assert . That ( context . ResolvedMessageId , Is . EqualTo ( context . SecondMessageFailureId ) ) ;
102+ Assert . That ( context . EditedMessageEditOf1 , Is . EqualTo ( context . OriginalMessageFailureId ) ) ;
103+ Assert . That ( context . EditedMessageEditOf2 , Is . EqualTo ( context . SecondMessageFailureId ) ) ;
104+ } ) ;
105+ }
106+
107+
108+ public class EditMessageResolutionContext : ScenarioContext
109+ {
110+ public bool OriginalMessageHandled { get ; set ; }
111+ public bool EditedMessage { get ; set ; }
112+ public string OriginalMessageFailureId { get ; set ; }
113+ public bool EditedMessageHandled { get ; set ; }
114+ public string ResolvedMessageId { get ; set ; }
115+ public bool MessageResolved { get ; set ; }
116+ public bool HasBeenEditedOnce { get ; set ; }
117+ public bool FirstEdit { get ; set ; }
118+ public bool SecondEdit { get ; set ; }
119+ public string SecondMessageFailureId { get ; set ; }
120+ public string EditedMessageEditOf2 { get ; set ; }
121+ public string EditedMessageEditOf1 { get ; set ; }
122+ }
123+
124+ public class MessageReceiver : EndpointConfigurationBuilder
125+ {
126+ public MessageReceiver ( ) => EndpointSetup < DefaultServerWithoutAudit > ( c => c . NoRetries ( ) ) ;
127+
128+
129+ public class EditMessageResolutionHandler ( EditMessageResolutionContext testContext )
130+ : IHandleMessages < EditResolutionMessage > , IHandleMessages < MessageFailureResolvedByRetry >
131+ {
132+ public Task Handle ( EditResolutionMessage message , IMessageHandlerContext context )
133+ {
134+ switch ( message . MessageAttempt )
135+ {
136+ case 0 :
137+ testContext . OriginalMessageHandled = true ;
138+ throw new SimulatedException ( ) ;
139+ case 1 :
140+ testContext . HasBeenEditedOnce = true ;
141+ testContext . EditedMessageEditOf1 = context . MessageHeaders [ "ServiceControl.EditOf" ] ;
142+ throw new SimulatedException ( ) ;
143+ case 2 :
144+ testContext . EditedMessageHandled = true ;
145+ testContext . EditedMessageEditOf2 = context . MessageHeaders [ "ServiceControl.EditOf" ] ;
146+ return Task . CompletedTask ;
147+ default :
148+ return Task . CompletedTask ;
149+ }
150+ }
151+
152+ public Task Handle ( MessageFailureResolvedByRetry message , IMessageHandlerContext context )
153+ {
154+ testContext . ResolvedMessageId = message . FailedMessageId ;
155+ testContext . MessageResolved = true ;
156+ return Task . CompletedTask ;
157+ }
158+ }
159+ }
160+
161+ public class EditResolutionMessage : IMessage
162+ {
163+ public int MessageAttempt { get ; init ; }
164+ }
165+ }
166+ }
0 commit comments