summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@elliehuxtable.com>2024-01-29 13:06:47 +0000
committerGitHub <noreply@github.com>2024-01-29 13:06:47 +0000
commit15bad15f48fa714a7d99ac782cec9b5d11cf336b (patch)
tree59c78cd5d1a1b65d39ef59a076abcc0d37fde8a7
parente53c7c9dd61fb6a4f80dc78e7bcbbd23172812c2 (diff)
test: add multi-user integration tests (#1648)
1. Test that multiple users can be registered without clobbering each other 2. Test that one user can change their password without it affecting the other I'd like to also test sync with multiple users, to ensure we never accidentally leak data cross-users.
-rw-r--r--atuin/tests/common/mod.rs99
-rw-r--r--atuin/tests/sync.rs158
-rw-r--r--atuin/tests/users.rs131
3 files changed, 233 insertions, 155 deletions
diff --git a/atuin/tests/common/mod.rs b/atuin/tests/common/mod.rs
new file mode 100644
index 00000000..bda6abd3
--- /dev/null
+++ b/atuin/tests/common/mod.rs
@@ -0,0 +1,99 @@
+use std::{env, time::Duration};
+
+use atuin_client::api_client;
+use atuin_common::{api::AddHistoryRequest, utils::uuid_v7};
+use atuin_server::{launch_with_tcp_listener, Settings as ServerSettings};
+use atuin_server_postgres::{Postgres, PostgresSettings};
+use futures_util::TryFutureExt;
+use time::OffsetDateTime;
+use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle};
+use tracing::{dispatcher, Dispatch};
+use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
+
+pub async fn start_server(path: &str) -> (String, oneshot::Sender<()>, JoinHandle<()>) {
+ let formatting_layer = tracing_tree::HierarchicalLayer::default()
+ .with_writer(tracing_subscriber::fmt::TestWriter::new())
+ .with_indent_lines(true)
+ .with_ansi(true)
+ .with_targets(true)
+ .with_indent_amount(2);
+
+ let dispatch: Dispatch = tracing_subscriber::registry()
+ .with(formatting_layer)
+ .with(EnvFilter::new("atuin_server=debug,atuin_client=debug,info"))
+ .into();
+
+ let db_uri = env::var("ATUIN_DB_URI")
+ .unwrap_or_else(|_| "postgres://atuin:pass@localhost:5432/atuin".to_owned());
+
+ let server_settings = ServerSettings {
+ host: "127.0.0.1".to_owned(),
+ port: 0,
+ path: path.to_owned(),
+ open_registration: true,
+ max_history_length: 8192,
+ max_record_size: 1024 * 1024 * 1024,
+ page_size: 1100,
+ register_webhook_url: None,
+ register_webhook_username: String::new(),
+ db_settings: PostgresSettings { db_uri },
+ metrics: atuin_server::settings::Metrics::default(),
+ tls: atuin_server::settings::Tls::default(),
+ };
+
+ let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
+ let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
+ let addr = listener.local_addr().unwrap();
+ let server = tokio::spawn(async move {
+ let _tracing_guard = dispatcher::set_default(&dispatch);
+
+ if let Err(e) = launch_with_tcp_listener::<Postgres>(
+ server_settings,
+ listener,
+ shutdown_rx.unwrap_or_else(|_| ()),
+ )
+ .await
+ {
+ tracing::error!(error=?e, "server error");
+ panic!("error running server: {e:?}");
+ }
+ });
+
+ // let the server come online
+ tokio::time::sleep(Duration::from_millis(200)).await;
+
+ (format!("http://{addr}{path}"), shutdown_tx, server)
+}
+
+pub async fn register_inner<'a>(
+ address: &'a str,
+ username: &str,
+ password: &str,
+) -> api_client::Client<'a> {
+ let email = format!("{}@example.com", uuid_v7().as_simple());
+
+ // registration works
+ let registration_response = api_client::register(address, username, &email, password)
+ .await
+ .unwrap();
+
+ api_client::Client::new(address, &registration_response.session, 5, 30).unwrap()
+}
+
+pub async fn login(address: &str, username: String, password: String) -> api_client::Client<'_> {
+ // registration works
+ let login_respose = api_client::login(
+ address,
+ atuin_common::api::LoginRequest { username, password },
+ )
+ .await
+ .unwrap();
+
+ api_client::Client::new(address, &login_respose.session, 5, 30).unwrap()
+}
+
+pub async fn register(address: &str) -> api_client::Client<'_> {
+ let username = uuid_v7().as_simple().to_string();
+ let password = uuid_v7().as_simple().to_string();
+ register_inner(address, &username, &password).await
+}
diff --git a/atuin/tests/sync.rs b/atuin/tests/sync.rs
index 6dacbd54..1835b20b 100644
--- a/atuin/tests/sync.rs
+++ b/atuin/tests/sync.rs
@@ -10,166 +10,14 @@ use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle};
use tracing::{dispatcher, Dispatch};
use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
-async fn start_server(path: &str) -> (String, oneshot::Sender<()>, JoinHandle<()>) {
- let formatting_layer = tracing_tree::HierarchicalLayer::default()
- .with_writer(tracing_subscriber::fmt::TestWriter::new())
- .with_indent_lines(true)
- .with_ansi(true)
- .with_targets(true)
- .with_indent_amount(2);
-
- let dispatch: Dispatch = tracing_subscriber::registry()
- .with(formatting_layer)
- .with(EnvFilter::new("atuin_server=debug,atuin_client=debug,info"))
- .into();
-
- let db_uri = env::var("ATUIN_DB_URI")
- .unwrap_or_else(|_| "postgres://atuin:pass@localhost:5432/atuin".to_owned());
-
- let server_settings = ServerSettings {
- host: "127.0.0.1".to_owned(),
- port: 0,
- path: path.to_owned(),
- open_registration: true,
- max_history_length: 8192,
- max_record_size: 1024 * 1024 * 1024,
- page_size: 1100,
- register_webhook_url: None,
- register_webhook_username: String::new(),
- db_settings: PostgresSettings { db_uri },
- metrics: atuin_server::settings::Metrics::default(),
- tls: atuin_server::settings::Tls::default(),
- };
-
- let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
- let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let addr = listener.local_addr().unwrap();
- let server = tokio::spawn(async move {
- let _tracing_guard = dispatcher::set_default(&dispatch);
-
- if let Err(e) = launch_with_tcp_listener::<Postgres>(
- server_settings,
- listener,
- shutdown_rx.unwrap_or_else(|_| ()),
- )
- .await
- {
- tracing::error!(error=?e, "server error");
- panic!("error running server: {e:?}");
- }
- });
-
- // let the server come online
- tokio::time::sleep(Duration::from_millis(200)).await;
-
- (format!("http://{addr}{path}"), shutdown_tx, server)
-}
-
-async fn register_inner<'a>(
- address: &'a str,
- username: &str,
- password: &str,
-) -> api_client::Client<'a> {
- let email = format!("{}@example.com", uuid_v7().as_simple());
-
- // registration works
- let registration_response = api_client::register(address, username, &email, password)
- .await
- .unwrap();
-
- api_client::Client::new(address, &registration_response.session, 5, 30).unwrap()
-}
-
-async fn login(address: &str, username: String, password: String) -> api_client::Client<'_> {
- // registration works
- let login_respose = api_client::login(
- address,
- atuin_common::api::LoginRequest { username, password },
- )
- .await
- .unwrap();
-
- api_client::Client::new(address, &login_respose.session, 5, 30).unwrap()
-}
-
-async fn register(address: &str) -> api_client::Client<'_> {
- let username = uuid_v7().as_simple().to_string();
- let password = uuid_v7().as_simple().to_string();
- register_inner(address, &username, &password).await
-}
-
-#[tokio::test]
-async fn registration() {
- let path = format!("/{}", uuid_v7().as_simple());
- let (address, shutdown, server) = start_server(&path).await;
- dbg!(&address);
-
- // -- REGISTRATION --
-
- let username = uuid_v7().as_simple().to_string();
- let password = uuid_v7().as_simple().to_string();
- let client = register_inner(&address, &username, &password).await;
-
- // the session token works
- let status = client.status().await.unwrap();
- assert_eq!(status.username, username);
-
- // -- LOGIN --
-
- let client = login(&address, username.clone(), password).await;
-
- // the session token works
- let status = client.status().await.unwrap();
- assert_eq!(status.username, username);
-
- shutdown.send(()).unwrap();
- server.await.unwrap();
-}
-
-#[tokio::test]
-async fn change_password() {
- let path = format!("/{}", uuid_v7().as_simple());
- let (address, shutdown, server) = start_server(&path).await;
-
- // -- REGISTRATION --
-
- let username = uuid_v7().as_simple().to_string();
- let password = uuid_v7().as_simple().to_string();
- let client = register_inner(&address, &username, &password).await;
-
- // the session token works
- let status = client.status().await.unwrap();
- assert_eq!(status.username, username);
-
- // -- PASSWORD CHANGE --
-
- let current_password = password;
- let new_password = uuid_v7().as_simple().to_string();
- let result = client
- .change_password(current_password, new_password.clone())
- .await;
-
- // the password change request succeeded
- assert!(result.is_ok());
-
- // -- LOGIN --
-
- let client = login(&address, username.clone(), new_password).await;
-
- // login with new password yields a working token
- let status = client.status().await.unwrap();
- assert_eq!(status.username, username);
-
- shutdown.send(()).unwrap();
- server.await.unwrap();
-}
+mod common;
#[tokio::test]
async fn sync() {
let path = format!("/{}", uuid_v7().as_simple());
- let (address, shutdown, server) = start_server(&path).await;
+ let (address, shutdown, server) = common::start_server(&path).await;
- let client = register(&address).await;
+ let client = common::register(&address).await;
let hostname = uuid_v7().as_simple().to_string();
let now = OffsetDateTime::now_utc();
diff --git a/atuin/tests/users.rs b/atuin/tests/users.rs
new file mode 100644
index 00000000..8580d64c
--- /dev/null
+++ b/atuin/tests/users.rs
@@ -0,0 +1,131 @@
+use std::{env, time::Duration};
+
+use atuin_client::api_client;
+use atuin_common::{api::AddHistoryRequest, utils::uuid_v7};
+use atuin_server::{launch_with_tcp_listener, Settings as ServerSettings};
+use atuin_server_postgres::{Postgres, PostgresSettings};
+use futures_util::TryFutureExt;
+use time::OffsetDateTime;
+use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle};
+use tracing::{dispatcher, Dispatch};
+use tracing_subscriber::{layer::SubscriberExt, EnvFilter};
+
+mod common;
+
+#[tokio::test]
+async fn registration() {
+ let path = format!("/{}", uuid_v7().as_simple());
+ let (address, shutdown, server) = common::start_server(&path).await;
+ dbg!(&address);
+
+ // -- REGISTRATION --
+
+ let username = uuid_v7().as_simple().to_string();
+ let password = uuid_v7().as_simple().to_string();
+ let client = common::register_inner(&address, &username, &password).await;
+
+ // the session token works
+ let status = client.status().await.unwrap();
+ assert_eq!(status.username, username);
+
+ // -- LOGIN --
+
+ let client = common::login(&address, username.clone(), password).await;
+
+ // the session token works
+ let status = client.status().await.unwrap();
+ assert_eq!(status.username, username);
+
+ shutdown.send(()).unwrap();
+ server.await.unwrap();
+}
+
+#[tokio::test]
+async fn change_password() {
+ let path = format!("/{}", uuid_v7().as_simple());
+ let (address, shutdown, server) = common::start_server(&path).await;
+
+ // -- REGISTRATION --
+
+ let username = uuid_v7().as_simple().to_string();
+ let password = uuid_v7().as_simple().to_string();
+ let client = common::register_inner(&address, &username, &password).await;
+
+ // the session token works
+ let status = client.status().await.unwrap();
+ assert_eq!(status.username, username);
+
+ // -- PASSWORD CHANGE --
+
+ let current_password = password;
+ let new_password = uuid_v7().as_simple().to_string();
+ let result = client
+ .change_password(current_password, new_password.clone())
+ .await;
+
+ // the password change request succeeded
+ assert!(result.is_ok());
+
+ // -- LOGIN --
+
+ let client = common::login(&address, username.clone(), new_password).await;
+
+ // login with new password yields a working token
+ let status = client.status().await.unwrap();
+ assert_eq!(status.username, username);
+
+ shutdown.send(()).unwrap();
+ server.await.unwrap();
+}
+
+#[tokio::test]
+async fn multi_user_test() {
+ let path = format!("/{}", uuid_v7().as_simple());
+ let (address, shutdown, server) = common::start_server(&path).await;
+ dbg!(&address);
+
+ // -- REGISTRATION --
+
+ let user_one = uuid_v7().as_simple().to_string();
+ let password_one = uuid_v7().as_simple().to_string();
+ let client_one = common::register_inner(&address, &user_one, &password_one).await;
+
+ // the session token works
+ let status = client_one.status().await.unwrap();
+ assert_eq!(status.username, user_one);
+
+ let user_two = uuid_v7().as_simple().to_string();
+ let password_two = uuid_v7().as_simple().to_string();
+ let client_two = common::register_inner(&address, &user_two, &password_two).await;
+
+ // the session token works
+ let status = client_two.status().await.unwrap();
+ assert_eq!(status.username, user_two);
+
+ // check that we can change user one's password, and _this does not affect user two_
+
+ let current_password = password_one;
+ let new_password = uuid_v7().as_simple().to_string();
+ let result = client_one
+ .change_password(current_password, new_password.clone())
+ .await;
+
+ // the password change request succeeded
+ assert!(result.is_ok());
+
+ // -- LOGIN --
+
+ let client_one = common::login(&address, user_one.clone(), new_password).await;
+ let client_two = common::login(&address, user_two.clone(), password_two).await;
+
+ // login with new password yields a working token
+ let status = client_one.status().await.unwrap();
+ assert_eq!(status.username, user_one);
+ assert_ne!(status.username, user_two);
+
+ let status = client_two.status().await.unwrap();
+ assert_eq!(status.username, user_two);
+
+ shutdown.send(()).unwrap();
+ server.await.unwrap();
+}