use std::future; use axum::extract::{Json, State}; use futures::stream::StreamExt as _; use itertools::Itertools; use crate::{ conversation::app, name::Name, test::fixtures::{self, future::Expect as _}, }; #[tokio::test] async fn new_conversation() { // 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::conversation::propose(); let request = super::Request { name: name.clone() }; let super::Response(response) = super::handler( State(app.conversations()), creator, fixtures::now(), Json(request), ) .await .expect("creating a conversation 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"); let created = snapshot .events .into_iter() .filter_map(fixtures::event::conversation) .filter_map(fixtures::event::conversation::created) .exactly_one() .expect("only one conversation has been created"); assert_eq!(response, created.conversation); let conversation = app .conversations() .get(&response.id) .await .expect("the newly-created conversation exists"); assert_eq!(response, conversation); let mut events = app .events() .subscribe(resume_point) .await .expect("subscribing never fails") .filter_map(fixtures::event::stream::conversation) .filter_map(fixtures::event::stream::conversation::created) .filter(|event| future::ready(event.conversation == response)); let event = events.next().expect_some("creation event published").await; assert_eq!(event.conversation, 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 conversation = fixtures::conversation::create(&app, &fixtures::now()).await; // Call the endpoint let request = super::Request { name: conversation.name.clone(), }; let super::Error(error) = super::handler( State(app.conversations()), creator, fixtures::now(), Json(request), ) .await .expect_err("duplicate conversation name should fail the request"); // Verify the structure of the response assert!(matches!( error, app::CreateError::DuplicateName(name) if conversation.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.conversations() .create(&existing_name, &fixtures::now()) .await .expect("creating a conversation in an empty environment succeeds"); let conflicting_name = Name::from("r\u{0133}ksmuseum"); // Call the endpoint let request = super::Request { name: conflicting_name.clone(), }; let super::Error(error) = super::handler( State(app.conversations()), creator, fixtures::now(), Json(request), ) .await .expect_err("duplicate conversation 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::conversation::propose_invalid_name(); let request = super::Request { name: name.clone() }; let super::Error(error) = crate::conversation::handlers::create::handler( State(app.conversations()), creator, fixtures::now(), Json(request), ) .await .expect_err("invalid conversation 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::conversation::propose(); // Call the endpoint (first time) let request = super::Request { name: name.clone() }; let super::Response(response) = super::handler( State(app.conversations()), creator.clone(), fixtures::now(), Json(request), ) .await .expect("new conversation in an empty app"); // Delete the conversation app.conversations() .delete(&response.id, &fixtures::now()) .await .expect("deleting a newly-created conversation succeeds"); // Call the endpoint (second time) let request = super::Request { name: name.clone() }; let super::Response(response) = super::handler( State(app.conversations()), creator, fixtures::now(), Json(request), ) .await .expect("creation succeeds after original conversation deleted"); // Verify the structure of the response assert_eq!(name, response.name); // Verify the semantics let conversation = app .conversations() .get(&response.id) .await .expect("the newly-created conversation exists"); assert_eq!(response, conversation); } #[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::conversation::propose(); // Call the endpoint (first time) let request = super::Request { name: name.clone() }; let super::Response(_) = super::handler( State(app.conversations()), creator.clone(), fixtures::ancient(), Json(request), ) .await .expect("new conversation in an empty app"); // Expire the conversation app.conversations() .expire(&fixtures::now()) .await .expect("expiry always succeeds"); // Call the endpoint (second time) let request = super::Request { name: name.clone() }; let super::Response(response) = super::handler( State(app.conversations()), creator, fixtures::now(), Json(request), ) .await .expect("creation succeeds after original conversation expired"); // Verify the structure of the response assert_eq!(name, response.name); // Verify the semantics let conversation = app .conversations() .get(&response.id) .await .expect("the newly-created conversation exists"); assert_eq!(response, conversation); }