use ordinary_api::{client::OrdinaryApiClient, server::OrdinaryApiServer};
use ordinary_utils::shutdown_signal;
use reqwest::StatusCode;
use std::{error::Error, net::SocketAddr, path::Path};
use tokio::net::TcpListener;
use totp_rs::{Secret, TOTP};
static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
async fn server() -> anyhow::Result<(SocketAddr, Vec<u8>)> {
if tokio_rustls::rustls::crypto::ring::default_provider()
.install_default()
.is_err()
{
tracing::error!("failed to get rustls default provider");
}
let listener = TcpListener::bind("0.0.0.0:0").await?;
let addr = listener.local_addr()?;
let environment_dir = Path::new(".ordinary").join("environments").join("test");
if std::fs::read_dir(&environment_dir).is_ok() {
std::fs::remove_dir_all(&environment_dir)?;
}
std::fs::create_dir_all(&environment_dir)?;
let storage_size = 16384 * 64 * 10;
let server_span = tracing::info_span!("test");
let (_, mfa_secret) = OrdinaryApiServer::init(
"test",
"api.example.com",
"root_password",
&environment_dir,
storage_size,
&["example@api.example.com".into()],
&["example.com".into()],
&None,
&environment_dir.join("logs"),
)?;
let api_server = OrdinaryApiServer::new(
"test",
&environment_dir,
storage_size,
false,
0,
0,
0,
&environment_dir.join("logs"),
false,
&tracing::info_span!("server"),
)?;
tokio::spawn(async move {
api_server
.start::<&str, _>(
server_span,
ordinary_api::server::SecurityMode::Insecure,
listener,
false,
false,
false,
false,
None,
true,
false,
None,
false,
shutdown_signal,
)
.await
.expect("server crashed");
});
Ok((addr, mfa_secret))
}
#[tokio::test]
async fn admin() -> Result<(), Box<dyn Error>> {
let project_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("project");
let project = project_path.to_str().expect("project path not a string");
ordinary_build::build(project, true)?;
let (addr, mfa_secret) = server().await?;
let addr = format!("http://{addr}");
let totp = TOTP::new(
totp_rs::Algorithm::SHA1,
6,
1,
30,
Secret::Raw(mfa_secret).to_bytes()?,
Some("test".into()),
"root".into(),
)?;
let mfa_code = totp.generate_current()?;
let client_dir = Path::new(".ordinary")
.join("clients")
.join("api.example.com");
if std::fs::read_dir(&client_dir).is_ok() {
std::fs::remove_dir_all(&client_dir)?;
}
std::fs::create_dir_all(&client_dir)?;
let root_client =
OrdinaryApiClient::new(&addr, "root", Some("api.example.com"), false, USER_AGENT)?;
root_client.login("root_password", &mfa_code).await?;
let invite_token = root_client
.invite_api_account("test.example.com", "admin", vec![0])
.await?;
let api_client =
OrdinaryApiClient::new(&addr, "admin", Some("api.example.com"), false, USER_AGENT)?;
let (totp, recovery_codes_str) = api_client.register("api_password", &invite_token).await?;
let mut recovery_code1 = String::new();
let mut recovery_code2 = String::new();
for (i, c) in recovery_codes_str.chars().enumerate() {
if i < 11 {
recovery_code1 = format!("{recovery_code1}{c}");
} else if i < 22 {
recovery_code2 = format!("{recovery_code2}{c}");
} else {
break;
}
}
let mfa_code = totp.generate_current()?;
api_client.login("api_password", &mfa_code).await?;
api_client.deploy(project).await?;
api_client.patch(project).await?;
api_client.kill(project).await?;
api_client.restart(project).await?;
let mfa_code = totp.generate_current()?;
api_client
.reset_password("api_password", &mfa_code, "api_password1")
.await?;
let mfa_code = totp.generate_current()?;
api_client.login("api_password1", &mfa_code).await?;
api_client
.forgot_password("api_password2", &recovery_code1)
.await?;
let mfa_code = totp.generate_current()?;
api_client.login("api_password2", &mfa_code).await?;
let mfa_code = totp.generate_current()?;
let totp = api_client
.mfa_totp_reset("api_password2", &mfa_code)
.await?;
let mfa_code = totp.generate_current()?;
api_client.login("api_password2", &mfa_code).await?;
let totp = api_client
.mfa_totp_lost("api_password2", &recovery_code2)
.await?;
let mfa_code = totp.generate_current()?;
api_client.login("api_password2", &mfa_code).await?;
let mfa_code = totp.generate_current()?;
let recovery_codes_str = api_client
.recovery_codes_reset("api_password2", &mfa_code)
.await?;
let mut recovery_code3 = String::new();
for (i, c) in recovery_codes_str.chars().enumerate() {
if i < 11 {
recovery_code3 = format!("{recovery_code3}{c}");
} else {
break;
}
}
api_client
.forgot_password("api_password3", &recovery_code3)
.await?;
let mfa_code = totp.generate_current()?;
api_client.login("api_password3", &mfa_code).await?;
api_client
.store(project, "TEST_SECRET", &[1, 2, 3, 4])
.await?;
let app_port = api_client.restart(project).await?;
let status = reqwest::get(format!("http://localhost:{app_port}"))
.await?
.status();
assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
let client = reqwest::Client::new();
let status = client
.post(format!("http://localhost:{app_port}/test"))
.header("content-type", "application/json")
.body("[]")
.send()
.await?
.status();
assert_eq!(status, StatusCode::SERVICE_UNAVAILABLE);
Ok(())
}