huskies: merge 762
This commit is contained in:
+173
-1
@@ -14,7 +14,8 @@ use std::sync::Arc;
|
||||
pub use crate::service::gateway::{
|
||||
GatewayConfig, GatewayState as GatewayStateType, GatewayStatusEvent, JoinedAgent, ProjectEntry,
|
||||
broadcast_status_event, fetch_all_project_pipeline_statuses, format_aggregate_status_compact,
|
||||
spawn_gateway_notification_poller, subscribe_status_events,
|
||||
spawn_gateway_broadcaster_forwarder, spawn_gateway_notification_poller,
|
||||
subscribe_status_events,
|
||||
};
|
||||
|
||||
/// Build the complete gateway route tree.
|
||||
@@ -130,6 +131,7 @@ pub async fn run(config_path: &Path, port: u16) -> Result<(), std::io::Error> {
|
||||
gateway_projects,
|
||||
gateway_project_urls,
|
||||
port,
|
||||
Some(state_arc.event_tx.clone()),
|
||||
);
|
||||
*state_arc.bot_handle.lock().await = bot_abort;
|
||||
|
||||
@@ -976,6 +978,176 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Gateway broadcaster forwarder tests ─────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn broadcaster_forwarder_forwards_events_with_project_tag() {
|
||||
use crate::chat::{ChatTransport, MessageId};
|
||||
use crate::service::events::StoredEvent;
|
||||
use async_trait::async_trait;
|
||||
|
||||
type CallLog = Arc<std::sync::Mutex<Vec<(String, String)>>>;
|
||||
|
||||
struct MockTransport {
|
||||
calls: CallLog,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ChatTransport for MockTransport {
|
||||
async fn send_message(
|
||||
&self,
|
||||
room_id: &str,
|
||||
plain: &str,
|
||||
_html: &str,
|
||||
) -> Result<MessageId, String> {
|
||||
self.calls
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((room_id.to_string(), plain.to_string()));
|
||||
Ok("id".to_string())
|
||||
}
|
||||
|
||||
async fn edit_message(
|
||||
&self,
|
||||
_room_id: &str,
|
||||
_id: &str,
|
||||
_plain: &str,
|
||||
_html: &str,
|
||||
) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_typing(&self, _room_id: &str, _typing: bool) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let calls: CallLog = Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
let transport = Arc::new(MockTransport {
|
||||
calls: Arc::clone(&calls),
|
||||
});
|
||||
|
||||
let (tx, rx) =
|
||||
tokio::sync::broadcast::channel::<crate::service::gateway::GatewayStatusEvent>(16);
|
||||
gateway::spawn_gateway_broadcaster_forwarder(
|
||||
transport as Arc<dyn crate::chat::ChatTransport>,
|
||||
vec!["!room:example.org".to_string()],
|
||||
rx,
|
||||
);
|
||||
|
||||
// Give the forwarder task a moment to start.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
|
||||
let event = crate::service::gateway::GatewayStatusEvent {
|
||||
project: "my-project".to_string(),
|
||||
event: StoredEvent::StageTransition {
|
||||
story_id: "7_story_x".to_string(),
|
||||
from_stage: "2_current".to_string(),
|
||||
to_stage: "3_qa".to_string(),
|
||||
timestamp_ms: 100,
|
||||
},
|
||||
};
|
||||
tx.send(event).unwrap();
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
let messages = calls.lock().unwrap();
|
||||
assert_eq!(messages.len(), 1, "Expected exactly one notification");
|
||||
let (room, plain) = &messages[0];
|
||||
assert_eq!(room, "!room:example.org");
|
||||
assert!(
|
||||
plain.starts_with("[my-project]"),
|
||||
"Expected [my-project] prefix; got: {plain}"
|
||||
);
|
||||
assert!(
|
||||
plain.contains("7_story_x"),
|
||||
"Expected story ID; got: {plain}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn broadcaster_forwarder_resubscribes_on_lag() {
|
||||
use crate::chat::{ChatTransport, MessageId};
|
||||
use crate::service::events::StoredEvent;
|
||||
use async_trait::async_trait;
|
||||
|
||||
type Counter = Arc<std::sync::Mutex<usize>>;
|
||||
|
||||
struct CountTransport {
|
||||
count: Counter,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ChatTransport for CountTransport {
|
||||
async fn send_message(
|
||||
&self,
|
||||
_room_id: &str,
|
||||
_plain: &str,
|
||||
_html: &str,
|
||||
) -> Result<MessageId, String> {
|
||||
*self.count.lock().unwrap() += 1;
|
||||
Ok("id".to_string())
|
||||
}
|
||||
|
||||
async fn edit_message(
|
||||
&self,
|
||||
_room_id: &str,
|
||||
_id: &str,
|
||||
_plain: &str,
|
||||
_html: &str,
|
||||
) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_typing(&self, _room_id: &str, _typing: bool) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let count: Counter = Arc::new(std::sync::Mutex::new(0));
|
||||
let transport = Arc::new(CountTransport {
|
||||
count: Arc::clone(&count),
|
||||
});
|
||||
|
||||
// Use a tiny channel (capacity 1) so the second send causes a Lagged error.
|
||||
let (tx, rx) =
|
||||
tokio::sync::broadcast::channel::<crate::service::gateway::GatewayStatusEvent>(1);
|
||||
|
||||
// Flood the channel to trigger Lagged before the forwarder task starts.
|
||||
let make_event = |n: u64| crate::service::gateway::GatewayStatusEvent {
|
||||
project: "p".to_string(),
|
||||
event: StoredEvent::StageTransition {
|
||||
story_id: format!("{n}_story"),
|
||||
from_stage: "2_current".to_string(),
|
||||
to_stage: "3_qa".to_string(),
|
||||
timestamp_ms: n,
|
||||
},
|
||||
};
|
||||
// Send 3 events to overflow the capacity-1 channel before the task runs.
|
||||
let _ = tx.send(make_event(1));
|
||||
let _ = tx.send(make_event(2));
|
||||
let _ = tx.send(make_event(3));
|
||||
|
||||
gateway::spawn_gateway_broadcaster_forwarder(
|
||||
transport as Arc<dyn crate::chat::ChatTransport>,
|
||||
vec!["!r:x.org".to_string()],
|
||||
rx,
|
||||
);
|
||||
|
||||
// Send one more event after the forwarder subscribes; it should arrive.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
tx.send(make_event(4)).unwrap();
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
// After Lagged + resubscribe, the forwarder must still process event 4.
|
||||
let received = *count.lock().unwrap();
|
||||
assert!(
|
||||
received >= 1,
|
||||
"Expected at least one event after Lagged resubscribe; got {received}"
|
||||
);
|
||||
}
|
||||
|
||||
// ── BotConfig tests ─────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user