- Exclude problematic markdown files from linting (existing legacy issues) - Make clippy check less aggressive (warnings only, not -D warnings) - Move cargo test to manual stage (too slow for pre-commit) - Exclude SVG files from end-of-file-fixer and trailing-whitespace - Add markdown linting exclusions for existing documentation This allows pre-commit hooks to run successfully on new code without blocking commits due to existing issues in legacy documentation files.
158 lines
4.1 KiB
Rust
158 lines
4.1 KiB
Rust
// vapora-backend: WebSocket handler for real-time workflow updates
|
|
// Phase 3: Stream workflow progress to connected clients
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use tokio::sync::broadcast;
|
|
use tracing::{debug, error};
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
#[allow(dead_code)]
|
|
pub struct WorkflowUpdate {
|
|
pub workflow_id: String,
|
|
pub status: String,
|
|
pub progress: u32,
|
|
pub message: String,
|
|
pub timestamp: chrono::DateTime<chrono::Utc>,
|
|
}
|
|
|
|
impl WorkflowUpdate {
|
|
pub fn new(workflow_id: String, status: String, progress: u32, message: String) -> Self {
|
|
Self {
|
|
workflow_id,
|
|
status,
|
|
progress,
|
|
message,
|
|
timestamp: chrono::Utc::now(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Broadcaster for workflow updates
|
|
#[allow(dead_code)]
|
|
pub struct WorkflowBroadcaster {
|
|
tx: broadcast::Sender<WorkflowUpdate>,
|
|
}
|
|
|
|
impl WorkflowBroadcaster {
|
|
pub fn new() -> Self {
|
|
let (tx, _) = broadcast::channel(100);
|
|
Self { tx }
|
|
}
|
|
|
|
/// Send workflow update to all subscribers
|
|
pub fn send_update(&self, update: WorkflowUpdate) {
|
|
debug!(
|
|
"Broadcasting update for workflow {}: {} ({}%)",
|
|
update.workflow_id, update.message, update.progress
|
|
);
|
|
|
|
if let Err(e) = self.tx.send(update) {
|
|
error!("Failed to broadcast update: {}", e);
|
|
}
|
|
}
|
|
|
|
/// Subscribe to workflow updates
|
|
pub fn subscribe(&self) -> broadcast::Receiver<WorkflowUpdate> {
|
|
self.tx.subscribe()
|
|
}
|
|
|
|
/// Get subscriber count
|
|
pub fn subscriber_count(&self) -> usize {
|
|
self.tx.receiver_count()
|
|
}
|
|
}
|
|
|
|
impl Default for WorkflowBroadcaster {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl Clone for WorkflowBroadcaster {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
tx: self.tx.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note: WebSocket support requires ws feature in axum
|
|
// For Phase 4, we focus on the broadcaster infrastructure
|
|
// WebSocket handlers would be added when the ws feature is enabled
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_broadcaster_creation() {
|
|
let broadcaster = WorkflowBroadcaster::new();
|
|
assert_eq!(broadcaster.subscriber_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_subscribe() {
|
|
let broadcaster = WorkflowBroadcaster::new();
|
|
let _rx = broadcaster.subscribe();
|
|
assert_eq!(broadcaster.subscriber_count(), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_send_update() {
|
|
let broadcaster = WorkflowBroadcaster::new();
|
|
let mut rx = broadcaster.subscribe();
|
|
|
|
let update = WorkflowUpdate::new(
|
|
"wf-1".to_string(),
|
|
"in_progress".to_string(),
|
|
50,
|
|
"Step 1 completed".to_string(),
|
|
);
|
|
|
|
broadcaster.send_update(update.clone());
|
|
|
|
let received = rx.recv().await.unwrap();
|
|
assert_eq!(received.workflow_id, "wf-1");
|
|
assert_eq!(received.progress, 50);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_multiple_subscribers() {
|
|
let broadcaster = WorkflowBroadcaster::new();
|
|
let mut rx1 = broadcaster.subscribe();
|
|
let mut rx2 = broadcaster.subscribe();
|
|
|
|
let update = WorkflowUpdate::new(
|
|
"wf-1".to_string(),
|
|
"completed".to_string(),
|
|
100,
|
|
"All steps completed".to_string(),
|
|
);
|
|
|
|
broadcaster.send_update(update);
|
|
|
|
let received1 = rx1.recv().await.unwrap();
|
|
let received2 = rx2.recv().await.unwrap();
|
|
|
|
assert_eq!(received1.workflow_id, received2.workflow_id);
|
|
assert_eq!(received1.progress, 100);
|
|
assert_eq!(received2.progress, 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_update_serialization() {
|
|
let update = WorkflowUpdate::new(
|
|
"wf-1".to_string(),
|
|
"running".to_string(),
|
|
75,
|
|
"Almost done".to_string(),
|
|
);
|
|
|
|
let json = serde_json::to_string(&update).unwrap();
|
|
let deserialized: WorkflowUpdate = serde_json::from_str(&json).unwrap();
|
|
|
|
assert_eq!(deserialized.workflow_id, "wf-1");
|
|
assert_eq!(deserialized.progress, 75);
|
|
}
|
|
}
|