Not-found lookups return null/false instead of throwing NotFoundException across all services — a missing row is expected control flow, not an exceptional condition. NotFoundException stays for embedded precondition checks inside mutations (missing parent, invalid foreign reference). Guard (copied from mic-check) enforces required arguments at the top of every service method. A ported IModelValidator<T> framework validates every request DTO at the API layer via a new ValidationEndpointFilter, returning a 400 with field-level messages; services re-run the same validator and throw for direct callers that bypass the API. Endpoints translate null/false into 404 via a new ToApiResult() helper. The agent toolset boundary translates the same nullable/bool results into the tool-error text the model already expected.
83 lines
4.0 KiB
C#
83 lines
4.0 KiB
C#
using Novelly.Api.Beats;
|
|
using Novelly.Api.Chapters;
|
|
using Novelly.Api.Characters;
|
|
using Novelly.Api.Projects;
|
|
using Novelly.Api.Questions;
|
|
using Novelly.Api.Scenes;
|
|
using Novelly.Api.Tags;
|
|
|
|
namespace Novelly.Api.Tests;
|
|
|
|
/// <summary>
|
|
/// Shared plumbing for the service tests: a fresh in-memory database and a matching set
|
|
/// of services per test.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The setup lives in <c>[SetUp]</c> rather than a constructor or field initialisers
|
|
/// because NUnit builds one fixture instance for the whole class — anything created once
|
|
/// would leak state from one test into the next.
|
|
/// </remarks>
|
|
public abstract class ServiceTestFixture
|
|
{
|
|
protected TestDatabase Db { get; private set; } = null!;
|
|
protected TagService Tags { get; private set; } = null!;
|
|
protected ProjectService Projects { get; private set; } = null!;
|
|
protected CharacterService Characters { get; private set; } = null!;
|
|
protected ChapterService Chapters { get; private set; } = null!;
|
|
protected SceneService Scenes { get; private set; } = null!;
|
|
protected BeatService Beats { get; private set; } = null!;
|
|
protected CharacterArcService Arcs { get; private set; } = null!;
|
|
protected OpenQuestionService Questions { get; private set; } = null!;
|
|
|
|
protected CapturingLogger<ProjectService> ProjectLogs { get; private set; } = null!;
|
|
protected CapturingLogger<CharacterService> CharacterLogs { get; private set; } = null!;
|
|
protected CapturingLogger<ChapterService> ChapterLogs { get; private set; } = null!;
|
|
protected CapturingLogger<SceneService> SceneLogs { get; private set; } = null!;
|
|
protected CapturingLogger<BeatService> BeatLogs { get; private set; } = null!;
|
|
protected CapturingLogger<TagService> TagLogs { get; private set; } = null!;
|
|
protected CapturingLogger<CharacterArcService> ArcLogs { get; private set; } = null!;
|
|
protected CapturingLogger<OpenQuestionService> QuestionLogs { get; private set; } = null!;
|
|
|
|
[SetUp]
|
|
public void SetUpFixture()
|
|
{
|
|
Db = new TestDatabase();
|
|
|
|
TagLogs = new CapturingLogger<TagService>();
|
|
ProjectLogs = new CapturingLogger<ProjectService>();
|
|
CharacterLogs = new CapturingLogger<CharacterService>();
|
|
ChapterLogs = new CapturingLogger<ChapterService>();
|
|
SceneLogs = new CapturingLogger<SceneService>();
|
|
BeatLogs = new CapturingLogger<BeatService>();
|
|
ArcLogs = new CapturingLogger<CharacterArcService>();
|
|
QuestionLogs = new CapturingLogger<OpenQuestionService>();
|
|
|
|
Tags = new TagService(Db.Context, TagLogs, new CreateTagRequestValidator(), new UpdateTagRequestValidator());
|
|
Projects = new ProjectService(Db.Context, ProjectLogs, new CreateProjectRequestValidator(), new UpdateProjectRequestValidator());
|
|
Characters = new CharacterService(
|
|
Db.Context, Tags, CharacterLogs,
|
|
new CreateCharacterRequestValidator(), new UpdateCharacterRequestValidator(), new CreateRelationshipRequestValidator());
|
|
Chapters = new ChapterService(Db.Context, Tags, ChapterLogs, new CreateChapterRequestValidator(), new UpdateChapterRequestValidator());
|
|
Scenes = new SceneService(Db.Context, SceneLogs, new CreateSceneRequestValidator(), new UpdateSceneRequestValidator());
|
|
Beats = new BeatService(
|
|
Db.Context, Tags, BeatLogs,
|
|
new CreateBeatRequestValidator(), new UpdateBeatRequestValidator(), new ReorderBeatsRequestValidator());
|
|
Arcs = new CharacterArcService(
|
|
Db.Context, ArcLogs,
|
|
new CreateArcStageRequestValidator(), new UpdateArcStageRequestValidator(), new ReorderArcStagesRequestValidator());
|
|
Questions = new OpenQuestionService(
|
|
Db.Context, QuestionLogs,
|
|
new CreateOpenQuestionRequestValidator(), new UpdateOpenQuestionRequestValidator(), new ResolveOpenQuestionRequestValidator());
|
|
|
|
OnSetUp();
|
|
}
|
|
|
|
/// <summary>Runs after the services exist, for per-class seed data.</summary>
|
|
protected virtual void OnSetUp()
|
|
{
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDownFixture() => Db.Dispose();
|
|
}
|