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; /// /// Covers the logging behaviour added across the services: a warning fires before a /// not-found is thrown, and prose bodies never leak into a log message. /// [TestFixture] public class LoggingTests : ServiceTestFixture { [Test] public void Fetching_a_missing_chapter_logs_a_warning_before_throwing() { var missingId = Guid.NewGuid(); Assert.That(() => Chapters.GetAsync(missingId), Throws.TypeOf()); var warning = ChapterLogs.Entries.Single(e => e.Level == LogLevel.Warning); Assert.That(warning.Message, Does.Contain(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(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", CharacterId: foreignCharacter.Id)), Throws.TypeOf()); 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); }); } }