using ClawdDotNet.Core.Cases;
using ClawdDotNet.Core.Config;
using ClawdDotNet.Core.Organisation;
using Shouldly;
namespace ClawdDotNet.Core.Tests.Cases;
/// #689: The rules of escalation, conflict and decision — read off the tree, none configured.
public sealed class CaseRulesTests
{
///
/// purchasing (lead: buy-lead) › team-a (lead: a-lead; buyer-a), team-b (lead: b-lead; buyer-b);
/// devops (lead: ops-lead; dev). No lead above the top units — the top is a human.
///
private static OrgChart House() => new(
[
new UnitConfig { Id = "purchasing", Name = "Purchasing", LeaderAgentId = "buy-lead" },
new UnitConfig { Id = "team-a", Name = "Team A", ParentId = "purchasing", LeaderAgentId = "a-lead" },
new UnitConfig { Id = "team-b", Name = "Team B", ParentId = "purchasing", LeaderAgentId = "b-lead" },
new UnitConfig { Id = "devops", Name = "DevOps", LeaderAgentId = "ops-lead" }
],
[
new AgentConfig { AgentId = "buy-lead", UnitId = "purchasing" },
new AgentConfig { AgentId = "a-lead", UnitId = "team-a" },
new AgentConfig { AgentId = "buyer-a", UnitId = "team-a" },
new AgentConfig { AgentId = "b-lead", UnitId = "team-b" },
new AgentConfig { AgentId = "buyer-b", UnitId = "team-b" },
new AgentConfig { AgentId = "ops-lead", UnitId = "devops" },
new AgentConfig { AgentId = "dev", UnitId = "devops" }
]);
[Fact]
public void An_escalation_goes_to_the_own_lead_one_level_at_a_time()
{
var org = House();
CaseRules.NextHolder(org, "buyer-a").ShouldBe("a-lead");
CaseRules.NextHolder(org, "a-lead").ShouldBe("buy-lead");
CaseRules.NextHolder(org, "buy-lead").ShouldBe(CaseRules.Human, "above the top is a human");
CaseRules.NextHolder(null, "anyone").ShouldBe(CaseRules.Human);
}
[Fact]
public void Two_teams_of_one_area_are_decided_by_that_area_s_lead_and_nobody_above_is_bothered()
{
var route = CaseRules.RouteConflict(House(), "a-lead", "b-lead");
route.Decider.ShouldBe("buy-lead");
route.Informed.ShouldBeEmpty();
}
[Fact]
public void Two_areas_go_to_the_top()
{
var route = CaseRules.RouteConflict(House(), "buy-lead", "ops-lead");
route.DecidedByHuman.ShouldBeTrue();
}
[Fact]
public void A_conflict_is_delivered_directly_and_the_levels_between_are_told_not_asked()
{
var route = CaseRules.RouteConflict(House(), "buyer-a", "dev");
route.DecidedByHuman.ShouldBeTrue("purchasing and devops meet only at the top");
route.Informed.ShouldBe(["a-lead", "buy-lead", "ops-lead"], ignoreOrder: true);
}
[Fact]
public void Someone_above_the_other_side_decides_it_that_is_no_conflict()
{
CaseRules.RouteConflict(House(), "a-lead", "buyer-a").Refusal.ShouldNotBeNull().ShouldContain("no conflict");
CaseRules.RouteConflict(House(), "buyer-a", "buy-lead").Refusal.ShouldNotBeNull().ShouldContain("escalate");
}
[Fact]
public void A_decision_binds_the_deciding_lead_s_area_and_everything_below()
{
var org = House();
var decision = new Decision { Id = "d-1", ScopeUnitId = CaseRules.ScopeOf(org, "buy-lead") };
decision.ScopeUnitId.ShouldBe("purchasing");
CaseRules.Binds(org, decision, "buyer-a").ShouldBeTrue();
CaseRules.Binds(org, decision, "dev").ShouldBeFalse();
CaseRules.Binds(org, decision with { ScopeUnitId = CaseRules.ScopeOf(org, CaseRules.Human) }, "dev")
.ShouldBeTrue("a human decides for the whole instance");
}
[Fact]
public void A_decision_is_found_by_its_subject_or_by_telling_words()
{
var decisions = new[]
{
new Decision { Id = "d-1", Subject = "Delivery date field in orders", Question = "Where do we record the delivery date?" },
new Decision { Id = "d-2", Subject = "Weekly report format" }
};
CaseRules.Find(decisions, "delivery date field orders").Select(d => d.Id).ShouldBe(["d-1"]);
CaseRules.Find(decisions, "Order confirmation", "The supplier confirmed a delivery date, where does it go?")
.Select(d => d.Id).ShouldBe(["d-1"]);
CaseRules.Find(decisions, "Holiday plan").ShouldBeEmpty();
}
}