Rename Project concept to Novel across the stack
Renames the domain concept from Project to Novel throughout the backend (entities, DTOs, services, endpoints, ProjectAccessService/Permission, ProjectId foreign keys), MCP server (tool names and routes), and the React/Vite frontend (types, hooks, routes, components). Adds a new EF Core migration (RenameProjectToNovel) using RenameTable/RenameColumn to preserve existing data instead of dropping/recreating tables. Updates CLAUDE.md's structure section to reference Novels/ instead of Projects/.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
using Novelly.Api.Chapters;
|
||||
using Novelly.Api.Characters;
|
||||
using Novelly.Api.Common;
|
||||
using Novelly.Api.Projects;
|
||||
using Novelly.Api.Novels;
|
||||
using Novelly.Api.Questions;
|
||||
|
||||
namespace Novelly.Api.Tests;
|
||||
@@ -9,21 +9,21 @@ namespace Novelly.Api.Tests;
|
||||
[TestFixture]
|
||||
public class OpenQuestionTests : ServiceTestFixture
|
||||
{
|
||||
private Guid _projectId;
|
||||
private Guid _novelId;
|
||||
private Guid _chapterId;
|
||||
private Guid _characterId;
|
||||
|
||||
protected override void OnSetUp()
|
||||
{
|
||||
_projectId = Projects.CreateAsync(new CreateProjectRequest("The Salt Road")).Result.Id;
|
||||
_chapterId = Chapters.CreateAsync(_projectId, new CreateChapterRequest("Landfall")).Result.Id;
|
||||
_characterId = Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines")).Result.Id;
|
||||
_novelId = Novels.CreateAsync(new CreateNovelRequest("The Salt Road")).Result.Id;
|
||||
_chapterId = Chapters.CreateAsync(_novelId, new CreateChapterRequest("Landfall")).Result.Id;
|
||||
_characterId = Characters.CreateAsync(_novelId, new CreateCharacterRequest("Ines")).Result.Id;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task A_question_can_hang_off_a_chapter_and_a_character_at_once()
|
||||
{
|
||||
var question = await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(
|
||||
var question = await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest(
|
||||
"Does she know about the letter before the harbour?",
|
||||
ChapterId: _chapterId,
|
||||
CharacterId: _characterId));
|
||||
@@ -41,7 +41,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
public async Task A_question_about_the_book_as_a_whole_needs_no_association()
|
||||
{
|
||||
var question = await Questions.CreateAsync(
|
||||
_projectId, new CreateOpenQuestionRequest("Is this one book or two?"));
|
||||
_novelId, new CreateOpenQuestionRequest("Is this one book or two?"));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@@ -53,21 +53,21 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
[Test]
|
||||
public async Task The_outline_and_the_character_page_each_see_only_their_own_questions()
|
||||
{
|
||||
await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(
|
||||
await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest(
|
||||
"Where does the chapter break?", ChapterId: _chapterId));
|
||||
await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(
|
||||
await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest(
|
||||
"What does she actually want?", CharacterId: _characterId));
|
||||
await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest("Is this one book or two?"));
|
||||
await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest("Is this one book or two?"));
|
||||
|
||||
var forChapter = await Questions.ListAsync(_projectId, chapterId: _chapterId);
|
||||
var forCharacter = await Questions.ListAsync(_projectId, characterId: _characterId);
|
||||
var forProject = await Questions.ListAsync(_projectId);
|
||||
var forChapter = await Questions.ListAsync(_novelId, chapterId: _chapterId);
|
||||
var forCharacter = await Questions.ListAsync(_novelId, characterId: _characterId);
|
||||
var forNovel = await Questions.ListAsync(_novelId);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(forChapter.Select(q => q.Question), Is.EqualTo(new[] { "Where does the chapter break?" }));
|
||||
Assert.That(forCharacter.Select(q => q.Question), Is.EqualTo(new[] { "What does she actually want?" }));
|
||||
Assert.That(forProject, Has.Count.EqualTo(3));
|
||||
Assert.That(forNovel, Has.Count.EqualTo(3));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -75,13 +75,13 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
public async Task Resolved_questions_drop_off_the_list_unless_asked_for()
|
||||
{
|
||||
var settled = await Questions.CreateAsync(
|
||||
_projectId, new CreateOpenQuestionRequest("Where does the chapter break?"));
|
||||
await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest("Is this one book or two?"));
|
||||
_novelId, new CreateOpenQuestionRequest("Where does the chapter break?"));
|
||||
await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest("Is this one book or two?"));
|
||||
|
||||
await Questions.ResolveAsync(settled.Id, new ResolveOpenQuestionRequest("After the harbour."));
|
||||
|
||||
var open = await Questions.ListAsync(_projectId);
|
||||
var everything = await Questions.ListAsync(_projectId, includeResolved: true);
|
||||
var open = await Questions.ListAsync(_novelId);
|
||||
var everything = await Questions.ListAsync(_novelId, includeResolved: true);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@@ -97,7 +97,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
public async Task Resolving_records_what_was_decided()
|
||||
{
|
||||
var question = await Questions.CreateAsync(
|
||||
_projectId, new CreateOpenQuestionRequest("Where does the chapter break?"));
|
||||
_novelId, new CreateOpenQuestionRequest("Where does the chapter break?"));
|
||||
|
||||
var resolved = (await Questions.ResolveAsync(
|
||||
question.Id, new ResolveOpenQuestionRequest("After the harbour burns.")))!;
|
||||
@@ -115,7 +115,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
{
|
||||
await Chapters.UpdateAsync(_chapterId, new UpdateChapterRequest(Notes: "Runs long."));
|
||||
|
||||
var question = await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(
|
||||
var question = await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest(
|
||||
"Where does the chapter break?", ChapterId: _chapterId, CharacterId: _characterId));
|
||||
|
||||
await Questions.ResolveAsync(
|
||||
@@ -136,7 +136,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
[Test]
|
||||
public async Task A_resolution_stays_off_the_notes_unless_asked_for()
|
||||
{
|
||||
var question = await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(
|
||||
var question = await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest(
|
||||
"Where does the chapter break?", ChapterId: _chapterId));
|
||||
|
||||
await Questions.ResolveAsync(question.Id, new ResolveOpenQuestionRequest("After the harbour."));
|
||||
@@ -147,7 +147,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
[Test]
|
||||
public async Task Reopening_clears_the_resolution_but_leaves_the_note_behind()
|
||||
{
|
||||
var question = await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(
|
||||
var question = await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest(
|
||||
"Where does the chapter break?", ChapterId: _chapterId));
|
||||
|
||||
await Questions.ResolveAsync(
|
||||
@@ -167,7 +167,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
[Test]
|
||||
public async Task A_question_can_be_detached_from_what_it_was_about()
|
||||
{
|
||||
var question = await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(
|
||||
var question = await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest(
|
||||
"Where does the chapter break?", ChapterId: _chapterId, CharacterId: _characterId));
|
||||
|
||||
var detached = (await Questions.UpdateAsync(
|
||||
@@ -184,7 +184,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
[Test]
|
||||
public async Task Deleting_a_chapter_leaves_its_questions_open_rather_than_taking_them()
|
||||
{
|
||||
var question = await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(
|
||||
var question = await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest(
|
||||
"Does she know about the letter?", ChapterId: _chapterId));
|
||||
|
||||
await Chapters.DeleteAsync(_chapterId);
|
||||
@@ -202,52 +202,52 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
public async Task A_question_can_be_deleted_outright()
|
||||
{
|
||||
var question = await Questions.CreateAsync(
|
||||
_projectId, new CreateOpenQuestionRequest("Where does the chapter break?"));
|
||||
_novelId, new CreateOpenQuestionRequest("Where does the chapter break?"));
|
||||
|
||||
await Questions.DeleteAsync(question.Id);
|
||||
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(await Questions.ListAsync(_projectId, includeResolved: true), Is.Empty);
|
||||
Assert.That(await Questions.ListAsync(_novelId, includeResolved: true), Is.Empty);
|
||||
Assert.That(await Questions.GetAsync(question.Id), Is.Null);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Deleting_a_project_takes_its_questions()
|
||||
public async Task Deleting_a_novel_takes_its_questions()
|
||||
{
|
||||
await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest("Is this one book or two?"));
|
||||
await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest("Is this one book or two?"));
|
||||
|
||||
await Projects.DeleteAsync(_projectId);
|
||||
await Novels.DeleteAsync(_novelId);
|
||||
|
||||
using var verification = Db.CreateContext();
|
||||
Assert.That(verification.OpenQuestions.Count(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void A_question_cannot_be_attached_to_another_project_s_chapter()
|
||||
public void A_question_cannot_be_attached_to_another_novel_s_chapter()
|
||||
{
|
||||
var elsewhere = Chapters.CreateAsync(
|
||||
Projects.CreateAsync(new CreateProjectRequest("Other Book")).Result.Id,
|
||||
Novels.CreateAsync(new CreateNovelRequest("Other Book")).Result.Id,
|
||||
new CreateChapterRequest("Elsewhere")).Result;
|
||||
|
||||
Assert.That(
|
||||
async () => await Questions.CreateAsync(
|
||||
_projectId, new CreateOpenQuestionRequest("A question", ChapterId: elsewhere.Id)),
|
||||
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("same project"));
|
||||
_novelId, new CreateOpenQuestionRequest("A question", ChapterId: elsewhere.Id)),
|
||||
Throws.TypeOf<InvalidOperationException>().With.Message.Contains("same novel"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void A_blank_question_is_refused() =>
|
||||
Assert.That(
|
||||
async () => await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(" ")),
|
||||
async () => await Questions.CreateAsync(_novelId, new CreateOpenQuestionRequest(" ")),
|
||||
Throws.TypeOf<ArgumentException>());
|
||||
|
||||
[Test]
|
||||
public async Task Resolving_with_nothing_decided_is_refused()
|
||||
{
|
||||
var question = await Questions.CreateAsync(
|
||||
_projectId, new CreateOpenQuestionRequest("Where does the chapter break?"));
|
||||
_novelId, new CreateOpenQuestionRequest("Where does the chapter break?"));
|
||||
|
||||
Assert.That(
|
||||
async () => await Questions.ResolveAsync(question.Id, new ResolveOpenQuestionRequest(" ")),
|
||||
|
||||
Reference in New Issue
Block a user