use aurabase::{create_client, types::SignUpRequest};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use std::time::Duration;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
struct Todo {
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<uuid::Uuid>,
title: String,
completed: bool,
owner_id: String,
}
#[derive(Deserialize)]
struct SmokeConfig {
project_uuid: String,
anon_key: String,
service_key: String,
}
fn get_smoke_config() -> Option<SmokeConfig> {
let path = Path::new("../.smoke/last-run.json");
if path.exists() {
let content = fs::read_to_string(path).ok()?;
serde_json::from_str(&content).ok()
} else {
None
}
}
fn e2e_url() -> Option<String> {
std::env::var("AURA_E2E_URL").ok().filter(|u| !u.is_empty())
}
#[tokio::test]
async fn test_e2e_prod_flow() {
let config = match get_smoke_config() {
Some(c) => c,
None => {
println!("Skipping E2E test: .smoke/last-run.json not found");
return;
}
};
let base_url = match e2e_url() {
Some(u) => u,
None => {
println!(
"Skipping E2E test: AURA_E2E_URL absente — l'URL du projet \
(https://<slug>.<apex>) est désormais la seule désignation du tenant"
);
return;
}
};
println!("Starting E2E test for project: {}", config.project_uuid);
let client = create_client(&base_url, &config.anon_key, None);
let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string();
let email = format!("e2e-user-{}@aurabase.cloud", test_id);
let password = "E2EUserPass123!".to_string();
println!("Signing up user: {}", email);
let signup_res = client
.auth()
.sign_up(SignUpRequest {
email: email.clone(),
password: password.clone(),
captcha_token: None,
})
.await
.expect("Signup failed");
println!("Signup response: {:?}", signup_res);
let user = match signup_res.data {
Some(aurabase::types::LoginResponse::TokenPair(ref tp)) => tp.user.clone(),
_ => panic!(
"Expected TokenPair in signup response, got: {:?}",
signup_res
),
};
let user_id = user.id.clone();
println!("User signed up with ID: {}", user_id);
println!("Signing in user: {}", email);
let login_res = client
.auth()
.sign_in_with_password(aurabase::types::SignInRequest {
email: email.clone(),
password: password.clone(),
captcha_token: None,
})
.await
.expect("Login failed");
println!("Login response: {:?}", login_res);
assert!(login_res.data.is_some());
println!("Login successful!");
let todo_item = Todo {
id: None,
title: "Test E2E SDK Rust".to_string(),
completed: false,
owner_id: user_id.clone(),
};
println!("Inserting todo item...");
let insert_res = client
.db()
.from::<Todo>("todos")
.insert(todo_item.clone())
.execute()
.await
.expect("Insert failed");
let inserted_todos = insert_res.data.expect("No data returned on insert");
assert!(!inserted_todos.is_empty(), "Inserted todos array is empty");
let inserted_todo = &inserted_todos[0];
let inserted_id = inserted_todo.id.expect("No ID generated for todo");
println!("Inserted todo with ID: {}", inserted_id);
println!("Reading todos (authenticated)...");
let read_res = client
.db()
.from::<Todo>("todos")
.select("*")
.eq("owner_id", &user_id)
.execute()
.await
.expect("Read failed");
let todos = read_res.data.expect("No data returned on read");
assert!(!todos.is_empty());
assert_eq!(todos[0].title, "Test E2E SDK Rust");
println!("Authenticated read verified!");
let anon_client = create_client(&base_url, &config.anon_key, None);
println!("Reading todos (anonymous)...");
let anon_read_res = anon_client
.db()
.from::<Todo>("todos")
.select("*")
.execute()
.await
.expect("Anonymous read failed");
let anon_todos = anon_read_res.data.expect("No data returned on anon read");
assert!(
anon_todos.is_empty(),
"Anon should not be able to read private data"
);
println!("Anonymous RLS isolation verified!");
println!("Updating todo item...");
let mut updated_todo_info = inserted_todo.clone();
updated_todo_info.completed = true;
let update_res = client
.db()
.from::<Todo>("todos")
.update(&inserted_id.to_string(), updated_todo_info)
.execute()
.await
.expect("Update failed");
let updated_todos = update_res.data.expect("No data returned on update");
assert!(!updated_todos.is_empty());
let updated_todo = &updated_todos[0];
assert!(updated_todo.completed);
println!("Todo updated successfully!");
println!("Deleting todo item...");
let delete_res = client
.db()
.from::<serde_json::Value>("todos")
.delete(&inserted_id.to_string())
.execute()
.await
.expect("Delete failed");
assert!(delete_res.error.is_none());
println!("Todo deleted successfully!");
let bucket_name = format!("e2e-bucket-{}", test_id);
let admin_client = create_client(&base_url, &config.service_key, None);
println!("Creating storage bucket: {}", bucket_name);
let create_bucket_res = admin_client
.storage()
.create_bucket(&bucket_name, Some(true), None, None)
.await
.expect("Create bucket failed");
assert_eq!(
create_bucket_res.data.expect("No bucket returned").name,
bucket_name
);
println!("Storage bucket created!");
let file_content = b"Contenu du fichier E2E Rust".to_vec();
let file_path = "rust_test.txt";
println!("Uploading file...");
let upload_res = client
.storage()
.upload(&bucket_name, file_path, file_content.clone(), None)
.await
.expect("Upload failed");
let upload_data = upload_res.data.expect("No data returned on upload");
let uploaded_key = upload_data.key.clone();
println!("File uploaded successfully with key: {}", uploaded_key);
let object_path = match uploaded_key.find('/') {
Some(idx) => &uploaded_key[idx + 1..],
None => &uploaded_key,
};
println!("File relative path: {}", object_path);
println!("Downloading file...");
let download_res = client
.storage()
.download(&bucket_name, object_path)
.await
.expect("Download failed");
if let Some(ref err) = download_res.error {
panic!("Download returned error: {:?}", err);
}
let downloaded_content = download_res.data.expect("No data returned on download");
assert_eq!(downloaded_content, file_content);
println!("File download verified!");
println!("Listing files in bucket...");
let list_res = client
.storage()
.list(&bucket_name, None, None, None)
.await
.expect("List files failed");
let files = list_res.data.expect("No files returned");
assert!(!files.is_empty());
assert_eq!(files[0].name, uploaded_key);
println!("File listing verified!");
println!("Removing file...");
client
.storage()
.remove(&bucket_name, vec![object_path.to_string()])
.await
.expect("Remove file failed");
println!("File removed!");
println!("Deleting bucket...");
admin_client
.storage()
.delete_bucket(&bucket_name)
.await
.expect("Delete bucket failed");
println!("Storage bucket deleted!");
println!("Reading hash.wasm...");
let wasm_path = Path::new("../scripts/functions/wasm/hash.wasm");
let wasm_bytes = fs::read(wasm_path).expect("Failed to read hash.wasm");
use base64::Engine;
let wasm_b64 = base64::engine::general_purpose::STANDARD.encode(&wasm_bytes);
println!("Creating Edge Function 'hash-fn'...");
let build_fn_client = reqwest::Client::new();
let create_fn_url = format!("http://localhost:8080/v1/functions/{}", config.project_uuid);
let create_fn_payload = serde_json::json!({
"name": "hash-fn",
"runtime": "wasm",
"code": wasm_b64,
"env_vars": {}
});
let create_fn_res = build_fn_client
.post(&create_fn_url)
.header("apikey", &config.service_key)
.json(&create_fn_payload)
.send()
.await
.expect("Failed to send create function request");
let create_status = create_fn_res.status();
let create_res_text = create_fn_res.text().await.unwrap_or_default();
assert!(
create_status.is_success(),
"Failed to create Edge Function: {} - {}",
create_status,
create_res_text
);
println!("Edge Function 'hash-fn' created successfully!");
tokio::time::sleep(Duration::from_secs(2)).await;
println!("Invoking Edge Function 'hash-fn'...");
let func_res = client
.functions()
.invoke::<serde_json::Value>(
"hash-fn",
Some(aurabase::services::functions::InvokeOptions {
body: Some(serde_json::json!("hello world")),
..Default::default()
}),
)
.await
.expect("Function invocation failed");
if let Some(ref err) = func_res.error {
panic!("Edge Function invocation returned error: {:?}", err);
}
let data_val = func_res.data.expect("No data returned from function");
let body_val = data_val
.get("body")
.expect("No body field in InvokeResponse");
let hash_val = body_val.get("hash").and_then(|v| v.as_str()).unwrap_or("");
println!("WASM Edge Function response hash: {}", hash_val);
assert_eq!(hash_val, "0xfc16783b");
println!("Deleting Edge Function 'hash-fn'...");
let delete_fn_url = format!(
"http://localhost:8080/v1/functions/{}/hash-fn",
config.project_uuid
);
let delete_fn_res = build_fn_client
.delete(&delete_fn_url)
.header("apikey", &config.service_key)
.send()
.await
.expect("Failed to send delete function request");
assert!(
delete_fn_res.status().is_success(),
"Failed to delete Edge Function"
);
println!("Edge Function 'hash-fn' deleted successfully!");
println!("Testing Realtime connection...");
let realtime = client.realtime();
let channel = realtime.channel("public:todos", None);
println!("Subscribing to channel public:todos...");
realtime
.subscribe_channel(channel)
.await
.expect("Subscription failed");
let mut state_rx = realtime.on_state_change();
let mut connected = false;
for _ in 0..10 {
if realtime.connection_state() == aurabase::ConnectionState::Connected {
connected = true;
break;
}
if let Ok(state) = tokio::time::timeout(Duration::from_millis(500), state_rx.recv()).await {
if state.ok() == Some(aurabase::ConnectionState::Connected) {
connected = true;
break;
}
}
}
if connected {
println!("Realtime connected successfully!");
} else {
println!("Warning: Realtime did not reach connected state in time.");
}
println!("=== E2E Test Completed Successfully! [OK] ===");
}