relay filter/appview bootstrap
at main 4.9 kB view raw
1use prism::{AppState, db::Database}; 2use reqwest::Client; 3use sqlx::postgres::PgPoolOptions; 4use std::sync::{Arc, OnceLock}; 5use tokio::net::TcpListener; 6use tokio::sync::broadcast; 7 8static SERVER_URL: OnceLock<String> = OnceLock::new(); 9static APP_PORT: OnceLock<u16> = OnceLock::new(); 10 11use testcontainers::{ContainerAsync, ImageExt, runners::AsyncRunner}; 12use testcontainers_modules::postgres::Postgres; 13 14static DB_CONTAINER: OnceLock<ContainerAsync<Postgres>> = OnceLock::new(); 15 16fn has_external_infra() -> bool { 17 std::env::var("PRISM_TEST_INFRA_READY").is_ok() 18} 19 20#[cfg(test)] 21#[ctor::dtor] 22fn cleanup() { 23 if has_external_infra() { 24 return; 25 } 26 27 if std::env::var("XDG_RUNTIME_DIR").is_ok() { 28 let _ = std::process::Command::new("podman") 29 .args(["rm", "-f", "--filter", "label=prism_test=true"]) 30 .output(); 31 } 32 33 let _ = std::process::Command::new("docker") 34 .args(["container", "prune", "-f", "--filter", "label=prism_test=true"]) 35 .output(); 36} 37 38#[allow(dead_code)] 39pub fn client() -> Client { 40 Client::new() 41} 42 43#[allow(dead_code)] 44pub fn app_port() -> u16 { 45 *APP_PORT.get().expect("APP_PORT not initialized") 46} 47 48pub async fn base_url() -> &'static str { 49 SERVER_URL.get_or_init(|| { 50 let (tx, rx) = std::sync::mpsc::channel(); 51 52 std::thread::spawn(move || { 53 if std::env::var("DOCKER_HOST").is_err() { 54 if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") { 55 let podman_sock = std::path::Path::new(&runtime_dir).join("podman/podman.sock"); 56 if podman_sock.exists() { 57 unsafe { 58 std::env::set_var( 59 "DOCKER_HOST", 60 format!("unix://{}", podman_sock.display()), 61 ); 62 } 63 } 64 } 65 } 66 67 let rt = tokio::runtime::Runtime::new().unwrap(); 68 rt.block_on(async move { 69 if has_external_infra() { 70 let url = setup_with_external_infra().await; 71 tx.send(url).unwrap(); 72 } else { 73 let url = setup_with_testcontainers().await; 74 tx.send(url).unwrap(); 75 } 76 std::future::pending::<()>().await; 77 }); 78 }); 79 80 rx.recv().expect("Failed to start test server") 81 }) 82} 83 84async fn setup_with_external_infra() -> String { 85 let database_url = 86 std::env::var("DATABASE_URL").expect("DATABASE_URL must be set when using external infra"); 87 88 spawn_app(database_url).await 89} 90 91async fn setup_with_testcontainers() -> String { 92 let container = Postgres::default() 93 .with_tag("18-alpine") 94 .with_label("prism_test", "true") 95 .start() 96 .await 97 .expect("Failed to start Postgres"); 98 99 let connection_string = format!( 100 "postgres://postgres:postgres@127.0.0.1:{}/postgres", 101 container 102 .get_host_port_ipv4(5432) 103 .await 104 .expect("Failed to get port") 105 ); 106 107 DB_CONTAINER.set(container).ok(); 108 109 spawn_app(connection_string).await 110} 111 112async fn spawn_app(database_url: String) -> String { 113 let pool = PgPoolOptions::new() 114 .max_connections(10) 115 .connect(&database_url) 116 .await 117 .expect("Failed to connect to Postgres"); 118 119 sqlx::migrate!("./migrations") 120 .run(&pool) 121 .await 122 .expect("Failed to run migrations"); 123 124 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 125 let addr = listener.local_addr().unwrap(); 126 APP_PORT.set(addr.port()).ok(); 127 128 let (tx, _rx) = broadcast::channel::<String>(1024); 129 let broadcaster = prism::ws::Broadcaster::new(tx); 130 131 let state = Arc::new(AppState { 132 db: Database::new(pool), 133 broadcaster, 134 }); 135 136 let app = prism::api::router() 137 .merge(prism::ws::router()) 138 .with_state(state); 139 140 tokio::spawn(async move { 141 axum::serve(listener, app).await.unwrap(); 142 }); 143 144 format!("http://{}", addr) 145} 146 147#[allow(dead_code)] 148pub async fn get_db_pool() -> sqlx::PgPool { 149 base_url().await; 150 151 let connection_string = if has_external_infra() { 152 std::env::var("DATABASE_URL").expect("DATABASE_URL not set") 153 } else { 154 let container = DB_CONTAINER.get().expect("DB container not initialized"); 155 let port = container 156 .get_host_port_ipv4(5432) 157 .await 158 .expect("Failed to get port"); 159 format!("postgres://postgres:postgres@127.0.0.1:{}/postgres", port) 160 }; 161 162 PgPoolOptions::new() 163 .max_connections(5) 164 .connect(&connection_string) 165 .await 166 .expect("Failed to connect to test database") 167}