Crash reports never reached SMTP: `SendCrashEmail` returned immediately when email was enabled and never called the sender. It now respects the enabled setting, sends the report, and waits up to 30 seconds before the crash handler returns and the process can exit. The sender uses the `Message-ID` generated by `MimeMessage` and the sender address already added by each caller. This removes the duplicate `Message-ID` header and duplicate address within the `From` header. Gmail rejects duplicate `Message-ID` headers: https://support.google.com/a/answer/13567860. Email retries use a bounded doubling schedule instead of squaring an integer delay, which could overflow and throw through an `async void` method. The sender returns `Task`, uses `ConfigureAwait(false)` throughout, and logs failures. With the default five attempts and three-second initial delay, waits are 3, 6, 12, and 24 seconds. Each wait is capped at one hour, and there is no wait after the last attempt. Validation: - Full Release test suite on the branch updated with `main`: 2,103 passed, 2 intentionally skipped. - Eight email test cases cover disabled-email early returns, retry timing, low attempt counts, and overflow-prone delays. - SMTP delivery and the crash-time shutdown path are not covered by an integration test.
61 lines
1.7 KiB
C#
61 lines
1.7 KiB
C#
using System;
|
|
using System.IO;
|
|
using Server.Configurations;
|
|
using Server.Misc;
|
|
using Xunit;
|
|
|
|
namespace UOContent.Tests;
|
|
|
|
[Collection("Sequential UOContent Tests")]
|
|
public class EmailTests
|
|
{
|
|
[Fact]
|
|
public void SendCrashEmailDoesNothingWhileEmailIsDisabled()
|
|
{
|
|
Assert.False(EmailConfiguration.EmailEnabled, "The test host never enables email");
|
|
|
|
// Missing input must be ignored while email is disabled.
|
|
var report = Path.Combine(Path.GetTempPath(), $"crash-email-test-{Guid.NewGuid():N}", "Crash.log");
|
|
|
|
Email.SendCrashEmail(report);
|
|
}
|
|
|
|
[Fact]
|
|
public void SendQueueEmailDoesNothingWhileEmailIsDisabled()
|
|
{
|
|
Assert.False(EmailConfiguration.EmailEnabled, "The test host never enables email");
|
|
|
|
Email.SendQueueEmail(null, "Other");
|
|
}
|
|
|
|
[Fact]
|
|
public void RetryWaitsDoubleFromTheConfiguredDelayAndStopAtTheLastAttempt()
|
|
{
|
|
TimeSpan[] expected =
|
|
[
|
|
TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(12), TimeSpan.FromSeconds(24)
|
|
];
|
|
|
|
Assert.Equal(expected, Email.RetryDelays(5, 3));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(int.MinValue)]
|
|
[InlineData(0)]
|
|
[InlineData(1)]
|
|
public void FewerThanTwoAttemptsNeverWait(int attempts)
|
|
{
|
|
Assert.Empty(Email.RetryDelays(attempts, 3));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(8)]
|
|
[InlineData(int.MaxValue)]
|
|
public void RetryWaitsNeverOverflowAndStopDoublingAtAnHour(int firstDelaySeconds)
|
|
{
|
|
var delays = Email.RetryDelays(64, firstDelaySeconds);
|
|
|
|
Assert.All(delays, delay => Assert.InRange(delay, TimeSpan.Zero, TimeSpan.FromHours(1)));
|
|
Assert.Equal(TimeSpan.FromHours(1), delays[^1]);
|
|
}
|
|
}
|