diff options
Diffstat (limited to 'src/channel/routes')
| -rw-r--r-- | src/channel/routes/channel/delete.rs | 56 | ||||
| -rw-r--r-- | src/channel/routes/channel/mod.rs | 9 | ||||
| -rw-r--r-- | src/channel/routes/channel/post.rs | 57 | ||||
| -rw-r--r-- | src/channel/routes/channel/test/delete.rs | 177 | ||||
| -rw-r--r-- | src/channel/routes/channel/test/mod.rs | 2 | ||||
| -rw-r--r-- | src/channel/routes/channel/test/post.rs | 131 | ||||
| -rw-r--r-- | src/channel/routes/mod.rs | 5 | ||||
| -rw-r--r-- | src/channel/routes/post.rs | 64 | ||||
| -rw-r--r-- | src/channel/routes/test.rs | 239 |
9 files changed, 0 insertions, 740 deletions
diff --git a/src/channel/routes/channel/delete.rs b/src/channel/routes/channel/delete.rs deleted file mode 100644 index 3db7772..0000000 --- a/src/channel/routes/channel/delete.rs +++ /dev/null @@ -1,56 +0,0 @@ -use axum::{ - extract::{Json, Path, State}, - http::StatusCode, - response::{self, IntoResponse}, -}; - -use crate::{ - app::App, - channel::{self, app}, - clock::RequestedAt, - error::{Internal, NotFound}, - token::extract::Identity, -}; - -pub async fn handler( - State(app): State<App>, - Path(channel): Path<super::PathInfo>, - RequestedAt(deleted_at): RequestedAt, - _: Identity, -) -> Result<Response, Error> { - app.channels().delete(&channel, &deleted_at).await?; - - Ok(Response { id: channel }) -} - -#[derive(Debug, serde::Serialize)] -pub struct Response { - pub id: channel::Id, -} - -impl IntoResponse for Response { - fn into_response(self) -> response::Response { - (StatusCode::ACCEPTED, Json(self)).into_response() - } -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct Error(#[from] pub app::DeleteError); - -impl IntoResponse for Error { - fn into_response(self) -> response::Response { - let Self(error) = self; - match error { - app::DeleteError::NotFound(_) | app::DeleteError::Deleted(_) => { - NotFound(error).into_response() - } - app::DeleteError::NotEmpty(_) => { - (StatusCode::CONFLICT, error.to_string()).into_response() - } - app::DeleteError::Name(_) | app::DeleteError::Database(_) => { - Internal::from(error).into_response() - } - } - } -} diff --git a/src/channel/routes/channel/mod.rs b/src/channel/routes/channel/mod.rs deleted file mode 100644 index 31a9142..0000000 --- a/src/channel/routes/channel/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::channel::Id; - -pub mod delete; -pub mod post; - -#[cfg(test)] -mod test; - -type PathInfo = Id; diff --git a/src/channel/routes/channel/post.rs b/src/channel/routes/channel/post.rs deleted file mode 100644 index 2547122..0000000 --- a/src/channel/routes/channel/post.rs +++ /dev/null @@ -1,57 +0,0 @@ -use axum::{ - extract::{Json, Path, State}, - http::StatusCode, - response::{self, IntoResponse}, -}; - -use crate::{ - app::App, - clock::RequestedAt, - error::{Internal, NotFound}, - message::{Body, Message, app::SendError}, - token::extract::Identity, -}; - -pub async fn handler( - State(app): State<App>, - Path(channel): Path<super::PathInfo>, - RequestedAt(sent_at): RequestedAt, - identity: Identity, - Json(request): Json<Request>, -) -> Result<Response, Error> { - let message = app - .messages() - .send(&channel, &identity.user, &sent_at, &request.body) - .await?; - - Ok(Response(message)) -} - -#[derive(serde::Deserialize)] -pub struct Request { - pub body: Body, -} - -#[derive(Debug)] -pub struct Response(pub Message); - -impl IntoResponse for Response { - fn into_response(self) -> response::Response { - let Self(message) = self; - (StatusCode::ACCEPTED, Json(message)).into_response() - } -} - -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct Error(#[from] pub SendError); - -impl IntoResponse for Error { - fn into_response(self) -> response::Response { - let Self(error) = self; - match error { - SendError::ChannelNotFound(_) => NotFound(error).into_response(), - SendError::Name(_) | SendError::Database(_) => Internal::from(error).into_response(), - } - } -} diff --git a/src/channel/routes/channel/test/delete.rs b/src/channel/routes/channel/test/delete.rs deleted file mode 100644 index bd9261d..0000000 --- a/src/channel/routes/channel/test/delete.rs +++ /dev/null @@ -1,177 +0,0 @@ -use axum::extract::{Path, State}; - -use crate::{ - channel::{app, routes::channel::delete}, - test::fixtures, -}; - -#[tokio::test] -pub async fn valid_channel() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let channel = fixtures::channel::create(&app, &fixtures::now()).await; - - // Send the request - - let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let response = delete::handler( - State(app.clone()), - Path(channel.id.clone()), - fixtures::now(), - deleter, - ) - .await - .expect("deleting a valid channel succeeds"); - - // Verify the response - - assert_eq!(channel.id, response.id); - - // Verify the semantics - - let snapshot = app.boot().snapshot().await.expect("boot always succeeds"); - assert!(!snapshot.channels.contains(&channel)); -} - -#[tokio::test] -pub async fn invalid_channel_id() { - // Set up the environment - - let app = fixtures::scratch_app().await; - - // Send the request - - let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let channel = fixtures::channel::fictitious(); - let delete::Error(error) = delete::handler( - State(app.clone()), - Path(channel.clone()), - fixtures::now(), - deleter, - ) - .await - .expect_err("deleting a nonexistent channel fails"); - - // Verify the response - - assert!(matches!(error, app::DeleteError::NotFound(id) if id == channel)); -} - -#[tokio::test] -pub async fn channel_deleted() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let channel = fixtures::channel::create(&app, &fixtures::now()).await; - - app.channels() - .delete(&channel.id, &fixtures::now()) - .await - .expect("deleting a recently-sent channel succeeds"); - - // Send the request - - let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( - State(app.clone()), - Path(channel.id.clone()), - fixtures::now(), - deleter, - ) - .await - .expect_err("deleting a deleted channel fails"); - - // Verify the response - - assert!(matches!(error, app::DeleteError::Deleted(id) if id == channel.id)); -} - -#[tokio::test] -pub async fn channel_expired() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let channel = fixtures::channel::create(&app, &fixtures::ancient()).await; - - app.channels() - .expire(&fixtures::now()) - .await - .expect("expiring channels always succeeds"); - - // Send the request - - let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( - State(app.clone()), - Path(channel.id.clone()), - fixtures::now(), - deleter, - ) - .await - .expect_err("deleting an expired channel fails"); - - // Verify the response - - assert!(matches!(error, app::DeleteError::Deleted(id) if id == channel.id)); -} - -#[tokio::test] -pub async fn channel_purged() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let channel = fixtures::channel::create(&app, &fixtures::ancient()).await; - - app.channels() - .expire(&fixtures::old()) - .await - .expect("expiring channels always succeeds"); - - app.channels() - .purge(&fixtures::now()) - .await - .expect("purging channels always succeeds"); - - // Send the request - - let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( - State(app.clone()), - Path(channel.id.clone()), - fixtures::now(), - deleter, - ) - .await - .expect_err("deleting a purged channel fails"); - - // Verify the response - - assert!(matches!(error, app::DeleteError::NotFound(id) if id == channel.id)); -} - -#[tokio::test] -pub async fn channel_not_empty() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let channel = fixtures::channel::create(&app, &fixtures::now()).await; - let sender = fixtures::user::create(&app, &fixtures::now()).await; - fixtures::message::send(&app, &channel, &sender, &fixtures::now()).await; - - // Send the request - - let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( - State(app.clone()), - Path(channel.id.clone()), - fixtures::now(), - deleter, - ) - .await - .expect_err("deleting a channel with messages fails"); - - // Verify the response - - assert!(matches!(error, app::DeleteError::NotEmpty(id) if id == channel.id)); -} diff --git a/src/channel/routes/channel/test/mod.rs b/src/channel/routes/channel/test/mod.rs deleted file mode 100644 index 78bf86e..0000000 --- a/src/channel/routes/channel/test/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod delete; -mod post; diff --git a/src/channel/routes/channel/test/post.rs b/src/channel/routes/channel/test/post.rs deleted file mode 100644 index d9527ac..0000000 --- a/src/channel/routes/channel/test/post.rs +++ /dev/null @@ -1,131 +0,0 @@ -use axum::extract::{Json, Path, State}; -use futures::stream::{self, StreamExt as _}; - -use crate::{ - channel::{self, routes::channel::post}, - 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 = post::Request { body: body.clone() }; - - let _ = post::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 = post::Request { - body: fixtures::message::propose(), - }; - let post::Error(error) = post::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 = post::Request { - body: fixtures::message::propose(), - }; - let post::Error(error) = post::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 - )); -} diff --git a/src/channel/routes/mod.rs b/src/channel/routes/mod.rs deleted file mode 100644 index bd90721..0000000 --- a/src/channel/routes/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod channel; -pub mod post; - -#[cfg(test)] -mod test; diff --git a/src/channel/routes/post.rs b/src/channel/routes/post.rs deleted file mode 100644 index 6ea9b61..0000000 --- a/src/channel/routes/post.rs +++ /dev/null @@ -1,64 +0,0 @@ -use axum::{ - extract::{Json, State}, - http::StatusCode, - response::{self, IntoResponse}, -}; - -use crate::{ - app::App, - channel::{Channel, app}, - clock::RequestedAt, - error::Internal, - name::Name, - token::extract::Identity, -}; - -pub async fn handler( - State(app): State<App>, - _: Identity, // requires auth, but doesn't actually care who you are - RequestedAt(created_at): RequestedAt, - Json(request): Json<Request>, -) -> Result<Response, Error> { - let channel = app - .channels() - .create(&request.name, &created_at) - .await - .map_err(Error)?; - - Ok(Response(channel)) -} - -#[derive(serde::Deserialize)] -pub struct Request { - pub name: Name, -} - -#[derive(Debug)] -pub struct Response(pub Channel); - -impl IntoResponse for Response { - fn into_response(self) -> response::Response { - let Self(channel) = self; - (StatusCode::ACCEPTED, Json(channel)).into_response() - } -} - -#[derive(Debug)] -pub struct Error(pub app::CreateError); - -impl IntoResponse for Error { - fn into_response(self) -> response::Response { - let Self(error) = self; - match error { - app::CreateError::DuplicateName(_) => { - (StatusCode::CONFLICT, error.to_string()).into_response() - } - app::CreateError::InvalidName(_) => { - (StatusCode::BAD_REQUEST, error.to_string()).into_response() - } - app::CreateError::Name(_) | app::CreateError::Database(_) => { - Internal::from(error).into_response() - } - } - } -} diff --git a/src/channel/routes/test.rs b/src/channel/routes/test.rs deleted file mode 100644 index cba8f2e..0000000 --- a/src/channel/routes/test.rs +++ /dev/null @@ -1,239 +0,0 @@ -use std::future; - -use axum::extract::{Json, State}; -use futures::stream::StreamExt as _; - -use super::post; -use crate::{ - channel::app, - name::Name, - test::fixtures::{self, future::Expect as _}, -}; - -#[tokio::test] -async fn new_channel() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let creator = fixtures::identity::create(&app, &fixtures::now()).await; - let resume_point = fixtures::boot::resume_point(&app).await; - - // Call the endpoint - - let name = fixtures::channel::propose(); - let request = post::Request { name: name.clone() }; - let post::Response(response) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect("creating a channel in an empty app succeeds"); - - // Verify the structure of the response - - assert_eq!(name, response.name); - - // Verify the semantics - - let snapshot = app.boot().snapshot().await.expect("boot always succeeds"); - assert!(snapshot.channels.iter().any(|channel| channel == &response)); - - let channel = app - .channels() - .get(&response.id) - .await - .expect("the newly-created channel exists"); - assert_eq!(response, channel); - - let mut events = app - .events() - .subscribe(resume_point) - .await - .expect("subscribing never fails") - .filter_map(fixtures::event::channel) - .filter_map(fixtures::event::channel::created) - .filter(|event| future::ready(event.channel == response)); - - let event = events.next().expect_some("creation event published").await; - - assert_eq!(event.channel, response); -} - -#[tokio::test] -async fn duplicate_name() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let creator = fixtures::identity::create(&app, &fixtures::now()).await; - let channel = fixtures::channel::create(&app, &fixtures::now()).await; - - // Call the endpoint - - let request = post::Request { - name: channel.name.clone(), - }; - let post::Error(error) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect_err("duplicate channel name should fail the request"); - - // Verify the structure of the response - - assert!(matches!( - error, - app::CreateError::DuplicateName(name) if channel.name == name - )); -} - -#[tokio::test] -async fn conflicting_canonical_name() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let creator = fixtures::identity::create(&app, &fixtures::now()).await; - - let existing_name = Name::from("rijksmuseum"); - app.channels() - .create(&existing_name, &fixtures::now()) - .await - .expect("creating a channel in an empty environment succeeds"); - - let conflicting_name = Name::from("r\u{0133}ksmuseum"); - - // Call the endpoint - - let request = post::Request { - name: conflicting_name.clone(), - }; - let post::Error(error) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect_err("duplicate channel name should fail the request"); - - // Verify the structure of the response - - assert!(matches!( - error, - app::CreateError::DuplicateName(name) if conflicting_name == name - )); -} - -#[tokio::test] -async fn invalid_name() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let creator = fixtures::identity::create(&app, &fixtures::now()).await; - - // Call the endpoint - - let name = fixtures::channel::propose_invalid_name(); - let request = post::Request { name: name.clone() }; - let post::Error(error) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect_err("invalid channel name should fail the request"); - - // Verify the structure of the response - - assert!(matches!( - error, - app::CreateError::InvalidName(error_name) if name == error_name - )); -} - -#[tokio::test] -async fn name_reusable_after_delete() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let creator = fixtures::identity::create(&app, &fixtures::now()).await; - let name = fixtures::channel::propose(); - - // Call the endpoint (first time) - - let request = post::Request { name: name.clone() }; - let post::Response(response) = post::handler( - State(app.clone()), - creator.clone(), - fixtures::now(), - Json(request), - ) - .await - .expect("new channel in an empty app"); - - // Delete the channel - - app.channels() - .delete(&response.id, &fixtures::now()) - .await - .expect("deleting a newly-created channel succeeds"); - - // Call the endpoint (second time) - - let request = post::Request { name: name.clone() }; - let post::Response(response) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect("creation succeeds after original channel deleted"); - - // Verify the structure of the response - - assert_eq!(name, response.name); - - // Verify the semantics - - let channel = app - .channels() - .get(&response.id) - .await - .expect("the newly-created channel exists"); - assert_eq!(response, channel); -} - -#[tokio::test] -async fn name_reusable_after_expiry() { - // Set up the environment - - let app = fixtures::scratch_app().await; - let creator = fixtures::identity::create(&app, &fixtures::ancient()).await; - let name = fixtures::channel::propose(); - - // Call the endpoint (first time) - - let request = post::Request { name: name.clone() }; - let post::Response(_) = post::handler( - State(app.clone()), - creator.clone(), - fixtures::ancient(), - Json(request), - ) - .await - .expect("new channel in an empty app"); - - // Delete the channel - - app.channels() - .expire(&fixtures::now()) - .await - .expect("expiry always succeeds"); - - // Call the endpoint (second time) - - let request = post::Request { name: name.clone() }; - let post::Response(response) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect("creation succeeds after original channel expired"); - - // Verify the structure of the response - - assert_eq!(name, response.name); - - // Verify the semantics - - let channel = app - .channels() - .get(&response.id) - .await - .expect("the newly-created channel exists"); - assert_eq!(response, channel); -} |
