using FluentAssertions; using NovelSoftware.Application; using NovelSoftware.Application.Dtos; using NovelSoftware.Application.Services; using NovelSoftware.Domain; namespace NovelSoftware.Tests; public class OutlineServiceTests : IDisposable { private readonly TestDatabase _db = new(); private readonly OutlineService _outlines; private readonly Guid _projectId; public OutlineServiceTests() { _outlines = new OutlineService(_db.Context); _projectId = new ProjectService(_db.Context) .CreateAsync(new CreateProjectRequest("The Salt Road")).Result.Id; } [Fact] public async Task Nested_nodes_come_back_as_a_tree() { var act = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest( "Act One", OutlineNodeType.Act)); await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest( "She finds the map", OutlineNodeType.Beat, ParentId: act.Id)); await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest( "The harbour burns", OutlineNodeType.Beat, ParentId: act.Id)); var tree = await _outlines.GetTreeAsync(_projectId); tree.Should().ContainSingle(); tree[0].Title.Should().Be("Act One"); tree[0].Children.Select(c => c.Title) .Should().Equal("She finds the map", "The harbour burns"); } [Fact] public async Task Sibling_order_follows_sort_order_not_insertion_order() { await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("Third", SortOrder: 30)); await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("First", SortOrder: 10)); await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("Second", SortOrder: 20)); var tree = await _outlines.GetTreeAsync(_projectId); tree.Select(n => n.Title).Should().Equal("First", "Second", "Third"); } [Fact] public async Task Moving_a_node_under_its_own_descendant_is_rejected() { var act = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("Act One")); var sequence = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest( "Sequence", ParentId: act.Id)); var beat = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest( "Beat", ParentId: sequence.Id)); var move = async () => await _outlines.MoveAsync(act.Id, new MoveOutlineNodeRequest(beat.Id, 1)); await move.Should().ThrowAsync() .WithMessage("*beneath its own descendant*"); } [Fact] public async Task A_node_cannot_be_its_own_parent() { var node = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("Act One")); var move = async () => await _outlines.MoveAsync(node.Id, new MoveOutlineNodeRequest(node.Id, 1)); await move.Should().ThrowAsync() .WithMessage("*its own parent*"); } [Fact] public async Task Moving_to_the_root_detaches_from_the_old_parent() { var act = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("Act One")); var beat = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest( "Beat", ParentId: act.Id)); await _outlines.MoveAsync(beat.Id, new MoveOutlineNodeRequest(null, 2)); var tree = await _outlines.GetTreeAsync(_projectId); tree.Should().HaveCount(2); tree.Single(n => n.Title == "Act One").Children.Should().BeEmpty(); } [Fact] public async Task Deleting_a_node_takes_its_whole_subtree() { var act = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("Act One")); var sequence = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest( "Sequence", ParentId: act.Id)); await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("Beat", ParentId: sequence.Id)); var survivor = await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("Act Two")); await _outlines.DeleteAsync(act.Id); var tree = await _outlines.GetTreeAsync(_projectId); tree.Should().ContainSingle().Which.Id.Should().Be(survivor.Id); using var verification = _db.CreateContext(); verification.OutlineNodes.Should().ContainSingle(); } [Fact] public async Task Creating_under_a_missing_parent_reports_not_found() { var create = async () => await _outlines.CreateAsync(_projectId, new CreateOutlineNodeRequest("Orphan", ParentId: Guid.NewGuid())); await create.Should().ThrowAsync(); } public void Dispose() => _db.Dispose(); }