1+ namespace ServiceControl . AcceptanceTests . Recoverability . MessageFailures ;
2+
3+ using System ;
4+ using System . Collections . Generic ;
5+ using System . Threading . Tasks ;
6+ using AcceptanceTesting ;
7+ using AcceptanceTesting . EndpointTemplates ;
8+ using Infrastructure ;
9+ using NServiceBus ;
10+ using NServiceBus . AcceptanceTesting ;
11+ using NServiceBus . Faults ;
12+ using NServiceBus . Routing ;
13+ using NServiceBus . Transport ;
14+ using NUnit . Framework ;
15+ using ServiceControl . MessageFailures . Api ;
16+
17+ class When_ingesting_failed_message_with_missing_headers : AcceptanceTest
18+ {
19+ [ Test ]
20+ public async Task Should_be_ingested_when_minimal_required_headers_is_present ( )
21+ {
22+ var testStartTime = DateTime . UtcNow ;
23+
24+ var context = await Define < TestContext > ( c => c . AddMinimalRequiredHeaders ( ) )
25+ . WithEndpoint < FailingEndpoint > ( )
26+ . Done ( async c => await TryGetFailureFromApi ( c ) )
27+ . Run ( ) ;
28+
29+ var failure = context . Failure ;
30+
31+ Assert . That ( failure , Is . Not . Null ) ;
32+ Assert . That ( failure . TimeSent , Is . Null ) ;
33+
34+ //No failure time will result in utc now being used
35+ Assert . That ( failure . TimeOfFailure , Is . GreaterThan ( testStartTime ) ) ;
36+
37+ // Both host and endpoint name is currently needed so this will be null since no host can be detected from the failed q header
38+ Assert . That ( failure . ReceivingEndpoint , Is . Null ) ;
39+ }
40+
41+ [ Test ]
42+ public async Task Should_include_headers_required_by_ServicePulse ( )
43+ {
44+ var context = await Define < TestContext > ( c =>
45+ {
46+ c . AddMinimalRequiredHeaders ( ) ;
47+
48+ // This is needed for ServiceControl to be able to detect both endpoint (via failed q header) and host via the processing machine header
49+ // Missing endpoint or host will cause a null ref in ServicePulse
50+ c . Headers [ Headers . ProcessingMachine ] = "MyMachine" ;
51+
52+ c . Headers [ FaultsHeaderKeys . ExceptionType ] = "SomeExceptionType" ;
53+ c . Headers [ FaultsHeaderKeys . Message ] = "Some message" ;
54+ } )
55+ . WithEndpoint < FailingEndpoint > ( )
56+ . Done ( async c => await TryGetFailureFromApi ( c ) )
57+ . Run ( ) ;
58+
59+ var failure = context . Failure ;
60+
61+ Assert . That ( failure , Is . Not . Null ) ;
62+
63+ // ServicePulse assumes that the receiving endpoint name is present
64+ Assert . That ( failure . ReceivingEndpoint , Is . Not . Null ) ;
65+ Assert . That ( failure . ReceivingEndpoint . Name , Is . EqualTo ( context . EndpointNameOfReceivingEndpoint ) ) ;
66+ Assert . That ( failure . ReceivingEndpoint . Host , Is . EqualTo ( "MyMachine" ) ) ;
67+
68+ // ServicePulse needs both an exception type and description to render the UI in a resonable way
69+ Assert . That ( failure . Exception . ExceptionType , Is . EqualTo ( "SomeExceptionType" ) ) ;
70+ Assert . That ( failure . Exception . Message , Is . EqualTo ( "Some message" ) ) ;
71+ }
72+
73+ [ Test ]
74+ public async Task TimeSent_should_not_be_casted ( )
75+ {
76+ var sentTime = DateTime . Parse ( "2014-11-11T02:26:58.000462Z" ) ;
77+
78+ var context = await Define < TestContext > ( c =>
79+ {
80+ c . AddMinimalRequiredHeaders ( ) ;
81+ c . Headers . Add ( Headers . TimeSent , DateTimeOffsetHelper . ToWireFormattedString ( sentTime ) ) ;
82+ } )
83+ . WithEndpoint < FailingEndpoint > ( )
84+ . Done ( async c => await TryGetFailureFromApi ( c ) )
85+ . Run ( ) ;
86+
87+ var failure = context . Failure ;
88+
89+ Assert . That ( failure , Is . Not . Null ) ;
90+ Assert . That ( failure . TimeSent , Is . EqualTo ( sentTime ) ) ;
91+ }
92+
93+ async Task < bool > TryGetFailureFromApi ( TestContext context )
94+ {
95+ context . Failure = await this . TryGet < FailedMessageView > ( $ "/api/errors/last/{ context . UniqueMessageId } ") ;
96+ return context . Failure != null ;
97+ }
98+
99+ class TestContext : ScenarioContext
100+ {
101+ public string MessageId { get ; } = Guid . NewGuid ( ) . ToString ( ) ;
102+
103+ public string EndpointNameOfReceivingEndpoint => "MyEndpoint" ;
104+
105+ public string UniqueMessageId => DeterministicGuid . MakeId ( MessageId , EndpointNameOfReceivingEndpoint ) . ToString ( ) ;
106+
107+ public Dictionary < string , string > Headers { get ; } = [ ] ;
108+
109+ public FailedMessageView Failure { get ; set ; }
110+
111+ public void AddMinimalRequiredHeaders ( )
112+ {
113+ Headers [ FaultsHeaderKeys . FailedQ ] = EndpointNameOfReceivingEndpoint ;
114+ Headers [ NServiceBus . Headers . MessageId ] = MessageId ;
115+ }
116+ }
117+
118+ class FailingEndpoint : EndpointConfigurationBuilder
119+ {
120+ public FailingEndpoint ( ) => EndpointSetup < DefaultServerWithoutAudit > ( ) ;
121+
122+ class SendFailedMessage : DispatchRawMessages < TestContext >
123+ {
124+ protected override TransportOperations CreateMessage ( TestContext context )
125+ {
126+ var outgoingMessage = new OutgoingMessage ( context . MessageId , context . Headers , Array . Empty < byte > ( ) ) ;
127+
128+ return new TransportOperations ( new TransportOperation ( outgoingMessage , new UnicastAddressTag ( "error" ) ) ) ;
129+ }
130+ }
131+ }
132+ }
0 commit comments