use super::events::{TransitionFailedEvent, TransitionOccurredEvent, TransitionRequested};
use super::types::{OrgArchetype, TransitionTrigger};
use async_trait::async_trait;
#[async_trait]
pub trait OrgSuiteHook: Clone + Send + Sync + 'static {
async fn evaluate_custom_transition(
&self,
_faction_id: &str,
_current: OrgArchetype,
) -> Option<(OrgArchetype, TransitionTrigger)> {
None
}
async fn on_before_transition(&self, _event: &TransitionRequested) -> Result<(), String> {
Ok(())
}
async fn on_transition_occurred(&self, _event: &TransitionOccurredEvent) {
}
async fn on_transition_failed(&self, _event: &TransitionFailedEvent) {
}
}
#[derive(Debug, Clone, Copy)]
pub struct DefaultOrgSuiteHook;
#[async_trait]
impl OrgSuiteHook for DefaultOrgSuiteHook {
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_default_hook_evaluate_custom_transition() {
let hook = DefaultOrgSuiteHook;
let result = hook
.evaluate_custom_transition("test", OrgArchetype::Holacracy)
.await;
assert!(result.is_none());
}
#[tokio::test]
async fn test_default_hook_on_before_transition() {
let hook = DefaultOrgSuiteHook;
let event = TransitionRequested {
faction_id: "test".to_string(),
from: OrgArchetype::Holacracy,
to: OrgArchetype::Hierarchy,
reason: "test".to_string(),
};
let result = hook.on_before_transition(&event).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_default_hook_on_transition_occurred() {
let hook = DefaultOrgSuiteHook;
let event = TransitionOccurredEvent {
faction_id: "test".to_string(),
from: OrgArchetype::Holacracy,
to: OrgArchetype::Hierarchy,
trigger: TransitionTrigger::Scaling {
from: OrgArchetype::Holacracy,
to: OrgArchetype::Hierarchy,
member_count: 50,
},
timestamp: 100,
};
hook.on_transition_occurred(&event).await;
}
#[tokio::test]
async fn test_default_hook_on_transition_failed() {
let hook = DefaultOrgSuiteHook;
let event = TransitionFailedEvent {
faction_id: "test".to_string(),
from: OrgArchetype::Holacracy,
to: OrgArchetype::Hierarchy,
error: "test error".to_string(),
};
hook.on_transition_failed(&event).await;
}
#[derive(Clone)]
struct TestHook {
cancel_transitions: bool,
}
#[async_trait]
impl OrgSuiteHook for TestHook {
async fn on_before_transition(&self, _event: &TransitionRequested) -> Result<(), String> {
if self.cancel_transitions {
Err("Transition cancelled by test hook".to_string())
} else {
Ok(())
}
}
async fn evaluate_custom_transition(
&self,
faction_id: &str,
current: OrgArchetype,
) -> Option<(OrgArchetype, TransitionTrigger)> {
if faction_id == "special" && current == OrgArchetype::Holacracy {
Some((
OrgArchetype::Culture,
TransitionTrigger::Custom {
from: OrgArchetype::Holacracy,
to: OrgArchetype::Culture,
reason: "Special faction rule".to_string(),
},
))
} else {
None
}
}
}
#[tokio::test]
async fn test_custom_hook_cancel_transition() {
let hook = TestHook {
cancel_transitions: true,
};
let event = TransitionRequested {
faction_id: "test".to_string(),
from: OrgArchetype::Holacracy,
to: OrgArchetype::Hierarchy,
reason: "test".to_string(),
};
let result = hook.on_before_transition(&event).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("cancelled"));
}
#[tokio::test]
async fn test_custom_hook_allow_transition() {
let hook = TestHook {
cancel_transitions: false,
};
let event = TransitionRequested {
faction_id: "test".to_string(),
from: OrgArchetype::Holacracy,
to: OrgArchetype::Hierarchy,
reason: "test".to_string(),
};
let result = hook.on_before_transition(&event).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_custom_hook_custom_transition() {
let hook = TestHook {
cancel_transitions: false,
};
let result = hook
.evaluate_custom_transition("special", OrgArchetype::Holacracy)
.await;
assert!(result.is_some());
let (target, trigger) = result.unwrap();
assert_eq!(target, OrgArchetype::Culture);
match trigger {
TransitionTrigger::Custom { reason, .. } => {
assert!(reason.contains("Special faction"));
}
_ => panic!("Expected Custom trigger"),
}
let result = hook
.evaluate_custom_transition("normal", OrgArchetype::Holacracy)
.await;
assert!(result.is_none());
}
}