Files
novelly/tests/Novelly.Api.Tests/LoggingTests.cs
T
James Wampler 23348327a9 Remove Scenes, group beats by multiple characters; strip comments repo-wide
Drop the Scene entity/grouping in favor of chapters carrying prose directly
and beats belonging to many characters. Add markdown editor + character
multi-select components to the web client. Remove all XML doc and inline
comments across the touched C#/TS/CSS files in favor of self-documenting
names, and record that convention in CLAUDE.md. Add .mcp.json (local MCP
server config, no secrets) and ignore .idea/.
2026-08-11 21:05:13 -07:00

91 lines
3.4 KiB
C#

using Microsoft.Extensions.Logging;
using Novelly.Api.Beats;
using Novelly.Api.Chapters;
using Novelly.Api.Characters;
using Novelly.Api.Common;
using Novelly.Api.Projects;
namespace Novelly.Api.Tests;
[TestFixture]
public class LoggingTests : ServiceTestFixture
{
[Test]
public async Task Fetching_a_missing_chapter_returns_null_and_logs_at_information_not_warning()
{
var missingId = Guid.NewGuid();
var result = await Chapters.GetAsync(missingId);
Assert.Multiple(() =>
{
Assert.That(result, Is.Null);
Assert.That(ChapterLogs.Entries.Where(e => e.Level == LogLevel.Warning), Is.Empty);
Assert.That(
ChapterLogs.Entries,
Has.Some.Matches<CapturedLogEntry>(e => e.Level == LogLevel.Information && e.Message.Contains(missingId.ToString())));
});
}
[Test]
public async Task Creating_a_chapter_logs_the_project_and_title_at_information()
{
var project = await Projects.CreateAsync(new CreateProjectRequest("The Salt Road"));
ChapterLogs.Entries.Clear();
await Chapters.CreateAsync(project.Id, new CreateChapterRequest("Landfall"));
var info = ChapterLogs.Entries.Single(e => e.Level == LogLevel.Information);
Assert.Multiple(() =>
{
Assert.That(info.Message, Does.Contain("Landfall"));
Assert.That(info.Message, Does.Contain(project.Id.ToString()));
});
}
[Test]
public async Task Logged_values_never_include_a_chapter_summary_body()
{
var project = await Projects.CreateAsync(new CreateProjectRequest("The Salt Road"));
const string secretSummary = "A very specific plot twist nobody should see in a log line.";
ChapterLogs.Entries.Clear();
await Chapters.CreateAsync(project.Id, new CreateChapterRequest("Landfall", Summary: secretSummary));
Assert.That(ChapterLogs.Entries.Select(e => e.Message), Has.None.Contain(secretSummary));
}
[Test]
public async Task Deleting_a_project_logs_information_before_the_lookup()
{
var project = await Projects.CreateAsync(new CreateProjectRequest("The Salt Road"));
ProjectLogs.Entries.Clear();
await Projects.DeleteAsync(project.Id);
Assert.That(
ProjectLogs.Entries,
Has.Some.Matches<CapturedLogEntry>(e => e.Level == LogLevel.Information && e.Message.Contains(project.Id.ToString())));
}
[Test]
public async Task Rejecting_a_beat_with_a_foreign_character_logs_a_warning_not_an_error()
{
var projectA = await Projects.CreateAsync(new CreateProjectRequest("Project A"));
var projectB = await Projects.CreateAsync(new CreateProjectRequest("Project B"));
var chapter = await Chapters.CreateAsync(projectA.Id, new CreateChapterRequest("Landfall"));
var foreignCharacter = await Characters.CreateAsync(projectB.Id, new CreateCharacterRequest("Ines"));
BeatLogs.Entries.Clear();
Assert.That(
() => Beats.CreateAsync(chapter.Id, new CreateBeatRequest("Arrival", CharacterIds: [foreignCharacter.Id])),
Throws.TypeOf<InvalidOperationException>());
Assert.Multiple(() =>
{
Assert.That(BeatLogs.Entries.Where(e => e.Level == LogLevel.Error), Is.Empty);
Assert.That(BeatLogs.Entries.Any(e => e.Level == LogLevel.Warning), Is.True);
});
}
}