Stop throwing for not-found; add Guard and request validation
Not-found lookups return null/false instead of throwing NotFoundException across all services — a missing row is expected control flow, not an exceptional condition. NotFoundException stays for embedded precondition checks inside mutations (missing parent, invalid foreign reference). Guard (copied from mic-check) enforces required arguments at the top of every service method. A ported IModelValidator<T> framework validates every request DTO at the API layer via a new ValidationEndpointFilter, returning a 400 with field-level messages; services re-run the same validator and throw for direct callers that bypass the API. Endpoints translate null/false into 404 via a new ToApiResult() helper. The agent toolset boundary translates the same nullable/bool results into the tool-error text the model already expected.
This commit is contained in:
@@ -100,8 +100,8 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
var question = await Questions.CreateAsync(
|
||||
_projectId, new CreateOpenQuestionRequest("Where does the chapter break?"));
|
||||
|
||||
var resolved = await Questions.ResolveAsync(
|
||||
question.Id, new ResolveOpenQuestionRequest("After the harbour burns."));
|
||||
var resolved = (await Questions.ResolveAsync(
|
||||
question.Id, new ResolveOpenQuestionRequest("After the harbour burns.")))!;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@@ -123,8 +123,8 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
question.Id,
|
||||
new ResolveOpenQuestionRequest("After the harbour burns.", AppendToNotes: true));
|
||||
|
||||
var chapter = await Chapters.GetAsync(_chapterId);
|
||||
var character = await Characters.GetAsync(_characterId);
|
||||
var chapter = (await Chapters.GetAsync(_chapterId))!;
|
||||
var character = (await Characters.GetAsync(_characterId))!;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@@ -143,7 +143,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
|
||||
await Questions.ResolveAsync(question.Id, new ResolveOpenQuestionRequest("After the harbour."));
|
||||
|
||||
Assert.That((await Chapters.GetAsync(_chapterId)).Notes, Is.Null);
|
||||
Assert.That((await Chapters.GetAsync(_chapterId))!.Notes, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -155,8 +155,8 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
await Questions.ResolveAsync(
|
||||
question.Id, new ResolveOpenQuestionRequest("After the harbour.", AppendToNotes: true));
|
||||
|
||||
var reopened = await Questions.ReopenAsync(question.Id);
|
||||
var chapter = await Chapters.GetAsync(_chapterId);
|
||||
var reopened = (await Questions.ReopenAsync(question.Id))!;
|
||||
var chapter = (await Chapters.GetAsync(_chapterId))!;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@@ -172,8 +172,8 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
var question = await Questions.CreateAsync(_projectId, new CreateOpenQuestionRequest(
|
||||
"Where does the chapter break?", ChapterId: _chapterId, CharacterId: _characterId));
|
||||
|
||||
var detached = await Questions.UpdateAsync(
|
||||
question.Id, new UpdateOpenQuestionRequest(ClearChapter: true));
|
||||
var detached = (await Questions.UpdateAsync(
|
||||
question.Id, new UpdateOpenQuestionRequest(ClearChapter: true)))!;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@@ -192,7 +192,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
|
||||
await Chapters.DeleteAsync(_chapterId);
|
||||
|
||||
var survivor = await Questions.GetAsync(question.Id);
|
||||
var survivor = (await Questions.GetAsync(question.Id))!;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@@ -212,9 +212,7 @@ public class OpenQuestionTests : ServiceTestFixture
|
||||
Assert.Multiple(async () =>
|
||||
{
|
||||
Assert.That(await Questions.ListAsync(_projectId, includeResolved: true), Is.Empty);
|
||||
Assert.That(
|
||||
async () => await Questions.GetAsync(question.Id),
|
||||
Throws.TypeOf<NotFoundException>());
|
||||
Assert.That(await Questions.GetAsync(question.Id), Is.Null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user