using AxCopilot.Services; using FluentAssertions; using Xunit; namespace AxCopilot.Tests.Services; public class DraftQueueProcessorServiceTests { [Fact] public void TryStartNext_PicksPreferredDraftAndMarksItRunning() { var session = new ChatSessionStateService(); var processor = new DraftQueueProcessorService(); var first = session.EnqueueDraft("Chat", "first", "next"); var second = session.EnqueueDraft("Chat", "second", "later"); var started = processor.TryStartNext(session, "Chat", preferredDraftId: second!.Id); started.Should().NotBeNull(); started!.Id.Should().Be(second.Id); session.GetDraftQueueItems("Chat").Single(x => x.Id == second.Id).State.Should().Be("running"); session.GetDraftQueueItems("Chat").Single(x => x.Id == first!.Id).State.Should().Be("queued"); } [Fact] public void HandleFailure_SchedulesRetryBeforeRetryLimit() { var session = new ChatSessionStateService(); var processor = new DraftQueueProcessorService(); var item = session.EnqueueDraft("Chat", "retry me", "next"); processor.TryStartNext(session, "Chat", preferredDraftId: item!.Id); var handled = processor.HandleFailure(session, "Chat", item.Id, "boom", cancelled: false, maxAutoRetries: 3); handled.Should().BeTrue(); var stored = session.GetDraftQueueItems("Chat").Single(x => x.Id == item.Id); stored.State.Should().Be("queued"); stored.NextRetryAt.Should().NotBeNull(); } [Fact] public void PromoteReadyBlockedItems_ClearsExpiredRetryWindow() { var session = new ChatSessionStateService(); var processor = new DraftQueueProcessorService(); var item = session.EnqueueDraft("Chat", "retry me", "next"); processor.TryStartNext(session, "Chat", preferredDraftId: item!.Id); processor.HandleFailure(session, "Chat", item.Id, "boom", cancelled: false, maxAutoRetries: 3); session.CurrentConversation!.DraftQueueItems[0].NextRetryAt = DateTime.Now.AddSeconds(-1); var promoted = processor.PromoteReadyBlockedItems(session, "Chat"); promoted.Should().Be(1); session.CurrentConversation!.DraftQueueItems[0].NextRetryAt.Should().BeNull(); } [Fact] public void ClearCompleted_RemovesCompletedItems() { var session = new ChatSessionStateService(); var processor = new DraftQueueProcessorService(); var item = session.EnqueueDraft("Chat", "done item", "next"); processor.TryStartNext(session, "Chat", preferredDraftId: item!.Id); processor.Complete(session, "Chat", item.Id); var removed = processor.ClearCompleted(session, "Chat"); removed.Should().Be(1); session.GetDraftQueueItems("Chat").Should().BeEmpty(); } [Fact] public void TryStartNext_AndHandleFailure_RecordQueueTasks() { var session = new ChatSessionStateService(); var processor = new DraftQueueProcessorService(); var taskRuns = new TaskRunService(); var item = session.EnqueueDraft("Chat", "retry me", "next"); processor.TryStartNext(session, "Chat", preferredDraftId: item!.Id, taskRuns: taskRuns); processor.HandleFailure(session, "Chat", item.Id, "boom", cancelled: false, maxAutoRetries: 3, taskRuns: taskRuns); taskRuns.ActiveTasks.Should().BeEmpty(); taskRuns.RecentTasks.Should().ContainSingle(t => t.Kind == "queue" && t.Status == "blocked"); } }