using FictionArchive.Common.Enums; using FictionArchive.Service.NovelService.Sagas; using FictionArchive.Service.Shared.Contracts.Events; using FluentAssertions; using MassTransit; using MassTransit.Testing; using Microsoft.Extensions.DependencyInjection; using NodaTime; using NodaTime.Testing; using Xunit; namespace FictionArchive.Service.NovelService.Tests.Sagas; public class NovelImportSagaTests { private readonly FakeClock _clock = new(Instant.FromUtc(2026, 1, 27, 12, 0, 0)); [Fact] public async Task Should_transition_to_importing_on_import_requested() { await using var provider = CreateTestProvider(); var harness = provider.GetRequiredService(); await harness.Start(); var importId = Guid.NewGuid(); await harness.Bus.Publish(new NovelImportRequested(importId, "https://example.com/novel")); var sagaHarness = harness.GetSagaStateMachineHarness(); (await sagaHarness.Exists(importId, x => x.Importing)).HasValue.Should().BeTrue(); } [Fact] public async Task Should_transition_to_completed_when_no_chapters() { await using var provider = CreateTestProvider(); var harness = provider.GetRequiredService(); await harness.Start(); var importId = Guid.NewGuid(); await harness.Bus.Publish(new NovelImportRequested(importId, "https://example.com/novel")); await harness.Bus.Publish(new NovelMetadataImported(importId, 1, 0)); var sagaHarness = harness.GetSagaStateMachineHarness(); (await sagaHarness.Exists(importId, x => x.Completed)).HasValue.Should().BeTrue(); (await harness.Published.Any(x => x.Context.Message.ImportId == importId && x.Context.Message.Success)).Should().BeTrue(); } [Fact] public async Task Should_transition_to_processing_when_chapters_pending() { await using var provider = CreateTestProvider(); var harness = provider.GetRequiredService(); await harness.Start(); var importId = Guid.NewGuid(); await harness.Bus.Publish(new NovelImportRequested(importId, "https://example.com/novel")); await harness.Bus.Publish(new NovelMetadataImported(importId, 1, 2)); var sagaHarness = harness.GetSagaStateMachineHarness(); (await sagaHarness.Exists(importId, x => x.Processing)).HasValue.Should().BeTrue(); } [Fact] public async Task Should_complete_when_all_chapters_pulled_and_images_uploaded() { await using var provider = CreateTestProvider(); var harness = provider.GetRequiredService(); await harness.Start(); var importId = Guid.NewGuid(); await harness.Bus.Publish(new NovelImportRequested(importId, "https://example.com/novel")); await harness.Bus.Publish(new NovelMetadataImported(importId, 1, 2)); await harness.Bus.Publish(new ChapterPullCompleted(importId, 1, 1)); await harness.Bus.Publish(new ChapterPullCompleted(importId, 2, 0)); await harness.Bus.Publish(new FileUploadRequestStatusUpdate( importId, Guid.NewGuid(), RequestStatus.Success, "https://cdn.example.com/image.jpg", null)); var sagaHarness = harness.GetSagaStateMachineHarness(); (await sagaHarness.Exists(importId, x => x.Completed)).HasValue.Should().BeTrue(); } private ServiceProvider CreateTestProvider() { return new ServiceCollection() .AddSingleton(_clock) .AddMassTransitTestHarness(cfg => { cfg.AddSagaStateMachine() .InMemoryRepository(); }) .BuildServiceProvider(true); } }