[FA-misc] Saga seems to work, fixed a UserNovelDataService bug
This commit is contained in:
@@ -9,8 +9,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="MassTransit" Version="8.5.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.11" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="NodaTime.Testing" Version="3.3.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
|
||||
@@ -14,6 +14,7 @@ using MassTransit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NodaTime;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
@@ -80,7 +81,10 @@ public class NovelUpdateServiceTests
|
||||
PendingImageUrl = pendingImageUrl
|
||||
});
|
||||
|
||||
return new NovelUpdateService(dbContext, NullLogger<NovelUpdateService>.Instance, new[] { adapter }, publishEndpoint, options);
|
||||
var clock = Substitute.For<IClock>();
|
||||
clock.GetCurrentInstant().Returns(Instant.FromUnixTimeSeconds(0));
|
||||
|
||||
return new NovelUpdateService(dbContext, NullLogger<NovelUpdateService>.Instance, new[] { adapter }, publishEndpoint, options, clock);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -110,8 +114,10 @@ public class NovelUpdateServiceTests
|
||||
var pendingImageUrl = "https://pending/placeholder.jpg";
|
||||
var service = CreateService(dbContext, adapter, publishEndpoint, pendingImageUrl);
|
||||
|
||||
var updatedChapter = await service.PullChapterContents(novel.Id, volume.Id, chapter.Order);
|
||||
var importId = Guid.NewGuid();
|
||||
var (updatedChapter, imageCount) = await service.PullChapterContents(importId, novel.Id, volume.Id, chapter.Order);
|
||||
|
||||
imageCount.Should().Be(2);
|
||||
updatedChapter.Images.Should().HaveCount(2);
|
||||
updatedChapter.Images.Select(i => i.OriginalPath).Should().BeEquivalentTo(new[] { image1.Url, image2.Url });
|
||||
updatedChapter.Images.All(i => i.Id != Guid.Empty).Should().BeTrue();
|
||||
@@ -128,9 +134,10 @@ public class NovelUpdateServiceTests
|
||||
.BeEquivalentTo(updatedChapter.Images.Select(img => img.Id.ToString()));
|
||||
|
||||
publishedEvents.Should().HaveCount(2);
|
||||
publishedEvents.Should().OnlyContain(e => e.ImportId == importId);
|
||||
publishedEvents.Select(e => e.RequestId).Should().BeEquivalentTo(updatedChapter.Images.Select(i => i.Id));
|
||||
publishedEvents.Select(e => e.FileData).Should().BeEquivalentTo(new[] { image1.Data, image2.Data });
|
||||
publishedEvents.Should().OnlyContain(e => e.FilePath.StartsWith($"{novel.Id}/Images/Chapter-{updatedChapter.Id}/"));
|
||||
publishedEvents.Should().OnlyContain(e => e.FilePath.StartsWith($"Novels/{novel.Id}/Images/Chapter-{updatedChapter.Id}/"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -155,8 +162,10 @@ public class NovelUpdateServiceTests
|
||||
|
||||
var service = CreateService(dbContext, adapter, publishEndpoint);
|
||||
|
||||
var updatedChapter = await service.PullChapterContents(novel.Id, volume.Id, chapter.Order);
|
||||
var importId = Guid.NewGuid();
|
||||
var (updatedChapter, imageCount) = await service.PullChapterContents(importId, novel.Id, volume.Id, chapter.Order);
|
||||
|
||||
imageCount.Should().Be(1);
|
||||
var storedHtml = updatedChapter.Body.Texts.Single().Text;
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(storedHtml);
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
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<ITestHarness>();
|
||||
await harness.Start();
|
||||
|
||||
var importId = Guid.NewGuid();
|
||||
await harness.Bus.Publish<INovelImportRequested>(new NovelImportRequested(importId, "https://example.com/novel"));
|
||||
|
||||
var sagaHarness = harness.GetSagaStateMachineHarness<NovelImportSaga, NovelImportSagaState>();
|
||||
(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<ITestHarness>();
|
||||
await harness.Start();
|
||||
|
||||
var importId = Guid.NewGuid();
|
||||
await harness.Bus.Publish<INovelImportRequested>(new NovelImportRequested(importId, "https://example.com/novel"));
|
||||
await harness.Bus.Publish<INovelMetadataImported>(new NovelMetadataImported(importId, 1, 0));
|
||||
|
||||
var sagaHarness = harness.GetSagaStateMachineHarness<NovelImportSaga, NovelImportSagaState>();
|
||||
(await sagaHarness.Exists(importId, x => x.Completed)).HasValue.Should().BeTrue();
|
||||
|
||||
(await harness.Published.Any<INovelImportCompleted>(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<ITestHarness>();
|
||||
await harness.Start();
|
||||
|
||||
var importId = Guid.NewGuid();
|
||||
await harness.Bus.Publish<INovelImportRequested>(new NovelImportRequested(importId, "https://example.com/novel"));
|
||||
await harness.Bus.Publish<INovelMetadataImported>(new NovelMetadataImported(importId, 1, 2));
|
||||
|
||||
var sagaHarness = harness.GetSagaStateMachineHarness<NovelImportSaga, NovelImportSagaState>();
|
||||
(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<ITestHarness>();
|
||||
await harness.Start();
|
||||
|
||||
var importId = Guid.NewGuid();
|
||||
await harness.Bus.Publish<INovelImportRequested>(new NovelImportRequested(importId, "https://example.com/novel"));
|
||||
await harness.Bus.Publish<INovelMetadataImported>(new NovelMetadataImported(importId, 1, 2));
|
||||
await harness.Bus.Publish<IChapterPullCompleted>(new ChapterPullCompleted(importId, 1, 1));
|
||||
await harness.Bus.Publish<IChapterPullCompleted>(new ChapterPullCompleted(importId, 2, 0));
|
||||
await harness.Bus.Publish<IFileUploadRequestStatusUpdate>(new FileUploadRequestStatusUpdate(
|
||||
importId, Guid.NewGuid(), RequestStatus.Success, "https://cdn.example.com/image.jpg", null));
|
||||
|
||||
var sagaHarness = harness.GetSagaStateMachineHarness<NovelImportSaga, NovelImportSagaState>();
|
||||
(await sagaHarness.Exists(importId, x => x.Completed)).HasValue.Should().BeTrue();
|
||||
}
|
||||
|
||||
private ServiceProvider CreateTestProvider()
|
||||
{
|
||||
return new ServiceCollection()
|
||||
.AddSingleton<IClock>(_clock)
|
||||
.AddMassTransitTestHarness(cfg =>
|
||||
{
|
||||
cfg.AddSagaStateMachine<NovelImportSaga, NovelImportSagaState>()
|
||||
.InMemoryRepository();
|
||||
})
|
||||
.BuildServiceProvider(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user