diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2024-10-24 20:17:55 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2024-10-24 20:17:55 -0400 |
| commit | 6321980ac49525d6d56f1fec67f9008e253a9c39 (patch) | |
| tree | 79581c5d1f546e5ad745c1e06e2356a17c43439f /src | |
| parent | f9cbf95e5b850a7407c34f936c0f858520682a5d (diff) | |
Tests for accepting invites
Diffstat (limited to 'src')
| -rw-r--r-- | src/invite/routes/invite/post.rs | 3 | ||||
| -rw-r--r-- | src/invite/routes/invite/test/mod.rs | 1 | ||||
| -rw-r--r-- | src/invite/routes/invite/test/post.rs | 208 | ||||
| -rw-r--r-- | src/login/password.rs | 2 |
4 files changed, 212 insertions, 2 deletions
diff --git a/src/invite/routes/invite/post.rs b/src/invite/routes/invite/post.rs index 3ca4e6b..0dd8dba 100644 --- a/src/invite/routes/invite/post.rs +++ b/src/invite/routes/invite/post.rs @@ -36,7 +36,8 @@ pub struct Request { pub password: Password, } -pub struct Error(app::AcceptError); +#[derive(Debug)] +pub struct Error(pub app::AcceptError); impl IntoResponse for Error { fn into_response(self) -> Response { diff --git a/src/invite/routes/invite/test/mod.rs b/src/invite/routes/invite/test/mod.rs index a608fbb..d6c1f06 100644 --- a/src/invite/routes/invite/test/mod.rs +++ b/src/invite/routes/invite/test/mod.rs @@ -1 +1,2 @@ mod get; +mod post; diff --git a/src/invite/routes/invite/test/post.rs b/src/invite/routes/invite/test/post.rs new file mode 100644 index 0000000..65ab61e --- /dev/null +++ b/src/invite/routes/invite/test/post.rs @@ -0,0 +1,208 @@ +use axum::extract::{Json, Path, State}; + +use crate::{ + invite::{app::AcceptError, routes::invite::post}, + name::Name, + test::fixtures, +}; + +#[tokio::test] +async fn valid_invite() { + // Set up the environment + + let app = fixtures::scratch_app().await; + let issuer = fixtures::login::create(&app, &fixtures::now()).await; + let invite = fixtures::invite::issue(&app, &issuer, &fixtures::now()).await; + + // Call the endpoint + + let (name, password) = fixtures::login::propose(); + let identity = fixtures::cookie::not_logged_in(); + let request = post::Request { + name: name.clone(), + password: password.clone(), + }; + let (identity, Json(response)) = post::handler( + State(app.clone()), + fixtures::now(), + identity, + Path(invite.id), + Json(request), + ) + .await + .expect("accepting a valid invite succeeds"); + + // Verify the response + + assert!(identity.secret().is_some()); + assert_eq!(name, response.name); + + // Verify that the issued token is valid + + let secret = identity + .secret() + .expect("newly-issued identity has a token secret"); + let (_, login) = app + .tokens() + .validate(&secret, &fixtures::now()) + .await + .expect("newly-issued identity cookie is valid"); + assert_eq!(response, login); + + // Verify that the given credentials can log in + + let (login, _) = app + .tokens() + .login(&name, &password, &fixtures::now()) + .await + .expect("credentials given on signup are valid"); + assert_eq!(response, login); +} + +#[tokio::test] +async fn nonexistent_invite() { + // Set up the environment + + let app = fixtures::scratch_app().await; + let invite = fixtures::invite::fictitious(); + + // Call the endpoint + + let (name, password) = fixtures::login::propose(); + let identity = fixtures::cookie::not_logged_in(); + let request = post::Request { + name: name.clone(), + password: password.clone(), + }; + let post::Error(error) = post::handler( + State(app.clone()), + fixtures::now(), + identity, + Path(invite.clone()), + Json(request), + ) + .await + .expect_err("accepting a nonexistent invite fails"); + + // Verify the response + + assert!(matches!(error, AcceptError::NotFound(error_id) if error_id == invite)); +} + +#[tokio::test] +async fn expired_invite() { + // Set up the environment + + let app = fixtures::scratch_app().await; + let issuer = fixtures::login::create(&app, &fixtures::ancient()).await; + let invite = fixtures::invite::issue(&app, &issuer, &fixtures::ancient()).await; + + app.invites() + .expire(&fixtures::now()) + .await + .expect("expiring invites never fails"); + + // Call the endpoint + + let (name, password) = fixtures::login::propose(); + let identity = fixtures::cookie::not_logged_in(); + let request = post::Request { + name: name.clone(), + password: password.clone(), + }; + let post::Error(error) = post::handler( + State(app.clone()), + fixtures::now(), + identity, + Path(invite.id.clone()), + Json(request), + ) + .await + .expect_err("accepting a nonexistent invite fails"); + + // Verify the response + + assert!(matches!(error, AcceptError::NotFound(error_id) if error_id == invite.id)); +} + +#[tokio::test] +async fn accepted_invite() { + // Set up the environment + + let app = fixtures::scratch_app().await; + let issuer = fixtures::login::create(&app, &fixtures::ancient()).await; + let invite = fixtures::invite::issue(&app, &issuer, &fixtures::ancient()).await; + + let (name, password) = fixtures::login::propose(); + app.invites() + .accept(&invite.id, &name, &password, &fixtures::now()) + .await + .expect("accepting a valid invite succeeds"); + + // Call the endpoint + + let (name, password) = fixtures::login::propose(); + let identity = fixtures::cookie::not_logged_in(); + let request = post::Request { + name: name.clone(), + password: password.clone(), + }; + let post::Error(error) = post::handler( + State(app.clone()), + fixtures::now(), + identity, + Path(invite.id.clone()), + Json(request), + ) + .await + .expect_err("accepting a nonexistent invite fails"); + + // Verify the response + + assert!(matches!(error, AcceptError::NotFound(error_id) if error_id == invite.id)); +} + +#[tokio::test] +async fn conflicting_name() { + // Set up the environment + + let app = fixtures::scratch_app().await; + let issuer = fixtures::login::create(&app, &fixtures::ancient()).await; + let invite = fixtures::invite::issue(&app, &issuer, &fixtures::ancient()).await; + + let existing_name = Name::from("rijksmuseum"); + app.logins() + .create( + &existing_name, + &fixtures::login::propose_password(), + &fixtures::now(), + ) + .await + .expect("creating a login in an empty environment succeeds"); + + // Call the endpoint + + let conflicting_name = Name::from("r\u{0133}ksmuseum"); + let password = fixtures::login::propose_password(); + + let identity = fixtures::cookie::not_logged_in(); + let request = post::Request { + name: conflicting_name.clone(), + password: password.clone(), + }; + let post::Error(error) = post::handler( + State(app.clone()), + fixtures::now(), + identity, + Path(invite.id.clone()), + Json(request), + ) + .await + .expect_err("accepting a nonexistent invite fails"); + + // Verify the response + + assert!( + matches!(error, AcceptError::DuplicateLogin(error_name) if error_name == conflicting_name) + ); +} diff --git a/src/login/password.rs b/src/login/password.rs index c27c950..e1d164e 100644 --- a/src/login/password.rs +++ b/src/login/password.rs @@ -31,7 +31,7 @@ impl fmt::Debug for StoredHash { } } -#[derive(serde::Deserialize)] +#[derive(Clone, serde::Deserialize)] #[serde(transparent)] pub struct Password(nfc::String); |
