use async_trait::async_trait;
use tokio::sync::broadcast;
use crate::error::VisualResult;
use crate::models::{PhaseEvent, ReviewDecision, SddPhase, SddSession, SddStatus};
#[async_trait]
pub trait SddFacade: Send + Sync {
async fn start(
&self,
feature: &str,
requirement: &str,
start_phase: SddPhase,
) -> VisualResult<SddSession>;
async fn submit_review(
&self,
session_id: &str,
phase: SddPhase,
decision: ReviewDecision,
comment: Option<&str>,
) -> VisualResult<SddSession>;
async fn cancel(&self, session_id: &str) -> VisualResult<()>;
async fn status(&self, session_id: &str) -> VisualResult<SddSession>;
async fn subscribe_events(&self) -> VisualResult<broadcast::Receiver<PhaseEvent>>;
async fn read_artifact(&self, session_id: &str, phase: SddPhase) -> VisualResult<String>;
}
pub struct MockSddFacade {
events: broadcast::Sender<PhaseEvent>,
}
impl MockSddFacade {
pub fn new() -> Self {
let (tx, _rx) = broadcast::channel(256);
Self { events: tx }
}
}
impl Default for MockSddFacade {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl SddFacade for MockSddFacade {
async fn start(
&self,
feature: &str,
_requirement: &str,
start_phase: SddPhase,
) -> VisualResult<SddSession> {
let session = SddSession {
session_id: uuid::Uuid::new_v4().to_string(),
trace_id: uuid::Uuid::new_v4().to_string(),
feature_name: feature.to_string(),
current_phase: start_phase,
status: SddStatus::Running,
started_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
Ok(session)
}
async fn submit_review(
&self,
session_id: &str,
_phase: SddPhase,
_decision: ReviewDecision,
_comment: Option<&str>,
) -> VisualResult<SddSession> {
Ok(SddSession {
session_id: session_id.to_string(),
trace_id: String::new(),
feature_name: String::new(),
current_phase: SddPhase::Spec,
status: SddStatus::Running,
started_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
})
}
async fn cancel(&self, _session_id: &str) -> VisualResult<()> {
Ok(())
}
async fn status(&self, session_id: &str) -> VisualResult<SddSession> {
Ok(SddSession {
session_id: session_id.to_string(),
trace_id: String::new(),
feature_name: String::new(),
current_phase: SddPhase::Spec,
status: SddStatus::Running,
started_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
})
}
async fn subscribe_events(&self) -> VisualResult<broadcast::Receiver<PhaseEvent>> {
Ok(self.events.subscribe())
}
async fn read_artifact(&self, _session_id: &str, _phase: SddPhase) -> VisualResult<String> {
Ok(String::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_mock_start() {
let facade = MockSddFacade::new();
let session = facade
.start("test-feature", "test requirement", SddPhase::Spec)
.await
.unwrap();
assert_eq!(session.feature_name, "test-feature");
assert_eq!(session.current_phase, SddPhase::Spec);
assert_eq!(session.status, SddStatus::Running);
}
#[tokio::test]
async fn test_mock_cancel() {
let facade = MockSddFacade::new();
let result = facade.cancel("session-1").await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_mock_subscribe_events() {
let facade = MockSddFacade::new();
let _rx = facade.subscribe_events().await.unwrap();
}
#[tokio::test]
async fn test_mock_read_artifact() {
let facade = MockSddFacade::new();
let artifact = facade
.read_artifact("session-1", SddPhase::Spec)
.await
.unwrap();
assert!(artifact.is_empty());
}
}