use super::config::OrgSuiteConfig;
use super::events::{TransitionFailedEvent, TransitionOccurredEvent, TransitionRequested};
use super::hook::OrgSuiteHook;
use super::service::TransitionService;
use super::state::OrgSuiteState;
use super::transition::ConditionContext;
use super::types::OrgArchetype;
pub struct OrgSuiteSystem<H: OrgSuiteHook> {
service: TransitionService,
hook: H,
}
impl<H: OrgSuiteHook> OrgSuiteSystem<H> {
pub fn new(service: TransitionService, hook: H) -> Self {
Self { service, hook }
}
pub async fn update<F>(
&mut self,
state: &mut OrgSuiteState,
config: &OrgSuiteConfig,
context_fn: F,
) -> Vec<TransitionOccurredEvent>
where
F: FnMut(&str) -> ConditionContext,
{
let mut events = Vec::new();
state.tick();
if !state
.current_tick()
.is_multiple_of(config.transition_check_interval as u64)
{
return events;
}
if !config.enable_auto_transition {
return events;
}
let transitions = self.service.evaluate_all_transitions(state, context_fn);
for (faction_id, from, to, trigger) in transitions {
if let Some((custom_to, custom_trigger)) = self
.hook
.evaluate_custom_transition(&faction_id, from)
.await
{
match self
.execute_transition(state, &faction_id, from, custom_to, custom_trigger)
.await
{
Ok(event) => events.push(event),
Err(failed_event) => {
self.hook.on_transition_failed(&failed_event).await;
}
}
} else {
match self
.execute_transition(state, &faction_id, from, to, trigger)
.await
{
Ok(event) => events.push(event),
Err(failed_event) => {
self.hook.on_transition_failed(&failed_event).await;
}
}
}
}
events
}
pub async fn handle_transition_request(
&mut self,
request: TransitionRequested,
state: &mut OrgSuiteState,
) -> Result<TransitionOccurredEvent, TransitionFailedEvent> {
if let Err(reason) = self.hook.on_before_transition(&request).await {
let failed = TransitionFailedEvent {
faction_id: request.faction_id.clone(),
from: request.from,
to: request.to,
error: reason,
};
self.hook.on_transition_failed(&failed).await;
return Err(failed);
}
let trigger = super::types::TransitionTrigger::Custom {
from: request.from,
to: request.to,
reason: request.reason,
};
self.execute_transition(
state,
&request.faction_id,
request.from,
request.to,
trigger,
)
.await
}
async fn execute_transition(
&mut self,
state: &mut OrgSuiteState,
faction_id: &str,
from: OrgArchetype,
to: OrgArchetype,
trigger: super::types::TransitionTrigger,
) -> Result<TransitionOccurredEvent, TransitionFailedEvent> {
if !self.service.registry().is_transition_valid(from, to) {
let failed = TransitionFailedEvent {
faction_id: faction_id.to_string(),
from,
to,
error: format!("No converter registered for {:?} -> {:?}", from, to),
};
return Err(failed);
}
match state.record_transition(faction_id, from, to, trigger.clone()) {
Ok(_) => {
let event = TransitionOccurredEvent {
faction_id: faction_id.to_string(),
from,
to,
trigger,
timestamp: state.current_tick(),
};
self.hook.on_transition_occurred(&event).await;
Ok(event)
}
Err(e) => {
let failed = TransitionFailedEvent {
faction_id: faction_id.to_string(),
from,
to,
error: e.to_string(),
};
Err(failed)
}
}
}
pub fn service(&self) -> &TransitionService {
&self.service
}
pub fn hook(&self) -> &H {
&self.hook
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::plugin::org_suite::{
DefaultOrgSuiteHook, HolacracyToHierarchyConverter, ScalingCondition, TransitionRegistry,
};
fn create_test_system() -> OrgSuiteSystem<DefaultOrgSuiteHook> {
let mut registry = TransitionRegistry::new();
registry.register_converter(Box::new(HolacracyToHierarchyConverter));
registry.register_condition(Box::new(ScalingCondition::new(
50,
OrgArchetype::Holacracy,
OrgArchetype::Hierarchy,
)));
let service = TransitionService::new(registry);
OrgSuiteSystem::new(service, DefaultOrgSuiteHook)
}
#[tokio::test]
async fn test_system_update_no_transitions() {
let mut system = create_test_system();
let mut state = OrgSuiteState::new();
state.register_faction("test", OrgArchetype::Holacracy);
let config = OrgSuiteConfig::default();
let context_fn = |_: &str| ConditionContext {
member_count: 30, ..Default::default()
};
let events = system.update(&mut state, &config, context_fn).await;
assert_eq!(events.len(), 0);
assert_eq!(state.current_tick(), 1);
}
#[tokio::test]
async fn test_system_update_with_transition() {
let mut system = create_test_system();
let mut state = OrgSuiteState::new();
state.register_faction("startup", OrgArchetype::Holacracy);
let config = OrgSuiteConfig::default();
let context_fn = |_: &str| ConditionContext {
member_count: 60, ..Default::default()
};
let events = system.update(&mut state, &config, context_fn).await;
assert_eq!(events.len(), 1);
assert_eq!(events[0].faction_id, "startup");
assert_eq!(events[0].from, OrgArchetype::Holacracy);
assert_eq!(events[0].to, OrgArchetype::Hierarchy);
assert_eq!(
state.get_archetype("startup"),
Some(OrgArchetype::Hierarchy)
);
}
#[tokio::test]
async fn test_system_respects_check_interval() {
let mut system = create_test_system();
let mut state = OrgSuiteState::new();
state.register_faction("test", OrgArchetype::Holacracy);
let config = OrgSuiteConfig::new().with_check_interval(5);
let context_fn = |_: &str| ConditionContext {
member_count: 60,
..Default::default()
};
let events = system.update(&mut state, &config, context_fn).await;
assert_eq!(events.len(), 0);
for _ in 0..3 {
let events = system
.update(&mut state, &config, |_| ConditionContext::default())
.await;
assert_eq!(events.len(), 0);
}
let events = system
.update(&mut state, &config, |_| ConditionContext {
member_count: 60,
..Default::default()
})
.await;
assert_eq!(events.len(), 1);
}
#[tokio::test]
async fn test_system_auto_transition_disabled() {
let mut system = create_test_system();
let mut state = OrgSuiteState::new();
state.register_faction("test", OrgArchetype::Holacracy);
let config = OrgSuiteConfig::new().with_auto_transition(false);
let context_fn = |_: &str| ConditionContext {
member_count: 60,
..Default::default()
};
let events = system.update(&mut state, &config, context_fn).await;
assert_eq!(events.len(), 0, "Should not auto-transition when disabled");
}
#[tokio::test]
async fn test_handle_transition_request_success() {
let mut system = create_test_system();
let mut state = OrgSuiteState::new();
state.register_faction("rebels", OrgArchetype::Holacracy);
let request = TransitionRequested {
faction_id: "rebels".to_string(),
from: OrgArchetype::Holacracy,
to: OrgArchetype::Hierarchy,
reason: "Manual transition test".to_string(),
};
let result = system.handle_transition_request(request, &mut state).await;
assert!(result.is_ok());
let event = result.unwrap();
assert_eq!(event.faction_id, "rebels");
assert_eq!(event.from, OrgArchetype::Holacracy);
assert_eq!(event.to, OrgArchetype::Hierarchy);
}
#[tokio::test]
async fn test_handle_transition_request_no_converter() {
let mut system = create_test_system();
let mut state = OrgSuiteState::new();
state.register_faction("test", OrgArchetype::Social);
let request = TransitionRequested {
faction_id: "test".to_string(),
from: OrgArchetype::Social,
to: OrgArchetype::Culture,
reason: "No converter registered".to_string(),
};
let result = system.handle_transition_request(request, &mut state).await;
assert!(result.is_err());
let failed = result.unwrap_err();
assert!(failed.error.contains("No converter"));
}
#[tokio::test]
async fn test_handle_transition_request_faction_not_found() {
let mut system = create_test_system();
let mut state = OrgSuiteState::new();
let request = TransitionRequested {
faction_id: "unknown".to_string(),
from: OrgArchetype::Holacracy,
to: OrgArchetype::Hierarchy,
reason: "Test".to_string(),
};
let result = system.handle_transition_request(request, &mut state).await;
assert!(result.is_err());
let failed = result.unwrap_err();
assert!(failed.error.contains("not found"));
}
}