Files
novelly/tests/Novelly.Api.Tests/ChapterServiceTests.cs
T
James Wampler e598c18d67 Add users, roles, and per-novel permissions
Introduces accounts (ASP.NET Identity + cookie auth), four global
roles (Admin/Writer/Editor/Reviewer), per-novel ownership and grants
via ProjectMember, and a service-API-key principal for the MCP server
and background import jobs. Enforcement lives in the application
services (not endpoint filters) so the embedded agent and MCP tools,
which call the same services directly, can't bypass it. Web client
gets a login page, session-aware routing, and a People section for
managing per-novel access.

Also includes prior in-flight changes from this branch (CLAUDE.md
compliance pass, dev-deploy docker-compose setup) that were
uncommitted when this feature work started.
2026-08-15 22:29:33 -07:00

82 lines
2.9 KiB
C#

using Microsoft.EntityFrameworkCore;
using Novelly.Api.Beats;
using Novelly.Api.Chapters;
using Novelly.Api.Common;
using Novelly.Api.Projects;
namespace Novelly.Api.Tests;
[TestFixture]
public class ChapterServiceTests : ServiceTestFixture
{
private Guid _projectId;
protected override void OnSetUp()
{
_projectId = Projects.CreateAsync(new CreateProjectRequest("The Salt Road")).Result.Id;
}
[Test]
public async Task Setting_a_number_that_already_exists_is_still_stored_as_given()
{
var first = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall", Number: 5));
var second = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("The Harbour", Number: 5));
Assert.Multiple(() =>
{
Assert.That(first.Number, Is.EqualTo(5));
Assert.That(second.Number, Is.EqualTo(5));
});
}
[Test]
public async Task Updating_leaves_omitted_fields_alone_and_clears_notes_on_empty_string()
{
var chapter = await Chapters.CreateAsync(_projectId, new CreateChapterRequest(
"Landfall", Summary: "The ship makes shore.", Notes: "Check the tide tables."));
var renamed = (await Chapters.UpdateAsync(chapter.Id, new UpdateChapterRequest(Title: "First Landfall")))!;
Assert.Multiple(() =>
{
Assert.That(renamed.Title, Is.EqualTo("First Landfall"));
Assert.That(renamed.Summary, Is.EqualTo("The ship makes shore."));
Assert.That(renamed.Notes, Is.EqualTo("Check the tide tables."));
});
var cleared = (await Chapters.UpdateAsync(chapter.Id, new UpdateChapterRequest(Notes: "")))!;
Assert.Multiple(() =>
{
Assert.That(cleared.Notes, Is.Null);
Assert.That(cleared.Summary, Is.EqualTo("The ship makes shore."));
});
}
[Test]
public async Task Deleting_a_chapter_takes_its_beats_with_it()
{
var chapter = await Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall"));
await Beats.CreateAsync(chapter.Id, new CreateBeatRequest("She finds the map"));
await Chapters.DeleteAsync(chapter.Id);
using var verification = Db.CreateContext();
Assert.That(await verification.Beats.CountAsync(), Is.EqualTo(0));
}
[Test]
public async Task Creating_a_chapter_under_a_missing_project_returns_null_rather_than_throwing() =>
Assert.That(
await Chapters.CreateAsync(Guid.NewGuid(), new CreateChapterRequest("Landfall")),
Is.Null);
[Test]
public async Task Reading_a_missing_chapter_returns_null_rather_than_throwing() =>
Assert.That(await Chapters.GetAsync(Guid.NewGuid()), Is.Null);
[Test]
public async Task Deleting_a_missing_chapter_returns_false_rather_than_throwing() =>
Assert.That(await Chapters.DeleteAsync(Guid.NewGuid()), Is.False);
}