diff --git a/SeaHavenIndustries.Tests/UpliftDecisionAuditRelationalTests.cs b/SeaHavenIndustries.Tests/UpliftDecisionAuditRelationalTests.cs index b95ffbd..fb07d09 100644 --- a/SeaHavenIndustries.Tests/UpliftDecisionAuditRelationalTests.cs +++ b/SeaHavenIndustries.Tests/UpliftDecisionAuditRelationalTests.cs @@ -162,10 +162,61 @@ public sealed class UpliftDecisionAuditRelationalTests Assert.Empty(audits); } + [Theory] + [InlineData(Ownership.Linked)] + [InlineData(Ownership.Unresolvable)] + public async Task Escalation_CommitsAndAuditsResolvedWorkOrder(Ownership ownership) + { + await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True"); + await connection.OpenAsync(); + var options = new DbContextOptionsBuilder().UseSqlite(connection).Options; + + int upliftId; + int? expectedWorkOrderId; + await using (var seed = new SqliteUpliftDecisionTestDbContext(options)) + { + await seed.Database.EnsureCreatedAsync(); + (upliftId, expectedWorkOrderId) = await SeedAsync( + seed, + ownership, + expiresAt: null, + initialNotificationSentAt: DateTime.UtcNow.AddDays(-3)); + } + + var emailSender = new NoEmailSender(); + await using (var context = new SqliteUpliftDecisionTestDbContext(options)) + { + var lifecycle = new UpliftLifecycleService( + new UpliftDataService(context), + new DispatchDataService(context), + null!, + emailSender, + Options.Create(new FrontendOptions()), + Options.Create(new ApprovalsOptions { EscalationRecipients = new[] { "escalate@test.local" } }), + TimeProvider.System, + NullLogger.Instance); + + Assert.Equal(1, await lifecycle.EscalateDueAsync(CancellationToken.None)); + } + + await using var verify = new SqliteUpliftDecisionTestDbContext(options); + var saved = await verify.DispatchUpliftRequests.AsNoTracking().SingleAsync(u => u.Id == upliftId); + Assert.NotNull(saved.EscalatedAt); + Assert.Equal(1, emailSender.Calls); + + var audits = await verify.WorkOrderAuditLogs.AsNoTracking().ToListAsync(); + Assert.DoesNotContain(audits, a => a.WorkOrderId == 0); + if (expectedWorkOrderId is int workOrderId) + Assert.Equal(workOrderId, Assert.Single(audits).WorkOrderId); + else + Assert.Empty(audits); + } + private static async Task<(int UpliftId, int? ExpectedWorkOrderId)> SeedAsync( ApplicationDbContext context, Ownership ownership, - DateTime? expiresAt) + DateTime? expiresAt, + DateTime? initialNotificationSentAt = null) { context.Users.Add(new ApplicationUser { @@ -218,7 +269,8 @@ public sealed class UpliftDecisionAuditRelationalTests RequiredTier = 2, CreatedDate = DateTime.UtcNow.AddDays(-4), ExpiresAt = expiresAt, - NotificationStatus = "Pending" + InitialNotificationSentAt = initialNotificationSentAt, + NotificationStatus = initialNotificationSentAt == null ? "Pending" : "Sent" }; context.DispatchUpliftRequests.Add(request); await context.SaveChangesAsync(); @@ -238,9 +290,16 @@ public sealed class UpliftDecisionAuditRelationalTests => throw new NotSupportedException(); } + // Records sends without delivering anything. private sealed class NoEmailSender : IEmailSender { - public Task SendEmailAsync(string emailTo, string subject, string body) => Task.FromResult(true); + public int Calls; + + public Task SendEmailAsync(string emailTo, string subject, string body) + { + Calls++; + return Task.FromResult(true); + } } private sealed class SqliteUpliftDecisionTestDbContext : ApplicationDbContext