Skip to content

Commit 3b02216

Browse files
Refine test to show minimal metadata needed for errors to be imported successfully (#4943)
* Refine test to show minimal metadata needed for errors to be imported successfully * Wording * Better comment * More comments * Isolate changes * More rollbacks * Separate tests for minimal required for ingestion vs minimal required for UX experience * Improve assertions * Cleanup * Consolidate done criteria * Use header key constants
1 parent 63cf922 commit 3b02216

File tree

4 files changed

+138
-130
lines changed

4 files changed

+138
-130
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
}

src/ServiceControl.AcceptanceTests/Recoverability/MessageFailures/When_processing_message_with_missing_metadata_failed.cs

Lines changed: 0 additions & 126 deletions
This file was deleted.

src/ServiceControl.Audit/Recoverability/DetectSuccessfulRetriesEnricher.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Contracts.MessageFailures;
88
using Infrastructure;
99
using NServiceBus;
10+
using NServiceBus.Faults;
1011
using NServiceBus.Routing;
1112
using NServiceBus.Transport;
1213
using ServiceControl.Audit.Persistence.Infrastructure;
@@ -64,7 +65,7 @@ IEnumerable<string> GetAlternativeUniqueMessageId(IReadOnlyDictionary<string, st
6465
yield return DeterministicGuid.MakeId(messageId, processingEndpoint).ToString();
6566
}
6667

67-
if (headers.TryGetValue("NServiceBus.FailedQ", out var failedQ))
68+
if (headers.TryGetValue(FaultsHeaderKeys.FailedQ, out var failedQ))
6869
{
6970
yield return DeterministicGuid.MakeId(messageId, ExtractQueueNameForLegacyReasons(failedQ)).ToString();
7071
}

src/ServiceControl/Operations/EndpointDetailsParser.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace ServiceControl.Contracts.Operations
44
using System.Collections.Generic;
55
using Infrastructure;
66
using NServiceBus;
7+
using NServiceBus.Faults;
78
using ServiceControl.Operations;
89

910
class EndpointDetailsParser
@@ -13,7 +14,7 @@ public static EndpointDetails SendingEndpoint(IReadOnlyDictionary<string, string
1314
var endpointDetails = new EndpointDetails();
1415

1516
DictionaryExtensions.CheckIfKeyExists(Headers.OriginatingEndpoint, headers, s => endpointDetails.Name = s);
16-
DictionaryExtensions.CheckIfKeyExists("NServiceBus.OriginatingMachine", headers, s => endpointDetails.Host = s);
17+
DictionaryExtensions.CheckIfKeyExists(Headers.OriginatingMachine, headers, s => endpointDetails.Host = s);
1718
DictionaryExtensions.CheckIfKeyExists(Headers.OriginatingHostId, headers, s => endpointDetails.HostId = Guid.Parse(s));
1819

1920
if (!string.IsNullOrEmpty(endpointDetails.Name) && !string.IsNullOrEmpty(endpointDetails.Host))
@@ -50,7 +51,7 @@ public static EndpointDetails ReceivingEndpoint(IReadOnlyDictionary<string, stri
5051
}
5152
else
5253
{
53-
DictionaryExtensions.CheckIfKeyExists("NServiceBus.ProcessingMachine", headers, s => endpoint.Host = s);
54+
DictionaryExtensions.CheckIfKeyExists(Headers.ProcessingMachine, headers, s => endpoint.Host = s);
5455
}
5556

5657
DictionaryExtensions.CheckIfKeyExists(Headers.ProcessingEndpoint, headers, s => endpoint.Name = s);
@@ -62,7 +63,7 @@ public static EndpointDetails ReceivingEndpoint(IReadOnlyDictionary<string, stri
6263

6364
string address = null;
6465
//use the failed q to determine the receiving endpoint
65-
DictionaryExtensions.CheckIfKeyExists("NServiceBus.FailedQ", headers, s => address = s);
66+
DictionaryExtensions.CheckIfKeyExists(FaultsHeaderKeys.FailedQ, headers, s => address = s);
6667

6768
// If we have a failed queue, then construct an endpoint from the failed queue information
6869
if (address != null)

0 commit comments

Comments
 (0)