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