diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2024-09-19 01:25:31 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2024-09-20 23:55:22 -0400 |
| commit | e5f72711c5a17c5db24e209b14f82d426eceb86e (patch) | |
| tree | 04865172284c86549dd08d700c21a29c36f54005 /src/test | |
| parent | 0079624488af334817f58e30dbc676d3adde8de6 (diff) | |
Write tests.
Diffstat (limited to 'src/test')
| -rw-r--r-- | src/test/fixtures/channel.rs | 24 | ||||
| -rw-r--r-- | src/test/fixtures/error.rs | 14 | ||||
| -rw-r--r-- | src/test/fixtures/future.rs | 55 | ||||
| -rw-r--r-- | src/test/fixtures/identity.rs | 27 | ||||
| -rw-r--r-- | src/test/fixtures/login.rs | 44 | ||||
| -rw-r--r-- | src/test/fixtures/message.rs | 26 | ||||
| -rw-r--r-- | src/test/fixtures/mod.rs | 28 | ||||
| -rw-r--r-- | src/test/mod.rs | 1 |
8 files changed, 219 insertions, 0 deletions
diff --git a/src/test/fixtures/channel.rs b/src/test/fixtures/channel.rs new file mode 100644 index 0000000..0558395 --- /dev/null +++ b/src/test/fixtures/channel.rs @@ -0,0 +1,24 @@ +use faker_rand::{ + en_us::{addresses::CityName, names::FullName}, + faker_impl_from_templates, +}; +use rand; + +use crate::{app::App, repo::channel::Channel}; + +pub async fn create(app: &App) -> Channel { + let name = propose(); + app.channels() + .create(&name) + .await + .expect("should always succeed if the channel is actually new") +} + +pub fn propose() -> String { + rand::random::<Name>().to_string() +} + +struct Name(String); +faker_impl_from_templates! { + Name; "{} {}", CityName, FullName; +} diff --git a/src/test/fixtures/error.rs b/src/test/fixtures/error.rs new file mode 100644 index 0000000..559afee --- /dev/null +++ b/src/test/fixtures/error.rs @@ -0,0 +1,14 @@ +macro_rules! expected { + ($expr:expr, $expect:pat $(,)?) => { + $crate::test::fixtures::error::expected!($expr, $expect, ()) + }; + + ($expr:expr, $expect:pat, $body:expr $(,)?) => { + match $expr { + $expect => $body, + other => panic!("expected {}, found {other:#?}", stringify!($expect)), + } + }; +} + +pub(crate) use expected; diff --git a/src/test/fixtures/future.rs b/src/test/fixtures/future.rs new file mode 100644 index 0000000..bbdc9f8 --- /dev/null +++ b/src/test/fixtures/future.rs @@ -0,0 +1,55 @@ +use std::{future::IntoFuture, time::Duration}; + +use futures::{stream, Stream}; +use tokio::time::timeout; + +async fn immediately<F>(fut: F) -> F::Output +where + F: IntoFuture, +{ + // I haven't been particularly rigorous here. Zero delay _seems to work_, + // but this can be set higher; it makes tests that fail to meet the + // "immediate" expectation take longer, but gives slow tests time to + // succeed, as well. + let duration = Duration::from_nanos(0); + timeout(duration, fut) + .await + .expect("expected result immediately") +} + +// This is only intended for streams, since their `next()`, `collect()`, and +// so on can all block indefinitely on an empty stream. There's no need to +// force immediacy on futures that "can't" block forever, and it can hide logic +// errors if you do that. +// +// The impls below _could_ be replaced with a blanket impl for all future +// types, otherwise. The choice to restrict impls to stream futures is +// deliberate. +pub trait Immediately { + type Output; + + async fn immediately(self) -> Self::Output; +} + +impl<'a, St> Immediately for stream::Next<'a, St> +where + St: Stream + Unpin + ?Sized, +{ + type Output = Option<<St as Stream>::Item>; + + async fn immediately(self) -> Self::Output { + immediately(self).await + } +} + +impl<St, C> Immediately for stream::Collect<St, C> +where + St: Stream, + C: Default + Extend<<St as Stream>::Item>, +{ + type Output = C; + + async fn immediately(self) -> Self::Output { + immediately(self).await + } +} diff --git a/src/test/fixtures/identity.rs b/src/test/fixtures/identity.rs new file mode 100644 index 0000000..16463aa --- /dev/null +++ b/src/test/fixtures/identity.rs @@ -0,0 +1,27 @@ +use uuid::Uuid; + +use crate::{app::App, clock::RequestedAt, login::extract::IdentityToken}; + +pub fn not_logged_in() -> IdentityToken { + IdentityToken::new() +} + +pub async fn logged_in(app: &App, login: &(String, String), now: &RequestedAt) -> IdentityToken { + let (name, password) = login; + let token = app + .logins() + .login(name, password, now) + .await + .expect("should succeed given known-valid credentials"); + + IdentityToken::new().set(&token) +} + +pub fn secret(identity: &IdentityToken) -> &str { + identity.secret().expect("identity contained a secret") +} + +pub fn fictitious() -> IdentityToken { + let token = Uuid::new_v4().to_string(); + IdentityToken::new().set(&token) +} diff --git a/src/test/fixtures/login.rs b/src/test/fixtures/login.rs new file mode 100644 index 0000000..b2a4292 --- /dev/null +++ b/src/test/fixtures/login.rs @@ -0,0 +1,44 @@ +use faker_rand::en_us::internet; +use uuid::Uuid; + +use crate::{ + app::App, + repo::login::{self, Login}, +}; + +pub async fn create_for_login(app: &App) -> (String, String) { + let (name, password) = propose(); + app.logins() + .create(&name, &password) + .await + .expect("should always succeed if the login is actually new"); + + (name, password) +} + +pub async fn create(app: &App) -> Login { + let (name, password) = propose(); + app.logins() + .create(&name, &password) + .await + .expect("should always succeed if the login is actually new") +} + +pub fn fictitious() -> Login { + Login { + id: login::Id::generate(), + name: name(), + } +} + +pub fn propose() -> (String, String) { + (name(), propose_password()) +} + +fn name() -> String { + rand::random::<internet::Username>().to_string() +} + +pub fn propose_password() -> String { + Uuid::new_v4().to_string() +} diff --git a/src/test/fixtures/message.rs b/src/test/fixtures/message.rs new file mode 100644 index 0000000..7fe3cb9 --- /dev/null +++ b/src/test/fixtures/message.rs @@ -0,0 +1,26 @@ +use faker_rand::lorem::Paragraphs; + +use crate::{ + app::App, + clock::RequestedAt, + events::repo::broadcast, + repo::{channel::Channel, login::Login}, +}; + +pub async fn send( + app: &App, + login: &Login, + channel: &Channel, + sent_at: &RequestedAt, +) -> broadcast::Message { + let body = propose(); + + app.channels() + .send(login, &channel.id, &body, sent_at) + .await + .expect("should succeed if the channel exists") +} + +pub fn propose() -> String { + rand::random::<Paragraphs>().to_string() +} diff --git a/src/test/fixtures/mod.rs b/src/test/fixtures/mod.rs new file mode 100644 index 0000000..05e3f3f --- /dev/null +++ b/src/test/fixtures/mod.rs @@ -0,0 +1,28 @@ +use chrono::{TimeDelta, Utc}; + +use crate::{app::App, clock::RequestedAt, repo::pool}; + +pub mod channel; +pub mod error; +pub mod future; +pub mod identity; +pub mod login; +pub mod message; + +pub async fn scratch_app() -> App { + let pool = pool::prepare("sqlite::memory:") + .await + .expect("setting up in-memory sqlite database"); + App::from(pool) + .await + .expect("creating an app from a fresh, in-memory database") +} + +pub fn now() -> RequestedAt { + Utc::now().into() +} + +pub fn ancient() -> RequestedAt { + let timestamp = Utc::now() - TimeDelta::days(365); + timestamp.into() +} diff --git a/src/test/mod.rs b/src/test/mod.rs new file mode 100644 index 0000000..d066349 --- /dev/null +++ b/src/test/mod.rs @@ -0,0 +1 @@ +pub mod fixtures; |
