166 lines
5.8 KiB
Rust
166 lines
5.8 KiB
Rust
use reqwest::StatusCode;
|
|
use std::sync::atomic::{AtomicU32, Ordering};
|
|
|
|
static TEST_COUNTER: AtomicU32 = AtomicU32::new(0);
|
|
|
|
async fn start_server() -> String {
|
|
let id = TEST_COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
let db_path = format!("/tmp/mkv-test/index-{id}.db");
|
|
|
|
let _ = std::fs::remove_file(&db_path);
|
|
let _ = std::fs::remove_file(format!("{db_path}-wal"));
|
|
let _ = std::fs::remove_file(format!("{db_path}-shm"));
|
|
|
|
let args = mkv::Args {
|
|
db_path,
|
|
volumes: vec![
|
|
"http://localhost:3101".into(),
|
|
"http://localhost:3102".into(),
|
|
"http://localhost:3103".into(),
|
|
],
|
|
replicas: 2,
|
|
};
|
|
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
let port = listener.local_addr().unwrap().port();
|
|
let app = mkv::build_app(&args);
|
|
|
|
tokio::spawn(async move {
|
|
axum::serve(listener, app).await.unwrap();
|
|
});
|
|
|
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
|
format!("http://127.0.0.1:{port}")
|
|
}
|
|
|
|
fn client() -> reqwest::Client {
|
|
reqwest::Client::builder()
|
|
.redirect(reqwest::redirect::Policy::none())
|
|
.build()
|
|
.unwrap()
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_put_and_head() {
|
|
let base = start_server().await;
|
|
let c = client();
|
|
|
|
let resp = c.put(format!("{base}/hello")).body("world").send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::CREATED);
|
|
|
|
let resp = c.head(format!("{base}/hello")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
assert_eq!(resp.headers().get("content-length").unwrap().to_str().unwrap(), "5");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_put_and_get_redirect() {
|
|
let base = start_server().await;
|
|
let c = client();
|
|
|
|
let resp = c.put(format!("{base}/redirect-test")).body("some data").send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::CREATED);
|
|
|
|
let resp = c.get(format!("{base}/redirect-test")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::FOUND);
|
|
|
|
let location = resp.headers().get("location").unwrap().to_str().unwrap();
|
|
assert!(location.starts_with("http://localhost:310"), "got: {location}");
|
|
|
|
let blob_resp = reqwest::get(location).await.unwrap();
|
|
assert_eq!(blob_resp.status(), StatusCode::OK);
|
|
assert_eq!(blob_resp.text().await.unwrap(), "some data");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_get_nonexistent_returns_404() {
|
|
let base = start_server().await;
|
|
let c = client();
|
|
let resp = c.get(format!("{base}/does-not-exist")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_put_get_delete_get() {
|
|
let base = start_server().await;
|
|
let c = client();
|
|
|
|
let resp = c.put(format!("{base}/delete-me")).body("temporary").send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::CREATED);
|
|
|
|
let resp = c.get(format!("{base}/delete-me")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::FOUND);
|
|
|
|
let resp = c.delete(format!("{base}/delete-me")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
|
|
|
|
let resp = c.get(format!("{base}/delete-me")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_delete_nonexistent_returns_404() {
|
|
let base = start_server().await;
|
|
let c = client();
|
|
let resp = c.delete(format!("{base}/never-existed")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_list_keys() {
|
|
let base = start_server().await;
|
|
let c = client();
|
|
|
|
for name in ["docs/a", "docs/b", "docs/c", "other/x"] {
|
|
c.put(format!("{base}/{name}")).body("data").send().await.unwrap();
|
|
}
|
|
|
|
let resp = c.get(format!("{base}/")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
let body = resp.text().await.unwrap();
|
|
assert!(body.contains("docs/a"));
|
|
assert!(body.contains("other/x"));
|
|
|
|
let resp = c.get(format!("{base}/?prefix=docs/")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
let body = resp.text().await.unwrap();
|
|
let lines: Vec<&str> = body.lines().collect();
|
|
assert_eq!(lines.len(), 3);
|
|
assert!(!body.contains("other/x"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_put_overwrite() {
|
|
let base = start_server().await;
|
|
let c = client();
|
|
|
|
c.put(format!("{base}/overwrite")).body("version1").send().await.unwrap();
|
|
|
|
let resp = c.put(format!("{base}/overwrite")).body("version2").send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::CREATED);
|
|
|
|
let resp = c.head(format!("{base}/overwrite")).send().await.unwrap();
|
|
assert_eq!(resp.headers().get("content-length").unwrap().to_str().unwrap(), "8");
|
|
|
|
let resp = c.get(format!("{base}/overwrite")).send().await.unwrap();
|
|
let location = resp.headers().get("location").unwrap().to_str().unwrap();
|
|
let body = reqwest::get(location).await.unwrap().text().await.unwrap();
|
|
assert_eq!(body, "version2");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_replication_writes_to_multiple_volumes() {
|
|
let base = start_server().await;
|
|
let c = client();
|
|
|
|
c.put(format!("{base}/replicated")).body("replica-data").send().await.unwrap();
|
|
|
|
let resp = c.head(format!("{base}/replicated")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
|
|
let resp = c.get(format!("{base}/replicated")).send().await.unwrap();
|
|
assert_eq!(resp.status(), StatusCode::FOUND);
|
|
let location = resp.headers().get("location").unwrap().to_str().unwrap();
|
|
let body = reqwest::get(location).await.unwrap().text().await.unwrap();
|
|
assert_eq!(body, "replica-data");
|
|
}
|