Skip to content

Commit 6c9622f

Browse files
committed
Separate tests for minimal required for ingestion vs minimal required for UX experience
1 parent bd765a0 commit 6c9622f

File tree

1 file changed

+57
-46
lines changed

1 file changed

+57
-46
lines changed

src/ServiceControl.AcceptanceTests/Recoverability/MessageFailures/When_processing_message_with_missing_metadata_failed.cs renamed to src/ServiceControl.AcceptanceTests/Recoverability/MessageFailures/When_ingesting_failed_message_with_missing_headers.cs

Lines changed: 57 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,44 @@
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

Comments
 (0)