use std::sync::Arc;
use async_trait::async_trait;
use road_runner_common::notification::{
Audience, AudienceError, AudienceResolver, NotificationEvent, NotifyError, Notifier,
RecipientRef,
};
struct Ping;
impl NotificationEvent for Ping {
fn workflow(&self) -> &str {
"ping"
}
fn payload(&self) -> serde_json::Value {
serde_json::json!({})
}
}
struct Unavailable;
#[async_trait]
impl AudienceResolver for Unavailable {
async fn resolve(&self, reference: &RecipientRef) -> Result<Audience, AudienceError> {
Err(AudienceError::Unavailable {
reference: reference.log_label(),
message: "cex-account is down".into(),
})
}
}
#[tokio::test]
async fn an_unresolvable_audience_fails_before_any_trigger() {
let notifier = Notifier::new(
"http://notification.invalid/admin/api/v1/events/trigger",
Arc::new(Unavailable),
);
let error = notifier
.notify(&RecipientRef::AccountId(42), &Ping)
.await
.expect_err("resolution failed, so the trigger cannot proceed");
match error {
NotifyError::Audience(AudienceError::Unavailable { reference, .. }) => {
assert_eq!(reference, "accountId=42");
}
other => panic!("expected an audience error, got {other:?}"),
}
}
#[tokio::test]
async fn best_effort_delivery_swallows_a_resolution_failure() {
let notifier = Notifier::new(
"http://notification.invalid/admin/api/v1/events/trigger",
Arc::new(Unavailable),
);
notifier
.notify_best_effort(&RecipientRef::AccountId(42), &Ping)
.await;
}
#[tokio::test]
async fn an_admin_channel_never_touches_the_audience_resolver() {
use road_runner_common::notification::{AdminChannel, AdminTopics};
let notifier = Notifier::new(
"http://127.0.0.1:1/admin/api/v1/events/trigger",
Arc::new(Unavailable),
)
.with_admin_topics(AdminTopics::new("topic-1", "topic-2"));
let error = notifier
.notify(&RecipientRef::Admin(AdminChannel::Alert), &Ping)
.await
.expect_err("nothing is listening on port 1");
assert!(
matches!(error, NotifyError::Trigger(_)),
"expected a transport error, got {error:?}"
);
}