Group character beats into arc-stage sections; reciprocal relationship types
Arc stages now group the beats that establish or pay off that stage of a
character's arc (many-to-many via ArcStageBeats), and carry a Result field
(renamed from Description) describing what the stage results in for the
character. Assigning a beat to a stage moves it out of any other stage of
the same character. New endpoint POST /api/arc-stages/{id}/beats, MCP tool
set_arc_stage_beats, and frontend grouping UI in CharacterArc/CharacterBeats.
Also records a relationship's reciprocal type so both characters' dossiers
show the correct direction (e.g. "sister" / "brother") instead of mirroring
the same label.
This commit is contained in:
@@ -101,6 +101,58 @@ public class CharacterServiceTests : ServiceTestFixture
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Adding_a_relationship_records_it_on_both_characters()
|
||||
{
|
||||
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
|
||||
var mara = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Mara"));
|
||||
|
||||
await Characters.AddRelationshipAsync(
|
||||
ines.Id, new CreateRelationshipRequest(mara.Id, "sister", ReciprocalRelationshipType: "brother"));
|
||||
|
||||
var inesAfter = (await Characters.GetAsync(ines.Id))!;
|
||||
var maraAfter = (await Characters.GetAsync(mara.Id))!;
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(inesAfter.Relationships, Has.Count.EqualTo(1));
|
||||
Assert.That(inesAfter.Relationships[0].RelationshipType, Is.EqualTo("sister"));
|
||||
Assert.That(inesAfter.Relationships[0].RelatedCharacterId, Is.EqualTo(mara.Id));
|
||||
Assert.That(maraAfter.Relationships, Has.Count.EqualTo(1));
|
||||
Assert.That(maraAfter.Relationships[0].RelationshipType, Is.EqualTo("brother"));
|
||||
Assert.That(maraAfter.Relationships[0].RelatedCharacterId, Is.EqualTo(ines.Id));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task A_relationship_with_no_reciprocal_type_mirrors_the_same_type_both_ways()
|
||||
{
|
||||
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
|
||||
var mara = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Mara"));
|
||||
|
||||
await Characters.AddRelationshipAsync(ines.Id, new CreateRelationshipRequest(mara.Id, "rival"));
|
||||
|
||||
var maraAfter = (await Characters.GetAsync(mara.Id))!;
|
||||
|
||||
Assert.That(maraAfter.Relationships[0].RelationshipType, Is.EqualTo("rival"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Removing_a_relationship_removes_the_reciprocal_side_too()
|
||||
{
|
||||
var ines = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Ines"));
|
||||
var mara = await Characters.CreateAsync(_projectId, new CreateCharacterRequest("Mara"));
|
||||
|
||||
var withRelationship = (await Characters.AddRelationshipAsync(
|
||||
ines.Id, new CreateRelationshipRequest(mara.Id, "sister", ReciprocalRelationshipType: "brother")))!;
|
||||
var relationshipId = withRelationship.Relationships[0].Id;
|
||||
|
||||
await Characters.RemoveRelationshipAsync(relationshipId);
|
||||
var maraAfter = (await Characters.GetAsync(mara.Id))!;
|
||||
|
||||
Assert.That(maraAfter.Relationships, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Deleting_a_character_detaches_it_from_beats_rather_than_deleting_them()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user