fix(email): gate draft attempt rotation
Rotate persisted delivery identity only after the matching owner attempt is definitively failed. Keep stale, foreign, pending, sent, and uncertain drafts non-retryable.
This commit is contained in:
@@ -105,6 +105,42 @@ public sealed class EmailDraftsControllerTests
|
||||
Assert.Equal("user-2", remaining.OwnerUserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task New_attempt_identity_requires_the_matching_definitive_failure()
|
||||
{
|
||||
await using var fixture = await Fixture.CreateAsync(seedDrafts: true);
|
||||
await using var db = fixture.Context("user-1");
|
||||
var controller = Controller(db, "user-1");
|
||||
var original = await db.EmailDrafts.AsNoTracking().SingleAsync();
|
||||
|
||||
var refused = await controller.NewAttempt(original.Id, new EmailDraftsController.NewAttemptRequest(1), default);
|
||||
Assert.IsType<ConflictObjectResult>(refused.Result);
|
||||
db.EmailSendAttempts.Add(new EmailSendAttempt
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OwnerUserId = "user-1",
|
||||
JobApplicationId = original.JobApplicationId,
|
||||
Provider = original.Provider,
|
||||
ClientRequestId = original.ClientRequestId,
|
||||
PayloadHash = new string('a', 64),
|
||||
Status = EmailSendStatuses.Failed,
|
||||
FailureCategory = "provider_rejected",
|
||||
CreatedAtUtc = DateTime.UtcNow,
|
||||
CompletedAtUtc = DateTime.UtcNow,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var rotatedResult = await controller.NewAttempt(original.Id, new EmailDraftsController.NewAttemptRequest(1), default);
|
||||
var rotated = Assert.IsType<EmailDraftsController.DraftDto>(Assert.IsType<OkObjectResult>(rotatedResult.Result).Value);
|
||||
Assert.Equal(2, rotated.Revision);
|
||||
Assert.NotEqual(original.ClientRequestId, rotated.ClientRequestId);
|
||||
Assert.True(Guid.TryParse(rotated.ClientRequestId, out _));
|
||||
|
||||
Assert.IsType<ConflictObjectResult>((await controller.NewAttempt(original.Id, new EmailDraftsController.NewAttemptRequest(1), default)).Result);
|
||||
await using var otherDb = fixture.Context("user-2");
|
||||
Assert.IsType<NotFoundResult>((await Controller(otherDb, "user-2").NewAttempt(original.Id, new EmailDraftsController.NewAttemptRequest(2), default)).Result);
|
||||
}
|
||||
|
||||
private static EmailDraftsController Controller(JobTrackerContext db, string userId)
|
||||
{
|
||||
var controller = new EmailDraftsController(
|
||||
|
||||
Reference in New Issue
Block a user