storkit: merge 396_story_whatsapp_bot_startup_announcement_after_restart

This commit is contained in:
dave
2026-03-26 20:19:57 +00:00
parent 33cb363651
commit c4cee72938
2 changed files with 141 additions and 0 deletions
+104
View File
@@ -43,6 +43,19 @@ impl BotShutdownNotifier {
}
}
/// Send a startup announcement to all configured channels.
///
/// Called once per process start so users know the bot is online.
/// Errors are logged and ignored — startup is never blocked by a failed send.
pub async fn notify_startup(&self) {
let msg = format!("{} is online.", self.bot_name);
for channel in &self.channels {
if let Err(e) = self.transport.send_message(channel, &msg, &msg).await {
slog!("[startup] Failed to send startup message to {channel}: {e}");
}
}
}
/// Send a shutdown message to all configured channels.
///
/// Errors from individual sends are logged and ignored so that a single
@@ -345,4 +358,95 @@ mod tests {
notifier.notify(ShutdownReason::Manual).await;
assert!(transport.messages().is_empty());
}
// -- notify_startup -------------------------------------------------------
#[tokio::test]
async fn notify_startup_sends_online_message_to_all_channels() {
let transport = Arc::new(CapturingTransport::new());
let notifier = BotShutdownNotifier::new(
Arc::clone(&transport) as Arc<dyn ChatTransport>,
vec!["#channel1".to_string(), "#channel2".to_string()],
"Timmy".to_string(),
);
notifier.notify_startup().await;
let msgs = transport.messages();
assert_eq!(msgs.len(), 2);
assert_eq!(msgs[0].0, "#channel1");
assert_eq!(msgs[1].0, "#channel2");
assert!(
msgs[0].1.contains("online"),
"expected 'online' in startup message: {}",
msgs[0].1
);
assert!(
msgs[0].1.contains("Timmy"),
"expected bot name in startup message: {}",
msgs[0].1
);
}
#[tokio::test]
async fn notify_startup_message_uses_bot_name() {
let transport = Arc::new(CapturingTransport::new());
let notifier = BotShutdownNotifier::new(
Arc::clone(&transport) as Arc<dyn ChatTransport>,
vec!["#general".to_string()],
"HAL".to_string(),
);
notifier.notify_startup().await;
let msgs = transport.messages();
assert_eq!(msgs[0].1, "HAL is online.");
}
#[tokio::test]
async fn notify_startup_with_no_channels_is_noop() {
let transport = Arc::new(CapturingTransport::new());
let notifier = BotShutdownNotifier::new(
Arc::clone(&transport) as Arc<dyn ChatTransport>,
vec![],
"Timmy".to_string(),
);
notifier.notify_startup().await;
assert!(transport.messages().is_empty());
}
#[tokio::test]
async fn notify_startup_is_best_effort_failing_send_does_not_panic() {
let transport = Arc::new(CapturingTransport::failing());
let notifier = BotShutdownNotifier::new(
Arc::clone(&transport) as Arc<dyn ChatTransport>,
vec!["#channel".to_string()],
"Timmy".to_string(),
);
// Should complete without panicking.
notifier.notify_startup().await;
}
#[tokio::test]
async fn notify_startup_message_differs_from_shutdown_message() {
let transport_start = Arc::new(CapturingTransport::new());
let notifier_start = BotShutdownNotifier::new(
Arc::clone(&transport_start) as Arc<dyn ChatTransport>,
vec!["C1".to_string()],
"Bot".to_string(),
);
notifier_start.notify_startup().await;
let transport_stop = Arc::new(CapturingTransport::new());
let notifier_stop = BotShutdownNotifier::new(
Arc::clone(&transport_stop) as Arc<dyn ChatTransport>,
vec!["C1".to_string()],
"Bot".to_string(),
);
notifier_stop.notify(ShutdownReason::Manual).await;
let startup_msg = &transport_start.messages()[0].1;
let shutdown_msg = &transport_stop.messages()[0].1;
assert_ne!(startup_msg, shutdown_msg, "startup and shutdown messages must differ");
}
}