this repo has no description
1use reqwest::{header, Client, StatusCode}; 2use serde_json::{json, Value}; 3use chrono::Utc; 4#[allow(unused_imports)] 5use std::collections::HashMap; 6#[allow(unused_imports)] 7use std::time::Duration; 8use std::sync::OnceLock; 9use bspds::state::AppState; 10use sqlx::postgres::PgPoolOptions; 11use tokio::net::TcpListener; 12use testcontainers::{runners::AsyncRunner, ContainerAsync, ImageExt}; 13use testcontainers_modules::postgres::Postgres; 14 15static SERVER_URL: OnceLock<String> = OnceLock::new(); 16static DB_CONTAINER: OnceLock<ContainerAsync<Postgres>> = OnceLock::new(); 17 18#[allow(dead_code)] 19pub const AUTH_TOKEN: &str = "test-token"; 20#[allow(dead_code)] 21pub const BAD_AUTH_TOKEN: &str = "bad-token"; 22#[allow(dead_code)] 23pub const AUTH_DID: &str = "did:plc:fake"; 24#[allow(dead_code)] 25pub const TARGET_DID: &str = "did:plc:target"; 26 27pub fn client() -> Client { 28 Client::new() 29} 30 31pub async fn base_url() -> &'static str { 32 SERVER_URL.get_or_init(|| { 33 let (tx, rx) = std::sync::mpsc::channel(); 34 35 std::thread::spawn(move || { 36 if std::env::var("DOCKER_HOST").is_err() { 37 if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") { 38 let podman_sock = std::path::Path::new(&runtime_dir).join("podman/podman.sock"); 39 if podman_sock.exists() { 40 unsafe { std::env::set_var("DOCKER_HOST", format!("unix://{}", podman_sock.display())); } 41 } 42 } 43 } 44 45 let rt = tokio::runtime::Runtime::new().unwrap(); 46 rt.block_on(async move { 47 let container = Postgres::default().with_tag("18-alpine").start().await.expect("Failed to start Postgres"); 48 let connection_string = format!( 49 "postgres://postgres:postgres@127.0.0.1:{}/postgres", 50 container.get_host_port_ipv4(5432).await.expect("Failed to get port") 51 ); 52 53 DB_CONTAINER.set(container).ok(); 54 55 let url = spawn_app(connection_string).await; 56 tx.send(url).unwrap(); 57 std::future::pending::<()>().await; 58 }); 59 }); 60 61 rx.recv().expect("Failed to start test server") 62 }) 63} 64 65async fn spawn_app(database_url: String) -> String { 66 let pool = PgPoolOptions::new() 67 .connect(&database_url) 68 .await 69 .expect("Failed to connect to Postgres. Make sure the database is running."); 70 71 sqlx::migrate!("./migrations") 72 .run(&pool) 73 .await 74 .expect("Failed to run migrations"); 75 76 let state = AppState::new(pool); 77 let app = bspds::app(state); 78 79 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 80 let addr = listener.local_addr().unwrap(); 81 82 tokio::spawn(async move { 83 axum::serve(listener, app).await.unwrap(); 84 }); 85 86 format!("http://{}", addr) 87} 88 89#[allow(dead_code)] 90pub async fn upload_test_blob(client: &Client, data: &'static str, mime: &'static str) -> Value { 91 let res = client.post(format!("{}/xrpc/com.atproto.repo.uploadBlob", base_url().await)) 92 .header(header::CONTENT_TYPE, mime) 93 .bearer_auth(AUTH_TOKEN) 94 .body(data) 95 .send() 96 .await 97 .expect("Failed to send uploadBlob request"); 98 99 assert_eq!(res.status(), StatusCode::OK, "Failed to upload blob"); 100 let body: Value = res.json().await.expect("Blob upload response was not JSON"); 101 body["blob"].clone() 102} 103 104 105#[allow(dead_code)] 106pub async fn create_test_post( 107 client: &Client, 108 text: &str, 109 reply_to: Option<Value> 110) -> (String, String, String) { 111 let collection = "app.bsky.feed.post"; 112 let mut record = json!({ 113 "$type": collection, 114 "text": text, 115 "createdAt": Utc::now().to_rfc3339() 116 }); 117 118 if let Some(reply_obj) = reply_to { 119 record["reply"] = reply_obj; 120 } 121 122 let payload = json!({ 123 "repo": AUTH_DID, 124 "collection": collection, 125 "record": record 126 }); 127 128 let res = client.post(format!("{}/xrpc/com.atproto.repo.createRecord", base_url().await)) 129 .bearer_auth(AUTH_TOKEN) 130 .json(&payload) 131 .send() 132 .await 133 .expect("Failed to send createRecord"); 134 135 assert_eq!(res.status(), StatusCode::OK, "Failed to create post record"); 136 let body: Value = res.json().await.expect("createRecord response was not JSON"); 137 138 let uri = body["uri"].as_str().expect("Response had no URI").to_string(); 139 let cid = body["cid"].as_str().expect("Response had no CID").to_string(); 140 let rkey = uri.split('/').last().expect("URI was malformed").to_string(); 141 142 (uri, cid, rkey) 143} 144 145pub async fn create_account_and_login(client: &Client) -> (String, String) { 146 let handle = format!("user_{}", uuid::Uuid::new_v4()); 147 let payload = json!({ 148 "handle": handle, 149 "email": format!("{}@example.com", handle), 150 "password": "password" 151 }); 152 153 let res = client.post(format!("{}/xrpc/com.atproto.server.createAccount", base_url().await)) 154 .json(&payload) 155 .send() 156 .await 157 .expect("Failed to create account"); 158 159 if res.status() != StatusCode::OK { 160 panic!("Failed to create account: {:?}", res.text().await); 161 } 162 163 let body: Value = res.json().await.expect("Invalid JSON"); 164 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string(); 165 let did = body["did"].as_str().expect("No did").to_string(); 166 (access_jwt, did) 167}