using Microsoft.EntityFrameworkCore; using Novelly.Api.Chapters; using Novelly.Api.Common; using Novelly.Api.Novels; using Novelly.Api.Users; namespace Novelly.Api.Tests; [TestFixture] public class NovelAccessTests : ServiceTestFixture { private Guid AsNewUser(GlobalRole globalRole) { var user = new NovellyUser { Id = Guid.NewGuid(), UserName = $"{Guid.NewGuid()}@novelly.test", Email = $"{Guid.NewGuid()}@novelly.test", DisplayName = "Test User", GlobalRole = globalRole }; Db.Context.Users.Add(user); Db.Context.SaveChanges(); UserContext.UserId = user.Id; UserContext.GlobalRole = globalRole; return user.Id; } private void GrantNovelRole(Guid novelId, Guid userId, NovelRole role) { Db.Context.NovelMembers.Add(new NovelMember { NovelId = novelId, UserId = userId, NovelRole = role, GrantedByUserId = userId }); Db.Context.SaveChanges(); } private void AsAdmin() { UserContext.UserId = Db.Context.Users.Single(u => u.GlobalRole == GlobalRole.Admin).Id; UserContext.GlobalRole = GlobalRole.Admin; } [Test] public async Task A_writer_sees_only_novels_they_own_or_have_been_granted() { var writerId = AsNewUser(GlobalRole.Writer); var ownedNovel = await Novels.CreateAsync(new CreateNovelRequest("Owned by writer")); AsAdmin(); var otherNovel = await Novels.CreateAsync(new CreateNovelRequest("Owned by someone else")); UserContext.UserId = writerId; UserContext.GlobalRole = GlobalRole.Writer; var visibleBeforeGrant = await Novels.ListAsync(); Assert.That(visibleBeforeGrant.Select(p => p.Id), Is.EquivalentTo(new[] { ownedNovel.Id })); GrantNovelRole(otherNovel.Id, writerId, NovelRole.Reviewer); var visibleAfterGrant = await Novels.ListAsync(); Assert.That(visibleAfterGrant.Select(p => p.Id), Is.EquivalentTo(new[] { ownedNovel.Id, otherNovel.Id })); } [Test] public async Task An_editor_can_rewrite_a_chapter_but_cannot_delete_it() { var novel = await Novels.CreateAsync(new CreateNovelRequest("Editable Novel")); var chapter = await Chapters.CreateAsync(novel.Id, new CreateChapterRequest("Chapter One")); var editorId = AsNewUser(GlobalRole.Reviewer); GrantNovelRole(novel.Id, editorId, NovelRole.Editor); var updated = await Chapters.UpdateAsync(chapter!.Id, new UpdateChapterRequest(Title: "Renamed")); Assert.That(updated!.Title, Is.EqualTo("Renamed")); Assert.That(() => Chapters.DeleteAsync(chapter.Id), Throws.TypeOf()); } [Test] public void An_editor_cannot_create_a_new_novel() { AsNewUser(GlobalRole.Editor); Assert.That(() => Novels.CreateAsync(new CreateNovelRequest("Should not exist")), Throws.TypeOf()); } [Test] public async Task A_reviewer_can_read_a_chapter_but_not_change_it() { var novel = await Novels.CreateAsync(new CreateNovelRequest("Reviewed Novel")); var chapter = await Chapters.CreateAsync(novel.Id, new CreateChapterRequest("Chapter One")); var reviewerId = AsNewUser(GlobalRole.Reviewer); GrantNovelRole(novel.Id, reviewerId, NovelRole.Reviewer); var read = await Chapters.GetAsync(chapter!.Id); Assert.That(read, Is.Not.Null); Assert.That( () => Chapters.UpdateAsync(chapter.Id, new UpdateChapterRequest(Title: "Nope")), Throws.TypeOf()); } [Test] public async Task A_writer_granted_access_to_someone_elses_novel_still_cannot_grant_access_to_others() { var novel = await Novels.CreateAsync(new CreateNovelRequest("Someone Else's Novel")); var grantedWriterId = AsNewUser(GlobalRole.Writer); GrantNovelRole(novel.Id, grantedWriterId, NovelRole.Writer); Assert.That( () => Access.RequireAsync(novel.Id, NovelPermission.ManageAccess), Throws.TypeOf()); } [Test] public async Task An_admin_reaches_every_novel() { AsNewUser(GlobalRole.Writer); await Novels.CreateAsync(new CreateNovelRequest("Writer's Novel")); AsAdmin(); await Novels.CreateAsync(new CreateNovelRequest("Admin's Novel")); var visible = await Novels.ListAsync(); Assert.That(visible, Has.Count.EqualTo(2)); } [Test] public void Deleting_a_user_does_not_cascade_to_their_novels() { var ownerNavigation = Db.Context.Model.FindEntityType(typeof(Novel))!.FindNavigation(nameof(Novel.Owner))!; Assert.That(ownerNavigation.ForeignKey.DeleteBehavior, Is.EqualTo(DeleteBehavior.Restrict)); } [Test] public async Task The_creator_of_a_novel_sees_their_role_as_owner() { var writerId = AsNewUser(GlobalRole.Writer); var novel = await Novels.CreateAsync(new CreateNovelRequest("Owned by writer")); UserContext.UserId = writerId; var role = await Access.GetMyRoleAsync(novel); Assert.That(role, Is.EqualTo("Owner")); } [Test] public async Task An_admin_sees_their_role_as_admin_even_on_a_novel_they_do_not_own() { AsNewUser(GlobalRole.Writer); var novel = await Novels.CreateAsync(new CreateNovelRequest("Owned by someone else")); AsAdmin(); var role = await Access.GetMyRoleAsync(novel); Assert.That(role, Is.EqualTo("Admin")); } [Test] public async Task A_user_granted_editor_sees_their_role_as_editor() { var novel = await Novels.CreateAsync(new CreateNovelRequest("Granted Novel")); var editorId = AsNewUser(GlobalRole.Reviewer); GrantNovelRole(novel.Id, editorId, NovelRole.Editor); var role = await Access.GetMyRoleAsync(novel); Assert.That(role, Is.EqualTo("Editor")); } [Test] public async Task A_user_with_no_access_sees_a_null_role() { var novel = await Novels.CreateAsync(new CreateNovelRequest("Someone Else's Novel")); AsNewUser(GlobalRole.Writer); var role = await Access.GetMyRoleAsync(novel); Assert.That(role, Is.Null); } }