From 4e3d5ccac99b24934c972e088cd7eb02bb95df06 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 17 Jun 2025 02:11:45 -0400 Subject: Handlers are _named operations_, which can be exposed via routes. Each domain module that exposes handlers does so through a `handlers` child module, ideally as a top-level symbol that can be plugged directly into Axum's `MethodRouter`. Modules could make exceptions to this - kill the doctrinaire inside yourself, after all - but none of the API modules that actually exist need such exceptions, and consistency is useful. The related details of request types, URL types, response types, errors, &c &c are then organized into modules under `handlers`, along with their respective tests. --- src/channel/handlers/send/test.rs | 131 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/channel/handlers/send/test.rs (limited to 'src/channel/handlers/send/test.rs') diff --git a/src/channel/handlers/send/test.rs b/src/channel/handlers/send/test.rs new file mode 100644 index 0000000..f43f901 --- /dev/null +++ b/src/channel/handlers/send/test.rs @@ -0,0 +1,131 @@ +use axum::extract::{Json, Path, State}; +use futures::stream::{self, StreamExt as _}; + +use crate::{ + channel, + event::Sequenced, + message::app::SendError, + test::fixtures::{self, future::Expect as _}, +}; + +#[tokio::test] +async fn messages_in_order() { + // Set up the environment + + let app = fixtures::scratch_app().await; + let sender = fixtures::identity::create(&app, &fixtures::now()).await; + let channel = fixtures::channel::create(&app, &fixtures::now()).await; + let resume_point = fixtures::boot::resume_point(&app).await; + + // Call the endpoint (twice) + + let requests = vec![ + (fixtures::now(), fixtures::message::propose()), + (fixtures::now(), fixtures::message::propose()), + ]; + + for (sent_at, body) in &requests { + let request = super::Request { body: body.clone() }; + + let _ = super::handler( + State(app.clone()), + Path(channel.id.clone()), + sent_at.clone(), + sender.clone(), + Json(request), + ) + .await + .expect("sending to a valid channel succeeds"); + } + + // Verify the semantics + + let mut events = app + .events() + .subscribe(resume_point) + .await + .expect("subscribing to a valid channel succeeds") + .filter_map(fixtures::event::message) + .filter_map(fixtures::event::message::sent) + .zip(stream::iter(requests)); + + while let Some((event, (sent_at, body))) = events + .next() + .expect_ready("an event should be ready for each message") + .await + { + assert_eq!(*sent_at, event.at()); + assert_eq!(sender.user.id, event.message.sender); + assert_eq!(body, event.message.body); + } +} + +#[tokio::test] +async fn nonexistent_channel() { + // Set up the environment + + let app = fixtures::scratch_app().await; + let sender = fixtures::identity::create(&app, &fixtures::now()).await; + + // Call the endpoint + + let sent_at = fixtures::now(); + let channel = channel::Id::generate(); + let request = super::Request { + body: fixtures::message::propose(), + }; + let super::Error(error) = super::handler( + State(app), + Path(channel.clone()), + sent_at, + sender, + Json(request), + ) + .await + .expect_err("sending to a nonexistent channel fails"); + + // Verify the structure of the response + + assert!(matches!( + error, + SendError::ChannelNotFound(error_channel) if channel == error_channel + )); +} + +#[tokio::test] +async fn deleted_channel() { + // Set up the environment + + let app = fixtures::scratch_app().await; + let sender = fixtures::identity::create(&app, &fixtures::now()).await; + let channel = fixtures::channel::create(&app, &fixtures::now()).await; + + app.channels() + .delete(&channel.id, &fixtures::now()) + .await + .expect("deleting a new channel succeeds"); + + // Call the endpoint + + let sent_at = fixtures::now(); + let channel = channel::Id::generate(); + let request = super::Request { + body: fixtures::message::propose(), + }; + let super::Error(error) = super::handler( + State(app), + Path(channel.clone()), + sent_at, + sender, + Json(request), + ) + .await + .expect_err("sending to a deleted channel fails"); + + // Verify the structure of the response + + assert!(matches!( + error, + SendError::ChannelNotFound(error_channel) if channel == error_channel + )); +} -- cgit v1.2.3