Initial kosync-rs port
This commit is contained in:
264
tests/api.rs
Normal file
264
tests/api.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
use axum::body::{to_bytes, Body};
|
||||
use axum::http::{Request, StatusCode};
|
||||
use serde_json::{json, Value};
|
||||
use tempfile::TempDir;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use kosync_rs::http::{router, AppState};
|
||||
use kosync_rs::store::Store;
|
||||
|
||||
async fn test_app(registration_enabled: bool) -> (axum::Router, TempDir) {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let db = tmp.path().join("kosync.sqlite3");
|
||||
let store = Store::new(db);
|
||||
store.init().await.unwrap();
|
||||
let app = router(AppState {
|
||||
store,
|
||||
registration_enabled,
|
||||
});
|
||||
(app, tmp)
|
||||
}
|
||||
|
||||
async fn hit(
|
||||
app: axum::Router,
|
||||
method: &str,
|
||||
path: &str,
|
||||
auth: Option<(&str, &str)>,
|
||||
body: Option<Value>,
|
||||
) -> (StatusCode, Value) {
|
||||
let mut builder = Request::builder().method(method).uri(path);
|
||||
if let Some((username, key)) = auth {
|
||||
builder = builder
|
||||
.header("x-auth-user", username)
|
||||
.header("x-auth-key", key);
|
||||
}
|
||||
let request = if let Some(body) = body {
|
||||
builder
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(body.to_string()))
|
||||
.unwrap()
|
||||
} else {
|
||||
builder.body(Body::empty()).unwrap()
|
||||
};
|
||||
let response = app.oneshot(request).await.unwrap();
|
||||
let status = response.status();
|
||||
let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
let json = serde_json::from_slice(&bytes).unwrap();
|
||||
(status, json)
|
||||
}
|
||||
|
||||
async fn register(app: axum::Router, username: &str, password: &str) -> (StatusCode, Value) {
|
||||
hit(
|
||||
app,
|
||||
"POST",
|
||||
"/users/create",
|
||||
None,
|
||||
Some(json!({ "username": username, "password": password })),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update(
|
||||
app: axum::Router,
|
||||
username: &str,
|
||||
key: &str,
|
||||
document: &str,
|
||||
percentage: Value,
|
||||
progress: Value,
|
||||
device: Value,
|
||||
) -> (StatusCode, Value) {
|
||||
hit(
|
||||
app,
|
||||
"PUT",
|
||||
"/syncs/progress",
|
||||
Some((username, key)),
|
||||
Some(json!({
|
||||
"document": document,
|
||||
"percentage": percentage,
|
||||
"progress": progress,
|
||||
"device": device
|
||||
})),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_and_reject_duplicates() {
|
||||
let (app, _tmp) = test_app(true).await;
|
||||
|
||||
let (status, body) = register(app.clone(), "new-user", "passwd123").await;
|
||||
assert_eq!(status, StatusCode::CREATED);
|
||||
assert_eq!(body, json!({ "username": "new-user" }));
|
||||
|
||||
let (status, body) = register(app, "new-user", "passwd123").await;
|
||||
assert_eq!(status, StatusCode::PAYMENT_REQUIRED);
|
||||
assert_eq!(
|
||||
body,
|
||||
json!({ "code": 2002, "message": "Username is already registered." })
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn registration_can_be_disabled() {
|
||||
let (app, _tmp) = test_app(false).await;
|
||||
let (status, body) = register(app, "new-user", "passwd123").await;
|
||||
assert_eq!(status, StatusCode::PAYMENT_REQUIRED);
|
||||
assert_eq!(
|
||||
body,
|
||||
json!({ "code": 2005, "message": "User registration is disabled." })
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn auth_matches_original_behavior() {
|
||||
let (app, _tmp) = test_app(true).await;
|
||||
register(app.clone(), "user1", "passwd123").await;
|
||||
|
||||
let (status, body) = hit(app.clone(), "GET", "/users/auth", Some(("user1", "")), None).await;
|
||||
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
||||
assert_eq!(body, json!({ "code": 2001, "message": "Unauthorized" }));
|
||||
|
||||
let (status, body) = hit(
|
||||
app.clone(),
|
||||
"GET",
|
||||
"/users/auth",
|
||||
Some(("user1", "wrong_password")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
||||
assert_eq!(body, json!({ "code": 2001, "message": "Unauthorized" }));
|
||||
|
||||
let (status, body) = hit(
|
||||
app,
|
||||
"GET",
|
||||
"/users/auth",
|
||||
Some(("user1", "passwd123")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body, json!({ "authorized": "OK" }));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn progress_round_trip_and_last_write_wins() {
|
||||
let (app, _tmp) = test_app(true).await;
|
||||
register(app.clone(), "user1", "passwd123").await;
|
||||
|
||||
let (status, body) = update(
|
||||
app.clone(),
|
||||
"user1",
|
||||
"wrong",
|
||||
"89isjkdaj9j",
|
||||
json!(0.32),
|
||||
json!("56"),
|
||||
json!("my kpw"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::UNAUTHORIZED);
|
||||
assert_eq!(body, json!({ "code": 2001, "message": "Unauthorized" }));
|
||||
|
||||
let (status, body) = update(
|
||||
app.clone(),
|
||||
"user1",
|
||||
"passwd123",
|
||||
"89isjkdaj9j",
|
||||
json!(0.32),
|
||||
json!("56"),
|
||||
json!("my kpw"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body["document"], "89isjkdaj9j");
|
||||
assert!(body["timestamp"].as_i64().is_some());
|
||||
|
||||
let (status, body) = hit(
|
||||
app.clone(),
|
||||
"GET",
|
||||
"/syncs/progress/89isjkdaj9jnon_existent",
|
||||
Some(("user1", "passwd123")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body, json!({}));
|
||||
|
||||
update(
|
||||
app.clone(),
|
||||
"user1",
|
||||
"passwd123",
|
||||
"89isjkdaj9j",
|
||||
json!(0.22),
|
||||
json!("36"),
|
||||
json!("my pb"),
|
||||
)
|
||||
.await;
|
||||
let (status, mut body) = hit(
|
||||
app,
|
||||
"GET",
|
||||
"/syncs/progress/89isjkdaj9j",
|
||||
Some(("user1", "passwd123")),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert!(body["timestamp"].as_i64().is_some());
|
||||
body.as_object_mut().unwrap().remove("timestamp");
|
||||
assert_eq!(
|
||||
body,
|
||||
json!({
|
||||
"document": "89isjkdaj9j",
|
||||
"percentage": 0.22,
|
||||
"progress": "36",
|
||||
"device": "my pb"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn validation_errors_match_compatibility_contract() {
|
||||
let (app, _tmp) = test_app(true).await;
|
||||
register(app.clone(), "user1", "passwd123").await;
|
||||
|
||||
let (status, body) = register(app.clone(), "bad:user", "passwd123").await;
|
||||
assert_eq!(status, StatusCode::FORBIDDEN);
|
||||
assert_eq!(body, json!({ "code": 2003, "message": "Invalid request" }));
|
||||
|
||||
let (status, body) = update(
|
||||
app.clone(),
|
||||
"user1",
|
||||
"passwd123",
|
||||
"bad:doc",
|
||||
json!(0),
|
||||
json!(""),
|
||||
json!(""),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::FORBIDDEN);
|
||||
assert_eq!(
|
||||
body,
|
||||
json!({ "code": 2004, "message": "Field 'document' not provided." })
|
||||
);
|
||||
|
||||
let (status, body) = update(
|
||||
app,
|
||||
"user1",
|
||||
"passwd123",
|
||||
"doc",
|
||||
json!("not-a-number"),
|
||||
json!(""),
|
||||
json!(""),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::FORBIDDEN);
|
||||
assert_eq!(body, json!({ "code": 2003, "message": "Invalid request" }));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn healthcheck_returns_ok() {
|
||||
let (app, _tmp) = test_app(true).await;
|
||||
let (status, body) = hit(app, "GET", "/healthcheck", None, None).await;
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
assert_eq!(body, json!({ "state": "OK" }));
|
||||
}
|
||||
Reference in New Issue
Block a user