1212 using NServiceBus . Transport ;
1313 using NUnit . Framework ;
1414 using ServiceControl . MessageFailures . Api ;
15- using Conventions = NServiceBus . AcceptanceTesting . Customization . Conventions ;
1615
17- class When_processing_message_with_missing_metadata_failed : AcceptanceTest
16+ class When_ingesting_failed_message_with_missing_headers : AcceptanceTest
1817 {
1918 [ Test ]
20- public async Task Null_TimeSent_should_not_be_cast_to_DateTimeMin ( )
19+ public async Task TimeSent_should_not_be_casted ( )
2120 {
2221 FailedMessageView failure = null ;
2322
24- await Define < MyContext > ( )
25- . WithEndpoint < Failing > ( )
23+ var sentTime = DateTime . Parse ( "2014-11-11T02:26:58.000462Z" ) ;
24+
25+ await Define < MyContext > ( c =>
26+ {
27+ c . AddMinimalRequiredHeaders ( ) ;
28+ c . Headers . Add ( "NServiceBus.TimeSent" , DateTimeOffsetHelper . ToWireFormattedString ( sentTime ) ) ;
29+ } )
30+ . WithEndpoint < FailingEndpoint > ( )
2631 . Done ( async c =>
2732 {
28- var result = await this . TryGetSingle < FailedMessageView > ( "/api/errors/" , m => m . Id == c . UniqueMessageId ) ;
33+ var result = await this . TryGet < FailedMessageView > ( $ "/api/errors/last/ { c . UniqueMessageId } " ) ;
2934 failure = result ;
30- return result ;
35+ return ( c . UniqueMessageId != null ) & result ;
3136 } )
3237 . Run ( ) ;
3338
3439 Assert . That ( failure , Is . Not . Null ) ;
35- Assert . That ( failure . TimeSent , Is . Null ) ;
40+ Assert . That ( failure . TimeSent , Is . EqualTo ( sentTime ) ) ;
3641 }
3742
3843 [ Test ]
39- public async Task TimeSent_should_not_be_casted ( )
44+ public async Task Should_be_ingested_when_minimal_required_headers_is_present ( )
4045 {
4146 FailedMessageView failure = null ;
4247
43- var sentTime = DateTime . Parse ( "2014-11-11T02:26:58.000462Z" ) ;
44-
45- await Define < MyContext > ( ctx => ctx . TimeSent = sentTime )
46- . WithEndpoint < Failing > ( )
48+ await Define < MyContext > ( c =>
49+ {
50+ c . AddMinimalRequiredHeaders ( ) ;
51+ } )
52+ . WithEndpoint < FailingEndpoint > ( )
4753 . Done ( async c =>
4854 {
4955 var result = await this . TryGet < FailedMessageView > ( $ "/api/errors/last/{ c . UniqueMessageId } ") ;
@@ -53,18 +59,28 @@ await Define<MyContext>(ctx => ctx.TimeSent = sentTime)
5359 . Run ( ) ;
5460
5561 Assert . That ( failure , Is . Not . Null ) ;
56- Assert . That ( failure . TimeSent , Is . EqualTo ( sentTime ) ) ;
62+ Assert . That ( failure . TimeSent , Is . Null ) ;
5763 }
5864
5965 [ Test ]
60- public async Task Should_be_able_to_get_the_message_by_id ( )
66+ public async Task Should_include_headers_required_by_ServicePulse ( )
6167 {
6268 FailedMessageView failure = null ;
6369
6470 var testStartTime = DateTime . UtcNow ;
6571
66- var context = await Define < MyContext > ( )
67- . WithEndpoint < Failing > ( )
72+ var context = await Define < MyContext > ( c =>
73+ {
74+ c . AddMinimalRequiredHeaders ( ) ;
75+
76+ // This is needed for ServiceControl to be able to detect both endpoint (via failed q header) and host via the processing machine header
77+ // Missing endpoint or host will case a null ref in ServicePulse
78+ c . Headers [ Headers . ProcessingMachine ] = "MyMachine" ;
79+
80+ c . Headers [ "NServiceBus.ExceptionInfo.ExceptionType" ] = "SomeExceptionType" ;
81+ c . Headers [ "NServiceBus.ExceptionInfo.Message" ] = "Some message" ;
82+ } )
83+ . WithEndpoint < FailingEndpoint > ( )
6884 . Done ( async c =>
6985 {
7086 var result = await this . TryGet < FailedMessageView > ( $ "/api/errors/last/{ c . UniqueMessageId } ") ;
@@ -81,48 +97,43 @@ public async Task Should_be_able_to_get_the_message_by_id()
8197 // ServicePulse assumes that the receiving endpoint name is present
8298 Assert . That ( failure . ReceivingEndpoint , Is . Not . Null ) ;
8399 Assert . That ( failure . ReceivingEndpoint . Name , Is . EqualTo ( context . EndpointNameOfReceivingEndpoint ) ) ;
100+ Assert . That ( failure . ReceivingEndpoint . Host , Is . EqualTo ( "MyMachine" ) ) ;
101+
102+ // ServicePulse needs both an exception type and description to render the UI in a resonable way
103+ Assert . That ( failure . Exception . ExceptionType , Is . EqualTo ( "SomeExceptionType" ) ) ;
104+ Assert . That ( failure . Exception . Message , Is . EqualTo ( "Some message" ) ) ;
84105 }
85106
86- class Failing : EndpointConfigurationBuilder
107+ class MyContext : ScenarioContext
87108 {
88- public Failing ( ) => EndpointSetup < DefaultServerWithoutAudit > ( c => c . Recoverability ( ) . Delayed ( x => x . NumberOfRetries ( 0 ) ) ) ;
89-
90- class SendFailedMessage : DispatchRawMessages < MyContext >
91- {
92- protected override TransportOperations CreateMessage ( MyContext context )
93- {
94- context . EndpointNameOfReceivingEndpoint = Conventions . EndpointNamingConvention ( typeof ( Failing ) ) ;
95- context . MessageId = Guid . NewGuid ( ) . ToString ( ) ;
96- context . UniqueMessageId = DeterministicGuid . MakeId ( context . MessageId , context . EndpointNameOfReceivingEndpoint ) . ToString ( ) ;
109+ public string MessageId { get ; } = Guid . NewGuid ( ) . ToString ( ) ;
97110
98- var headers = new Dictionary < string , string >
99- {
100- [ Headers . MessageId ] = context . MessageId ,
101- [ "NServiceBus.FailedQ" ] = Conventions . EndpointNamingConvention ( typeof ( Failing ) ) ,
102- [ Headers . ProcessingMachine ] = "unknown" , // This is needed for endpoint detection to work since "host" is required, endpoint name is detected from the FailedQ header
103- } ;
111+ public string EndpointNameOfReceivingEndpoint => "MyEndpoint" ;
104112
105- if ( context . TimeSent . HasValue )
106- {
107- headers [ "NServiceBus.TimeSent" ] = DateTimeOffsetHelper . ToWireFormattedString ( context . TimeSent . Value ) ;
108- }
113+ public string UniqueMessageId => DeterministicGuid . MakeId ( MessageId , EndpointNameOfReceivingEndpoint ) . ToString ( ) ;
109114
110- var outgoingMessage = new OutgoingMessage ( context . MessageId , headers , Array . Empty < byte > ( ) ) ;
115+ public Dictionary < string , string > Headers { get ; } = [ ] ;
111116
112- return new TransportOperations ( new TransportOperation ( outgoingMessage , new UnicastAddressTag ( "error" ) ) ) ;
113- }
117+ public void AddMinimalRequiredHeaders ( )
118+ {
119+ Headers [ "NServiceBus.FailedQ" ] = EndpointNameOfReceivingEndpoint ;
120+ Headers [ NServiceBus . Headers . MessageId ] = MessageId ;
114121 }
115122 }
116123
117- class MyContext : ScenarioContext
124+ class FailingEndpoint : EndpointConfigurationBuilder
118125 {
119- public string MessageId { get ; set ; }
120-
121- public string EndpointNameOfReceivingEndpoint { get ; set ; }
126+ public FailingEndpoint ( ) => EndpointSetup < DefaultServerWithoutAudit > ( c => c . Recoverability ( ) . Delayed ( x => x . NumberOfRetries ( 0 ) ) ) ;
122127
123- public string UniqueMessageId { get ; set ; }
128+ class SendFailedMessage : DispatchRawMessages < MyContext >
129+ {
130+ protected override TransportOperations CreateMessage ( MyContext context )
131+ {
132+ var outgoingMessage = new OutgoingMessage ( context . MessageId , context . Headers , Array . Empty < byte > ( ) ) ;
124133
125- public DateTime ? TimeSent { get ; set ; }
134+ return new TransportOperations ( new TransportOperation ( outgoingMessage , new UnicastAddressTag ( "error" ) ) ) ;
135+ }
136+ }
126137 }
127138 }
128139}
0 commit comments