use axum::{Router, routing::get};
use minifly_api::{create_app, AppState, Config};
use minifly_core::models::*;
use std::sync::{Arc, RwLock};
use std::collections::HashMap;
use tokio::net::TcpListener;
use uuid::Uuid;
pub fn test_config() -> Config {
Config {
port: 0, database_url: ":memory:".to_string(), docker_host: None,
data_dir: "/tmp/minifly-test".into(),
internal_network_prefix: "fdaa:0:".to_string(),
dns_port: 0, litefs_port: 0, }
}
pub fn test_app_state() -> AppState {
AppState {
config: test_config(),
db: todo!("Mock database for tests"),
docker: todo!("Mock Docker for tests"),
litefs: todo!("Mock LiteFS for tests"),
leases: Arc::new(RwLock::new(HashMap::new())),
machines: Arc::new(RwLock::new(HashMap::new())),
apps: Arc::new(RwLock::new(HashMap::new())),
start_time: std::time::Instant::now(),
}
}
pub async fn start_test_server() -> (String, tokio::task::JoinHandle<()>) {
let state = test_app_state();
let app = create_app(state);
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("Failed to bind to port");
let port = listener.local_addr().unwrap().port();
let url = format!("http://127.0.0.1:{}", port);
let handle = tokio::spawn(async move {
axum::serve(listener, app)
.await
.expect("Failed to start server");
});
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
(url, handle)
}
pub fn create_test_app(name: &str) -> App {
App {
id: Uuid::new_v4().to_string(),
name: name.to_string(),
organization: Organization {
id: "test-org".to_string(),
slug: "test-org".to_string(),
name: "Test Organization".to_string(),
},
status: AppStatus::Running,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
}
}
pub fn create_test_machine(app_name: &str, machine_name: &str) -> Machine {
let id = format!("d{}", Uuid::new_v4().to_string().replace("-", "")[..15]);
Machine {
id: id.clone(),
name: format!("{}-{}", app_name, machine_name),
state: MachineState::Created,
region: "local".to_string(),
config: MachineConfig {
image: "nginx:latest".to_string(),
guest: Some(GuestConfig {
cpus: 1,
memory_mb: 256,
cpu_kind: CpuKind::Shared,
}),
env: HashMap::new(),
services: vec![],
mounts: vec![],
size: MachineSize::Shared1x,
restart: Some(RestartConfig {
policy: RestartPolicy::Always,
max_retries: Some(3),
}),
auto_destroy: false,
schedule: None,
},
app_name: app_name.to_string(),
private_ip: None,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
version: 1,
events: vec![],
checks: HashMap::new(),
nonce: None,
}
}
pub fn create_test_volume(name: &str, app_name: &str) -> Volume {
Volume {
id: format!("vol_{}", Uuid::new_v4().to_string().replace("-", "")[..10]),
name: name.to_string(),
app_id: app_name.to_string(),
size_gb: 1,
region: "local".to_string(),
zone: "local-a".to_string(),
encrypted: false,
created_at: chrono::Utc::now(),
attached_machine_id: None,
attached_allocation_id: None,
block_size: 4096,
blocks: 262144,
blocks_avail: 262144,
blocks_free: 262144,
fstype: "ext4".to_string(),
snapshot_retention: None,
}
}
pub fn test_client() -> reqwest::Client {
reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()
.expect("Failed to create test client")
}
#[macro_export]
macro_rules! assert_status {
($response:expr, $status:expr) => {
assert_eq!(
$response.status(),
$status,
"Expected status {}, got {} with body: {:?}",
$status,
$response.status(),
$response.text().await.unwrap_or_default()
);
};
}
#[macro_export]
macro_rules! assert_json_response {
($response:expr, $expected:expr) => {
let status = $response.status();
let body = $response.text().await.expect("Failed to read response body");
assert!(
status.is_success(),
"Expected success status, got {} with body: {}",
status,
body
);
let actual: serde_json::Value = serde_json::from_str(&body)
.expect(&format!("Failed to parse JSON response: {}", body));
assert_eq!(actual, $expected);
};
}