fix(store-and-forward): resolve StoreAndForward-004,005,010,013 — accurate handler-contract doc, conditional sweep writes, reset LastAttemptAt on parked retry, test coverage
This commit is contained in:
@@ -106,6 +106,35 @@ public class StoreAndForwardStorageTests : IAsyncLifetime, IDisposable
|
||||
Assert.All(forRetry, m => Assert.Equal(StoreAndForwardMessageStatus.Pending, m.Status));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetMessagesForRetryAsync_NonZeroInterval_ExcludesNotYetDueIncludesDue()
|
||||
{
|
||||
// StoreAndForward-013: exercise the julianday elapsed-time comparison with a
|
||||
// non-zero retry interval. A message attempted just now must NOT be due; one
|
||||
// attempted long ago must be due.
|
||||
var notDue = CreateMessage("notdue", StoreAndForwardCategory.ExternalSystem);
|
||||
notDue.RetryIntervalMs = (long)TimeSpan.FromHours(1).TotalMilliseconds;
|
||||
notDue.LastAttemptAt = DateTimeOffset.UtcNow;
|
||||
await _storage.EnqueueAsync(notDue);
|
||||
|
||||
var due = CreateMessage("due", StoreAndForwardCategory.ExternalSystem);
|
||||
due.RetryIntervalMs = (long)TimeSpan.FromMinutes(5).TotalMilliseconds;
|
||||
due.LastAttemptAt = DateTimeOffset.UtcNow.AddHours(-2);
|
||||
await _storage.EnqueueAsync(due);
|
||||
|
||||
var neverAttempted = CreateMessage("never", StoreAndForwardCategory.ExternalSystem);
|
||||
neverAttempted.RetryIntervalMs = (long)TimeSpan.FromHours(1).TotalMilliseconds;
|
||||
neverAttempted.LastAttemptAt = null;
|
||||
await _storage.EnqueueAsync(neverAttempted);
|
||||
|
||||
var forRetry = await _storage.GetMessagesForRetryAsync();
|
||||
var ids = forRetry.Select(m => m.Id).ToHashSet();
|
||||
|
||||
Assert.DoesNotContain("notdue", ids);
|
||||
Assert.Contains("due", ids);
|
||||
Assert.Contains("never", ids);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetParkedMessagesAsync_ReturnsParkedOnly()
|
||||
{
|
||||
@@ -136,6 +165,31 @@ public class StoreAndForwardStorageTests : IAsyncLifetime, IDisposable
|
||||
Assert.Equal(0, retrieved.RetryCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetryParkedMessageAsync_ClearsLastAttemptAt_SoMessageIsImmediatelyDue()
|
||||
{
|
||||
// StoreAndForward-010: a re-queued parked message must be unambiguously due
|
||||
// for the next sweep regardless of its (stale) last_attempt_at. Use a large
|
||||
// retry interval so a leftover timestamp would otherwise exclude the message.
|
||||
var msg = CreateMessage("requeue1", StoreAndForwardCategory.ExternalSystem);
|
||||
msg.RetryIntervalMs = (long)TimeSpan.FromHours(1).TotalMilliseconds;
|
||||
msg.LastAttemptAt = DateTimeOffset.UtcNow; // recent attempt
|
||||
msg.Status = StoreAndForwardMessageStatus.Parked;
|
||||
await _storage.EnqueueAsync(msg);
|
||||
await _storage.UpdateMessageAsync(msg);
|
||||
|
||||
var requeued = await _storage.RetryParkedMessageAsync("requeue1");
|
||||
Assert.True(requeued);
|
||||
|
||||
var retrieved = await _storage.GetMessageByIdAsync("requeue1");
|
||||
Assert.Null(retrieved!.LastAttemptAt);
|
||||
|
||||
// It must appear in the retry-due set even though the configured interval
|
||||
// (1 hour) has not elapsed since the original attempt.
|
||||
var due = await _storage.GetMessagesForRetryAsync();
|
||||
Assert.Contains(due, m => m.Id == "requeue1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DiscardParkedMessageAsync_RemovesMessage()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user