//! 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) -> 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), } ); }