Skip to content

Commit

Permalink
Implement test cases for server
Browse files Browse the repository at this point in the history
  • Loading branch information
paolorechia committed Aug 7, 2024
1 parent 4a23b4b commit 727ea41
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 28 deletions.
5 changes: 4 additions & 1 deletion steeldb-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ edition = "2021"
steeldb-core = { path = "../steeldb-core", features = ["json"]}
steeldb = { path = ".."}
serde_json = "1.0.111"
axum = "0.7.3"
axum = "0.7.5"
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] }
tower = "0.3"
http-body-util = "0.1.2"
mime = "0.3.17"
173 changes: 146 additions & 27 deletions steeldb-server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
use axum::{extract::State, http::StatusCode, routing::post, Json, Router};
use std::sync::{Arc, Mutex};
use steeldb::SteelDB;
use steeldb_core::json_result::{TableJSON, QueryResultJSON, UserQueryJSON};
use steeldb_core::json_result::{QueryResultJSON, TableJSON, UserQueryJSON};
use steeldb_core::{ExecutionResult, SteelDBInterface};


#[tokio::main]
async fn main() {
fn build_app() -> Router {
// build our application with a route
let database = Arc::new(Mutex::new(SteelDB::new()));

// build our application with a route
let app = Router::new()
// `GET /` goes to `root`
.route("/query", post(handle_query))
.with_state(database);
return app;
}

#[tokio::main]
async fn main() {
let app = build_app();
// run our app with hyper, listening globally on port 3000
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}


async fn handle_query(
State(database): State<Arc<Mutex<SteelDB>>>,
Json(payload): Json<UserQueryJSON>,
Expand All @@ -42,35 +43,153 @@ async fn handle_query(
select_columns: table.get_select_columns().clone(),
}),
message: "query successful".to_string(),
status_code: StatusCode::OK.as_u16()
status_code: StatusCode::OK.as_u16(),
};
return (
StatusCode::OK,
Json(result),
);
return (StatusCode::OK, Json(result));
}
ExecutionResult::ParseError(error) => {
return (StatusCode::BAD_REQUEST, Json(QueryResultJSON{
table_result: None,
message: format!("failed to execute query: {error}"),
status_code: StatusCode::BAD_REQUEST.as_u16()
}));
return (
StatusCode::BAD_REQUEST,
Json(QueryResultJSON {
table_result: None,
message: format!("failed to execute query: {error}"),
status_code: StatusCode::BAD_REQUEST.as_u16(),
}),
);
}
ExecutionResult::CommandError(error) => {
return (StatusCode::INTERNAL_SERVER_ERROR, Json(QueryResultJSON{
table_result: None,
message: format!("failed to execute query: {error}"),
status_code: StatusCode::INTERNAL_SERVER_ERROR.as_u16()
}));
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(QueryResultJSON {
table_result: None,
message: format!("failed to execute query: {error}"),
status_code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
}),
);
}
ExecutionResult::VoidOK => {
return (StatusCode::OK, Json(
QueryResultJSON {
return (
StatusCode::OK,
Json(QueryResultJSON {
table_result: None,
message: format!("Query successful"),
status_code: StatusCode::OK.as_u16()
}
))
status_code: StatusCode::OK.as_u16(),
}),
)
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use axum::{
body::Body,
http::{self, Request, StatusCode},
};
use http_body_util::BodyExt;
use serde_json::json;
// use tokio::net::TcpListener;
use tower::util::ServiceExt;

#[tokio::test]
async fn test_app() {
// Unfortunately these tests require a more elaborate fixture injection.
// While there are interesting libraries like rstest, it doesn't seem
// to support tokio out of the box.

// On the other hand, if we instantiate a new app on each test case,
// Then we end up in a PANIC triggered by the web framework,
// likely because it doesn't expect multiple app instances floating around.

// Test main query OK case
let app = build_app();
let mut response = app
.clone()
.oneshot(
Request::builder()
.method(http::Method::POST)
.uri("/query")
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(
json!({
"user_query": "select name;"
})
.to_string(),
))
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::OK);

// Test column not found
response = app
.clone()
.oneshot(
Request::builder()
.method(http::Method::POST)
.uri("/query")
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(
json!({
"user_query": "select nope;"
})
.to_string(),
))
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
let body: &[u8] = &response.into_body().collect().await.unwrap().to_bytes();
let s = String::from_utf8_lossy(body);
assert_eq!(true, s.contains("ColumnNotFound(\\\"nope\\\")"));

// Test malformed query
response = app
.clone()
.oneshot(
Request::builder()
.method(http::Method::POST)
.uri("/query")
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(
json!({
"user_query": "balsdfl"
})
.to_string(),
))
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::BAD_REQUEST.as_u16());
let body: &[u8] = &response.into_body().collect().await.unwrap().to_bytes();
let s = String::from_utf8_lossy(body);
assert_eq!(true, s.contains("UnrecognizedToken"));

// Test malformed query
response = app
.clone()
.oneshot(
Request::builder()
.method(http::Method::POST)
.uri("/query")
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
.body(Body::from(
json!({
"hello": "world"
})
.to_string(),
))
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY.as_u16());
}
}

0 comments on commit 727ea41

Please sign in to comment.