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.
This commit is contained in:
Ellie Huxtable 2024-01-29 13:06:47 +00:00 committed by GitHub
parent e53c7c9dd6
commit 15bad15f48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 233 additions and 155 deletions

99
atuin/tests/common/mod.rs Normal file
View File

@ -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
}

View File

@ -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();

131
atuin/tests/users.rs Normal file
View File

@ -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();
}