mirror of
https://github.com/atuinsh/atuin.git
synced 2024-11-25 09:44:03 +01:00
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:
parent
e53c7c9dd6
commit
15bad15f48
99
atuin/tests/common/mod.rs
Normal file
99
atuin/tests/common/mod.rs
Normal 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, ®istration_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
|
||||
}
|
@ -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, ®istration_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
131
atuin/tests/users.rs
Normal 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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user