integration.rs (6323B)
1 use reqwest::StatusCode; 2 use std::sync::atomic::{AtomicU32, Ordering}; 3 4 static TEST_COUNTER: AtomicU32 = AtomicU32::new(0); 5 6 async fn start_server() -> String { 7 let id = TEST_COUNTER.fetch_add(1, Ordering::Relaxed); 8 let db_path = format!("/tmp/mkv-test/index-{id}.db"); 9 10 let _ = std::fs::remove_file(&db_path); 11 let _ = std::fs::remove_file(format!("{db_path}-wal")); 12 let _ = std::fs::remove_file(format!("{db_path}-shm")); 13 14 let args = mkv::Args { 15 db_path, 16 volumes: vec![ 17 "http://localhost:3101".into(), 18 "http://localhost:3102".into(), 19 "http://localhost:3103".into(), 20 ], 21 replicas: 2, 22 voltimeout: std::time::Duration::from_secs(1), 23 }; 24 25 let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); 26 let port = listener.local_addr().unwrap().port(); 27 let app = mkv::build_app(&args); 28 29 tokio::spawn(async move { 30 axum::serve(listener, app).await.unwrap(); 31 }); 32 33 tokio::time::sleep(std::time::Duration::from_millis(50)).await; 34 format!("http://127.0.0.1:{port}") 35 } 36 37 fn client() -> reqwest::Client { 38 reqwest::Client::builder() 39 .redirect(reqwest::redirect::Policy::none()) 40 .build() 41 .unwrap() 42 } 43 44 #[tokio::test] 45 async fn test_put_and_head() { 46 let base = start_server().await; 47 let c = client(); 48 49 let resp = c 50 .put(format!("{base}/hello")) 51 .body("world") 52 .send() 53 .await 54 .unwrap(); 55 assert_eq!(resp.status(), StatusCode::CREATED); 56 57 let resp = c.head(format!("{base}/hello")).send().await.unwrap(); 58 assert_eq!(resp.status(), StatusCode::OK); 59 assert_eq!( 60 resp.headers() 61 .get("content-length") 62 .unwrap() 63 .to_str() 64 .unwrap(), 65 "5" 66 ); 67 } 68 69 #[tokio::test] 70 async fn test_put_and_get_redirect() { 71 let base = start_server().await; 72 let c = client(); 73 74 let resp = c 75 .put(format!("{base}/redirect-test")) 76 .body("some data") 77 .send() 78 .await 79 .unwrap(); 80 assert_eq!(resp.status(), StatusCode::CREATED); 81 82 let resp = c.get(format!("{base}/redirect-test")).send().await.unwrap(); 83 assert_eq!(resp.status(), StatusCode::FOUND); 84 85 let location = resp.headers().get("location").unwrap().to_str().unwrap(); 86 assert!( 87 location.starts_with("http://localhost:310"), 88 "got: {location}" 89 ); 90 91 let blob_resp = reqwest::get(location).await.unwrap(); 92 assert_eq!(blob_resp.status(), StatusCode::OK); 93 assert_eq!(blob_resp.text().await.unwrap(), "some data"); 94 } 95 96 #[tokio::test] 97 async fn test_get_nonexistent_returns_404() { 98 let base = start_server().await; 99 let c = client(); 100 let resp = c 101 .get(format!("{base}/does-not-exist")) 102 .send() 103 .await 104 .unwrap(); 105 assert_eq!(resp.status(), StatusCode::NOT_FOUND); 106 } 107 108 #[tokio::test] 109 async fn test_put_get_delete_get() { 110 let base = start_server().await; 111 let c = client(); 112 113 let resp = c 114 .put(format!("{base}/delete-me")) 115 .body("temporary") 116 .send() 117 .await 118 .unwrap(); 119 assert_eq!(resp.status(), StatusCode::CREATED); 120 121 let resp = c.get(format!("{base}/delete-me")).send().await.unwrap(); 122 assert_eq!(resp.status(), StatusCode::FOUND); 123 124 let resp = c.delete(format!("{base}/delete-me")).send().await.unwrap(); 125 assert_eq!(resp.status(), StatusCode::NO_CONTENT); 126 127 let resp = c.get(format!("{base}/delete-me")).send().await.unwrap(); 128 assert_eq!(resp.status(), StatusCode::NOT_FOUND); 129 } 130 131 #[tokio::test] 132 async fn test_delete_nonexistent_returns_404() { 133 let base = start_server().await; 134 let c = client(); 135 let resp = c 136 .delete(format!("{base}/never-existed")) 137 .send() 138 .await 139 .unwrap(); 140 assert_eq!(resp.status(), StatusCode::NOT_FOUND); 141 } 142 143 #[tokio::test] 144 async fn test_list_keys() { 145 let base = start_server().await; 146 let c = client(); 147 148 for name in ["docs/a", "docs/b", "docs/c", "other/x"] { 149 c.put(format!("{base}/{name}")) 150 .body("data") 151 .send() 152 .await 153 .unwrap(); 154 } 155 156 let resp = c.get(format!("{base}/")).send().await.unwrap(); 157 assert_eq!(resp.status(), StatusCode::OK); 158 let body = resp.text().await.unwrap(); 159 assert!(body.contains("docs/a")); 160 assert!(body.contains("other/x")); 161 162 let resp = c.get(format!("{base}/?prefix=docs/")).send().await.unwrap(); 163 assert_eq!(resp.status(), StatusCode::OK); 164 let body = resp.text().await.unwrap(); 165 let lines: Vec<&str> = body.lines().collect(); 166 assert_eq!(lines.len(), 3); 167 assert!(!body.contains("other/x")); 168 } 169 170 #[tokio::test] 171 async fn test_put_overwrite() { 172 let base = start_server().await; 173 let c = client(); 174 175 c.put(format!("{base}/overwrite")) 176 .body("version1") 177 .send() 178 .await 179 .unwrap(); 180 181 let resp = c 182 .put(format!("{base}/overwrite")) 183 .body("version2") 184 .send() 185 .await 186 .unwrap(); 187 assert_eq!(resp.status(), StatusCode::CREATED); 188 189 let resp = c.head(format!("{base}/overwrite")).send().await.unwrap(); 190 assert_eq!( 191 resp.headers() 192 .get("content-length") 193 .unwrap() 194 .to_str() 195 .unwrap(), 196 "8" 197 ); 198 199 let resp = c.get(format!("{base}/overwrite")).send().await.unwrap(); 200 let location = resp.headers().get("location").unwrap().to_str().unwrap(); 201 let body = reqwest::get(location).await.unwrap().text().await.unwrap(); 202 assert_eq!(body, "version2"); 203 } 204 205 #[tokio::test] 206 async fn test_replication_writes_to_multiple_volumes() { 207 let base = start_server().await; 208 let c = client(); 209 210 c.put(format!("{base}/replicated")) 211 .body("replica-data") 212 .send() 213 .await 214 .unwrap(); 215 216 let resp = c.head(format!("{base}/replicated")).send().await.unwrap(); 217 assert_eq!(resp.status(), StatusCode::OK); 218 219 let resp = c.get(format!("{base}/replicated")).send().await.unwrap(); 220 assert_eq!(resp.status(), StatusCode::FOUND); 221 let location = resp.headers().get("location").unwrap().to_str().unwrap(); 222 let body = reqwest::get(location).await.unwrap().text().await.unwrap(); 223 assert_eq!(body, "replica-data"); 224 }