using Microsoft.EntityFrameworkCore; using Novelly.Api.Chapters; using Novelly.Api.Characters; using Novelly.Api.Common; using Novelly.Api.Novels; namespace Novelly.Api.Tests; [TestFixture] public class NovelDataTests : ServiceTestFixture { private async Task NewNovelAsync() => (await Novels.CreateAsync(new CreateNovelRequest("The Salt Road"))).Id; [Test] public async Task Updating_leaves_omitted_fields_alone_and_clears_on_empty_string() { var id = (await Novels.CreateAsync( new CreateNovelRequest("Draft", Genre: "Fantasy", Logline: "A cartographer goes to sea."))).Id; var afterPartialUpdate = (await Novels.UpdateAsync(id, new UpdateNovelRequest(Title: "The Salt Road")))!; Assert.Multiple(() => { Assert.That(afterPartialUpdate.Title, Is.EqualTo("The Salt Road")); Assert.That(afterPartialUpdate.Genre, Is.EqualTo("Fantasy")); Assert.That(afterPartialUpdate.Logline, Is.EqualTo("A cartographer goes to sea.")); }); var afterClear = (await Novels.UpdateAsync(id, new UpdateNovelRequest(Genre: "")))!; Assert.Multiple(() => { Assert.That(afterClear.Genre, Is.Null); Assert.That(afterClear.Logline, Is.EqualTo("A cartographer goes to sea.")); }); } [Test] public async Task New_novels_start_in_the_brainstorming_phase_and_can_be_advanced() { var novel = await Novels.CreateAsync(new CreateNovelRequest("The Salt Road")); Assert.That(novel.Phase, Is.EqualTo(NovelPhase.Brainstorming)); var afterAdvance = (await Novels.UpdateAsync(novel.Id, new UpdateNovelRequest(Phase: NovelPhase.Outlining)))!; Assert.That(afterAdvance.Phase, Is.EqualTo(NovelPhase.Outlining)); var afterUnrelatedUpdate = (await Novels.UpdateAsync(novel.Id, new UpdateNovelRequest(Genre: "Fantasy")))!; Assert.That(afterUnrelatedUpdate.Phase, Is.EqualTo(NovelPhase.Outlining)); } [Test] public async Task A_novel_response_carries_the_owner_id_and_the_caller_s_role() { var novel = await Novels.CreateAsync(new CreateNovelRequest("The Salt Road")); var response = novel.ToResponse(await Access.GetMyRoleAsync(novel)); Assert.Multiple(() => { Assert.That(response.OwnerId, Is.EqualTo(UserContext.UserId)); Assert.That(response.MyRole, Is.EqualTo("Admin")); }); } [Test] public async Task Chapters_are_numbered_in_sequence_when_no_number_is_given() { var novelId = await NewNovelAsync(); var first = await Chapters.CreateAsync(novelId, new CreateChapterRequest("Landfall")); var second = await Chapters.CreateAsync(novelId, new CreateChapterRequest("The Harbour")); Assert.Multiple(() => { Assert.That(first.Number, Is.EqualTo(1)); Assert.That(second.Number, Is.EqualTo(2)); }); } [Test] public async Task Word_count_is_recomputed_whenever_prose_changes() { var novelId = await NewNovelAsync(); var chapter = await Chapters.CreateAsync(novelId, new CreateChapterRequest( "Landfall", Prose: "Five words go right here")); Assert.That(chapter.WordCount, Is.EqualTo(5)); var rewritten = (await Chapters.UpdateAsync(chapter.Id, new UpdateChapterRequest( Prose: "Now\nthere are seven words in total")))!; Assert.That(rewritten.WordCount, Is.EqualTo(7)); var cleared = (await Chapters.UpdateAsync(chapter.Id, new UpdateChapterRequest(Prose: "")))!; Assert.Multiple(() => { Assert.That(cleared.Prose, Is.Null); Assert.That(cleared.WordCount, Is.EqualTo(0)); }); } [Test] public async Task Chapter_updates_that_omit_prose_leave_the_draft_untouched() { var novelId = await NewNovelAsync(); var chapter = await Chapters.CreateAsync(novelId, new CreateChapterRequest( "Landfall", Prose: "The tide came in slow.")); var updated = (await Chapters.UpdateAsync(chapter.Id, new UpdateChapterRequest(Status: DraftStatus.Revised)))!; Assert.Multiple(() => { Assert.That(updated.Prose, Is.EqualTo("The tide came in slow.")); Assert.That(updated.WordCount, Is.EqualTo(5)); Assert.That(updated.Status, Is.EqualTo(DraftStatus.Revised)); }); } [Test] public async Task Deleting_a_novel_takes_its_characters_and_chapters() { var novelId = await NewNovelAsync(); await Characters.CreateAsync(novelId, new CreateCharacterRequest("Ines")); await Chapters.CreateAsync(novelId, new CreateChapterRequest("Landfall")); await Novels.DeleteAsync(novelId); using var verification = Db.CreateContext(); Assert.Multiple(async () => { Assert.That(await verification.Novels.CountAsync(), Is.EqualTo(0)); Assert.That(await verification.Characters.CountAsync(), Is.EqualTo(0)); Assert.That(await verification.Chapters.CountAsync(), Is.EqualTo(0)); }); } [Test] public async Task Relating_characters_across_novels_is_refused() { var firstNovel = await NewNovelAsync(); var secondNovel = (await Novels.CreateAsync(new CreateNovelRequest("Other Book"))).Id; var ines = await Characters.CreateAsync(firstNovel, new CreateCharacterRequest("Ines")); var stranger = await Characters.CreateAsync(secondNovel, new CreateCharacterRequest("Stranger")); Assert.That( async () => await Characters.AddRelationshipAsync( ines.Id, new CreateRelationshipRequest(stranger.Id, "sister")), Throws.TypeOf().With.Message.Contains("same novel")); } [Test] public async Task Relationships_resolve_the_other_character_by_name() { var novelId = await NewNovelAsync(); var ines = await Characters.CreateAsync(novelId, new CreateCharacterRequest("Ines")); var mara = await Characters.CreateAsync(novelId, new CreateCharacterRequest("Mara")); var updated = (await Characters.AddRelationshipAsync( ines.Id, new CreateRelationshipRequest(mara.Id, "sister", "Estranged since the fire.")))!; Assert.Multiple(() => { Assert.That(updated.Relationships, Has.Count.EqualTo(1)); Assert.That(updated.Relationships[0].RelatedCharacter!.Name, Is.EqualTo("Mara")); }); } [Test] public async Task Reading_a_missing_novel_returns_null_rather_than_throwing() => Assert.That(await Novels.GetAsync(Guid.NewGuid()), Is.Null); }