diff --git a/Cargo.lock b/Cargo.lock index 5814697..54cf126 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2174,6 +2174,7 @@ dependencies = [ "slack-morphism", "tokio", "tokio-util", + "tower", "tower-http", "tower-layer", "tracing", diff --git a/Cargo.toml b/Cargo.toml index b098189..0f1bb1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ serde_json = "1.0.115" slack-morphism = { version = "2.1.0", features = ["axum"] } tokio = { version = "1.36.0", features = ["full"] } tokio-util = { version = "0.7.10", features = ["rt"] } +tower = "0.4.13" tower-http = { version = "0.5.2", features = ["timeout", "trace"] } tower-layer = "0.3.2" tracing = "0.1.40" diff --git a/src/actors/gift_unwrapper.rs b/src/actors/gift_unwrapper.rs index ada90d4..35ea68c 100644 --- a/src/actors/gift_unwrapper.rs +++ b/src/actors/gift_unwrapper.rs @@ -110,7 +110,7 @@ mod tests { let messages_received = Arc::new(Mutex::new(Vec::::new())); let (receiver_actor_ref, receiver_actor_handle) = - Actor::spawn(None, TestActor::default(), messages_received.clone()) + Actor::spawn(None, TestActor::default(), Some(messages_received.clone())) .await .unwrap(); diff --git a/src/actors/relay_event_dispatcher.rs b/src/actors/relay_event_dispatcher.rs index c0a1c20..c0ba346 100644 --- a/src/actors/relay_event_dispatcher.rs +++ b/src/actors/relay_event_dispatcher.rs @@ -319,7 +319,7 @@ mod tests { let received_messages = Arc::new(Mutex::new(Vec::::new())); let (receiver_ref, receiver_handle) = - Actor::spawn(None, TestActor::default(), received_messages.clone()) + Actor::spawn(None, TestActor::default(), Some(received_messages.clone())) .await .unwrap(); diff --git a/src/actors/utilities/output_port_subscriber.rs b/src/actors/utilities/output_port_subscriber.rs index b250158..35f9907 100644 --- a/src/actors/utilities/output_port_subscriber.rs +++ b/src/actors/utilities/output_port_subscriber.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; pub type OutputPortSubscriber = Box>; -pub trait OutputPortSubscriberTrait: Debug + Send +pub trait OutputPortSubscriberTrait: Debug + Send + Sync where I: Send + Clone + Debug + 'static, { diff --git a/src/actors/utilities/test_actor.rs b/src/actors/utilities/test_actor.rs index 5477a23..1cf5e62 100644 --- a/src/actors/utilities/test_actor.rs +++ b/src/actors/utilities/test_actor.rs @@ -1,13 +1,24 @@ use anyhow::Result; +use ractor::SpawnErr; use ractor::{Actor, ActorProcessingErr, ActorRef}; use std::sync::Arc; use tokio::sync::Mutex; +use tokio::task::JoinHandle; pub type TestActorMessagesReceived = Arc>>; pub struct TestActor { _phantom: std::marker::PhantomData, } +impl TestActor +where + T: Send + Sync + 'static, +{ + pub async fn spawn_default() -> Result<(ActorRef, JoinHandle<()>), SpawnErr> { + Actor::spawn(None, TestActor::::default(), None).await + } +} + impl Default for TestActor { fn default() -> Self { Self { @@ -17,7 +28,7 @@ impl Default for TestActor { } pub struct TestActorState { - pub messages_received: TestActorMessagesReceived, + pub messages_received: Option>, } #[ractor::async_trait] @@ -27,12 +38,12 @@ where { type Msg = T; type State = TestActorState; - type Arguments = TestActorMessagesReceived; + type Arguments = Option>; async fn pre_start( &self, _: ActorRef, - messages_received: TestActorMessagesReceived, + messages_received: Option>, ) -> Result { let state = TestActorState { messages_received }; Ok(state) @@ -44,7 +55,10 @@ where message: Self::Msg, state: &mut Self::State, ) -> Result<(), ActorProcessingErr> { - state.messages_received.lock().await.push(message); + if let Some(messages_received) = &state.messages_received { + messages_received.lock().await.push(message); + }; + Ok(()) } } diff --git a/src/adapters/http_server/app_errors.rs b/src/adapters/http_server/app_errors.rs index 2db35f7..9f79f18 100644 --- a/src/adapters/http_server/app_errors.rs +++ b/src/adapters/http_server/app_errors.rs @@ -7,7 +7,6 @@ use axum::{ #[derive(Debug)] enum AppErrorKind { General(Error), - MissingResponseUrl, // TODO: Let's be more specific later SlackParsingError(String), } @@ -22,10 +21,6 @@ impl AppError { Self { kind } } - pub fn missing_response_url() -> Self { - Self::new(AppErrorKind::MissingResponseUrl) - } - pub fn slack_parsing_error(context: &str) -> Self { Self::new(AppErrorKind::SlackParsingError(context.to_string())) } @@ -39,9 +34,6 @@ impl IntoResponse for AppError { format!("Something went wrong: {}", err), ) .into_response(), - AppErrorKind::MissingResponseUrl => { - (StatusCode::BAD_REQUEST, "Missing response URL.".to_string()).into_response() - } AppErrorKind::SlackParsingError(context) => ( StatusCode::BAD_REQUEST, format!("Slack parsing error: {}.", context), diff --git a/src/adapters/http_server/slack_interactions_route.rs b/src/adapters/http_server/slack_interactions_route.rs index 7b3832c..f1087b5 100644 --- a/src/adapters/http_server/slack_interactions_route.rs +++ b/src/adapters/http_server/slack_interactions_route.rs @@ -20,15 +20,16 @@ pub fn slack_interactions_route() -> Result> { .context("Missing SLACK_SIGNING_SECRET") .map(|secret| secret.into())?; let listener = SlackEventsAxumListener::::new(listener_environment); + let slack_layer = listener + .events_layer(&signing_secret) + .with_event_extractor(SlackEventsExtractors::interaction_event()); - Ok(Router::new().route( + let route = Router::new().route( "/slack/interactions", - post(slack_interaction_handler).layer( - listener - .events_layer(&signing_secret) - .with_event_extractor(SlackEventsExtractors::interaction_event()), - ), - )) + post(slack_interaction_handler).layer(slack_layer), + ); + + Ok(route) } fn prepare_slack_client() -> Result> { @@ -117,7 +118,6 @@ fn parse_slack_action( .map_err(|_| AppError::slack_parsing_error("reporter_pubkey"))?; let report_request = ReportRequest::new(reported_event, reporter_pubkey, Some(reporter_text)); - let maybe_category = ModerationCategory::from_str(action_id).ok(); let maybe_moderated_report = report_request.report(maybe_category)?; @@ -188,7 +188,43 @@ fn slack_error_handler( #[cfg(test)] mod tests { use super::*; + use crate::actors::TestActor; + use axum::{ + body::Body, + http::{Request, StatusCode}, + }; + use handlebars::Handlebars; + use http_body_util::BodyExt; use serde_json::json; + use tower::ServiceExt; + + #[tokio::test] + async fn test_fails_with_empty_request() { + let (test_actor_ref, _receiver_actor_handle) = + TestActor::::spawn_default() + .await + .unwrap(); + let state = WebAppState { + event_dispatcher: test_actor_ref, + hb: Arc::new(Handlebars::new()), + }; + + let router = slack_interactions_route().unwrap().with_state(state); + + let response = router + .oneshot( + Request::builder() + .uri("/slack/interactions") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let body = response.into_body().collect().await.unwrap().to_bytes(); + assert!(body.is_empty()); + } #[test] fn test_parse_slack_action_with_hateful() { diff --git a/src/domain_objects/gift_wrap.rs b/src/domain_objects/gift_wrap.rs index ab29dc2..42d7745 100644 --- a/src/domain_objects/gift_wrap.rs +++ b/src/domain_objects/gift_wrap.rs @@ -12,6 +12,7 @@ impl GiftWrappedReportRequest { GiftWrappedReportRequest(event) } + #[allow(unused)] pub fn as_json(&self) -> String { self.0.as_json() } diff --git a/src/domain_objects/moderated_report.rs b/src/domain_objects/moderated_report.rs index fcdcb53..9c37694 100644 --- a/src/domain_objects/moderated_report.rs +++ b/src/domain_objects/moderated_report.rs @@ -58,10 +58,6 @@ impl ModeratedReport { pub fn event(&self) -> Event { self.event.clone() } - - pub fn to_json(&self) -> String { - serde_json::to_string(self).expect("Failed to serialize ModeratedReport to JSON") - } } impl Display for ModeratedReport {