Files
IdeA/crates/domain/tests/agent_profile_a0.rs
Blomios fdcf16c387 chore(wip): checkpoint P8/C avant chantier Codex inter-agents
Sauvegarde de l'arbre de travail en cours (persistance P8, conversations
C-series, write-portal frontend, médiation d'entrée) avant d'attaquer le
support de la délégation inter-agents pour les profils Codex.

Le round-trip inter-agent question/réponse est couvert sans tokens par
les tests loopback existants (state::mcp_e2e_loopback_tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:42:53 +02:00

254 lines
7.5 KiB
Rust

//! LOT A0 (fondation) — tests purs du domaine :
//! - `Agent::with_profile` ne change **que** `profile_id` ;
//! - `LayoutTree::leaf` retrouve/loupe un node (feuille vs split/grid) ;
//! - variante d'event `DomainEvent::AgentProfileChanged`.
//!
//! Réutilise les fixtures/helpers existants (`helpers::node`, `helpers::session`)
//! et colle au style des tests `entities.rs` / `layout.rs` (ARCHITECTURE §15.4 A0).
mod helpers;
use domain::events::DomainEvent;
use domain::ids::{AgentId, ProfileId, SkillId, TemplateId};
use domain::{
Agent, AgentOrigin, Direction, LayoutNode, LayoutTree, LeafCell, SkillRef, SkillScope,
SplitContainer, TemplateVersion, WeightedChild,
};
use helpers::{node, session};
use uuid::Uuid;
fn agent_id(n: u128) -> AgentId {
AgentId::from_uuid(Uuid::from_u128(n))
}
fn profile_id(n: u128) -> ProfileId {
ProfileId::from_uuid(Uuid::from_u128(n))
}
fn template_id(n: u128) -> TemplateId {
TemplateId::from_uuid(Uuid::from_u128(n))
}
fn skill_ref(n: u128) -> SkillRef {
SkillRef::new(SkillId::from_uuid(Uuid::from_u128(n)), SkillScope::Project)
}
// ---------------------------------------------------------------------------
// Agent::with_profile — ne change QUE profile_id
// ---------------------------------------------------------------------------
/// Un agent issu d'un template, synchronisé, avec des skills : cas le plus riche
/// pour prouver que `with_profile` ne touche à rien d'autre que `profile_id`.
fn rich_agent() -> Agent {
Agent::new(
agent_id(1),
"Architect",
"agents/architect.md",
profile_id(10),
AgentOrigin::FromTemplate {
template_id: template_id(7),
synced_template_version: TemplateVersion::INITIAL,
},
true,
)
.expect("valid rich agent")
.with_skills(vec![skill_ref(100), skill_ref(101)])
}
#[test]
fn with_profile_changes_only_profile_id() {
let before = rich_agent();
let new_profile = profile_id(99);
let after = before.clone().with_profile(new_profile);
// Le seul champ muté :
assert_eq!(after.profile_id, new_profile);
assert_ne!(after.profile_id, before.profile_id);
// Tous les autres champs sont strictement inchangés :
assert_eq!(after.id, before.id);
assert_eq!(after.name, before.name);
assert_eq!(after.context_path, before.context_path);
assert_eq!(after.origin, before.origin);
assert_eq!(after.synchronized, before.synchronized);
assert_eq!(after.skills, before.skills);
}
#[test]
fn with_profile_preserves_scratch_origin_and_unsynced() {
let before = Agent::new(
agent_id(2),
"Scratchy",
"agents/scratchy.md",
profile_id(10),
AgentOrigin::Scratch,
false,
)
.expect("valid scratch agent");
let after = before.clone().with_profile(profile_id(11));
assert_eq!(after.profile_id, profile_id(11));
assert_eq!(after.origin, AgentOrigin::Scratch);
assert!(!after.synchronized);
assert!(after.skills.is_empty());
// Tout sauf le profil identique → comparer un clone "patché" prouve l'égalité.
assert_eq!(after, before.with_profile(profile_id(11)));
}
#[test]
fn with_profile_to_same_profile_is_identity() {
// Idempotence / égalité attendue : re-poser le même profil ⇒ agent inchangé.
let before = rich_agent();
let after = before.clone().with_profile(before.profile_id);
assert_eq!(after, before);
}
#[test]
fn with_profile_is_idempotent_when_repeated() {
let base = rich_agent();
let once = base.clone().with_profile(profile_id(42));
let twice = once.clone().with_profile(profile_id(42));
assert_eq!(once, twice);
}
// ---------------------------------------------------------------------------
// LayoutTree::leaf — retrouve / loupe (feuille vs split/grid)
// ---------------------------------------------------------------------------
fn leaf_cell(id: u128, sess: Option<u128>) -> LeafCell {
LeafCell {
id: node(id),
session: sess.map(session),
agent: None,
conversation_id: None,
engine_session_id: None,
agent_was_running: false,
}
}
/// Arbre contenant **au moins un split** : racine = split (id 9) de deux feuilles
/// (id 1 avec session, id 2 sans).
fn split_tree() -> LayoutTree {
LayoutTree::new(LayoutNode::Split(SplitContainer {
id: node(9),
direction: Direction::Row,
children: vec![
WeightedChild {
node: LayoutNode::Leaf(leaf_cell(1, Some(100))),
weight: 1.0,
},
WeightedChild {
node: LayoutNode::Leaf(leaf_cell(2, None)),
weight: 1.0,
},
],
}))
}
#[test]
fn leaf_finds_existing_leaf_in_split_tree() {
let tree = split_tree();
// Feuille 1 : retrouvée, et c'est exactement le LeafCell attendu.
let expected = leaf_cell(1, Some(100));
let found = tree.leaf(node(1)).expect("leaf 1 exists");
assert_eq!(*found, expected);
// Feuille 2 (sans session) aussi.
let found2 = tree.leaf(node(2)).expect("leaf 2 exists");
assert_eq!(*found2, leaf_cell(2, None));
}
#[test]
fn leaf_returns_none_for_absent_node() {
let tree = split_tree();
assert!(tree.leaf(node(404)).is_none());
}
#[test]
fn leaf_returns_none_for_split_node_id() {
// L'id 9 existe mais désigne un split, pas une feuille ⇒ None.
let tree = split_tree();
assert!(tree.leaf(node(9)).is_none());
}
#[test]
fn leaf_returns_none_for_grid_node_id() {
use domain::{GridCell, GridContainer};
// Grille 1x1 (id 50) contenant une feuille (id 3).
let grid = LayoutTree::new(LayoutNode::Grid(GridContainer {
id: node(50),
col_weights: vec![1.0],
row_weights: vec![1.0],
cells: vec![GridCell {
node: LayoutNode::Leaf(leaf_cell(3, None)),
row: 0,
col: 0,
row_span: 1,
col_span: 1,
}],
}));
// L'id de la grille n'est pas une feuille.
assert!(grid.leaf(node(50)).is_none());
// La feuille imbriquée est bien retrouvée.
assert_eq!(
*grid.leaf(node(3)).expect("nested leaf"),
leaf_cell(3, None)
);
}
#[test]
fn leaf_finds_single_root_leaf() {
let tree = LayoutTree::single(leaf_cell(7, Some(70)));
assert_eq!(
*tree.leaf(node(7)).expect("root leaf"),
leaf_cell(7, Some(70))
);
assert!(tree.leaf(node(8)).is_none());
}
// ---------------------------------------------------------------------------
// DomainEvent::AgentProfileChanged — construction & champs
// ---------------------------------------------------------------------------
#[test]
fn agent_profile_changed_carries_agent_and_profile() {
let aid = agent_id(1);
let pid = profile_id(2);
let event = DomainEvent::AgentProfileChanged {
agent_id: aid,
profile_id: pid,
};
match event {
DomainEvent::AgentProfileChanged {
agent_id,
profile_id,
} => {
assert_eq!(agent_id, aid);
assert_eq!(profile_id, pid);
}
other => panic!("expected AgentProfileChanged, got {other:?}"),
}
}
#[test]
fn agent_profile_changed_equality_and_clone() {
let e1 = DomainEvent::AgentProfileChanged {
agent_id: agent_id(1),
profile_id: profile_id(2),
};
// Clone == original ; un profil différent rompt l'égalité.
assert_eq!(e1.clone(), e1);
assert_ne!(
e1,
DomainEvent::AgentProfileChanged {
agent_id: agent_id(1),
profile_id: profile_id(3),
}
);
}