Skip to content

Commit

Permalink
add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
BenoitRanque committed Jun 12, 2024
1 parent 496a6ea commit 923cbab
Show file tree
Hide file tree
Showing 61 changed files with 2,651 additions and 46 deletions.
212 changes: 166 additions & 46 deletions crates/ndc-clickhouse/tests/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use ndc_clickhouse::sql::QueryBuilderError;
use ndc_sdk::models;
use schemars::schema_for;
use std::error::Error;
use test_utils::{test_error, test_generated_sql};
use tokio::fs;

mod test_utils {
Expand All @@ -21,43 +20,58 @@ mod test_utils {
/// we don't want that behavior when running CI, so this value should be false in commited code
const UPDATE_GENERATED_SQL: bool = false;

fn base_path(group_dir: &str) -> PathBuf {
fn base_path(schema_dir: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("query_builder")
.join(group_dir)
.join(schema_dir)
}
async fn read_mock_configuration(group_dir: &str) -> Result<ServerConfig, Box<dyn Error>> {
fn tests_dir_path(schema_dir: &str, group_dir: &str) -> PathBuf {
base_path(schema_dir).join(group_dir)
}
fn config_dir_path(schema_dir: &str) -> PathBuf {
base_path(schema_dir).join("_config")
}
async fn read_mock_configuration(schema_dir: &str) -> Result<ServerConfig, Box<dyn Error>> {
// set mock values for required env vars, we won't be reading these anyways
env::set_var("CLICKHOUSE_URL", "");
env::set_var("CLICKHOUSE_USERNAME", "");
env::set_var("CLICKHOUSE_PASSWORD", "");
let config_dir = base_path(group_dir).join("config");
let config_dir = config_dir_path(schema_dir);
let configuration = read_server_config(config_dir).await?;
Ok(configuration)
}
async fn read_request(
schema_dir: &str,
group_dir: &str,
test_name: &str,
) -> Result<QueryRequest, Box<dyn Error>> {
let request_path = base_path(group_dir).join(format!("{test_name}.request.json"));
let request_path =
tests_dir_path(schema_dir, group_dir).join(format!("{test_name}.request.json"));

let file_content = fs::read_to_string(request_path).await?;
let request: models::QueryRequest = serde_json::from_str(&file_content)?;

Ok(request)
}
async fn read_expected_sql(group_dir: &str, test_name: &str) -> Result<String, Box<dyn Error>> {
let statement_path = base_path(group_dir).join(format!("{test_name}.statement.sql"));
async fn read_expected_sql(
schema_dir: &str,
group_dir: &str,
test_name: &str,
) -> Result<String, Box<dyn Error>> {
let statement_path =
tests_dir_path(schema_dir, group_dir).join(format!("{test_name}.statement.sql"));
let expected_statement = fs::read_to_string(&statement_path).await?;
Ok(expected_statement)
}
async fn write_expected_sql(
schema_dir: &str,
group_dir: &str,
test_name: &str,
generated_statement: &str,
) -> Result<(), Box<dyn Error>> {
let statement_path = base_path(group_dir).join(format!("{test_name}.statement.sql"));
let statement_path =
tests_dir_path(schema_dir, group_dir).join(format!("{test_name}.statement.sql"));
let pretty_statement = pretty_print_sql(&generated_statement);
fs::write(&statement_path, &pretty_statement).await?;
Ok(())
Expand Down Expand Up @@ -85,31 +99,33 @@ mod test_utils {
Ok(generated_statement)
}
pub async fn test_generated_sql(
schema_dir: &str,
group_dir: &str,
test_name: &str,
) -> Result<(), Box<dyn Error>> {
let configuration = read_mock_configuration(group_dir).await?;
let request = read_request(group_dir, test_name).await?;
let configuration = read_mock_configuration(schema_dir).await?;
let request = read_request(schema_dir, group_dir, test_name).await?;

let generated_sql = generate_sql(&configuration, &request)?;

let expected_sql = read_expected_sql(group_dir, test_name).await?;

if UPDATE_GENERATED_SQL {
write_expected_sql(group_dir, test_name, &generated_sql).await?;
}
write_expected_sql(schema_dir, group_dir, test_name, &generated_sql).await?;
} else {
let expected_sql = read_expected_sql(schema_dir, group_dir, test_name).await?;

assert_eq!(generated_sql, expected_sql);
assert_eq!(generated_sql, expected_sql);
}

Ok(())
}
pub async fn test_error(
schema_dir: &str,
group_dir: &str,
test_name: &str,
err: QueryBuilderError,
) -> Result<(), Box<dyn Error>> {
let configuration = read_mock_configuration(group_dir).await?;
let request = read_request(group_dir, test_name).await?;
let configuration = read_mock_configuration(schema_dir).await?;
let request = read_request(schema_dir, group_dir, test_name).await?;

let result = generate_sql(&configuration, &request);

Expand All @@ -135,35 +151,139 @@ async fn update_json_schema() -> Result<(), Box<dyn Error>> {

Ok(())
}
#[tokio::test]
async fn generate_column_accessor() -> Result<(), Box<dyn Error>> {
test_generated_sql("field_selector", "01_generate_column_accessor").await
}
#[tokio::test]
async fn skip_if_not_required() -> Result<(), Box<dyn Error>> {
test_generated_sql("field_selector", "02_skip_if_not_required").await
}
#[tokio::test]
async fn support_relationships_on_nested_field() -> Result<(), Box<dyn Error>> {
test_generated_sql("field_selector", "03_support_relationships_on_nested_field").await

#[cfg(test)]
mod simple_queries {
use super::*;

async fn test_generated_sql(name: &str) -> Result<(), Box<dyn Error>> {
super::test_utils::test_generated_sql("chinook", "01_simple_queries", name).await
}

#[tokio::test]
async fn select_rows() -> Result<(), Box<dyn Error>> {
test_generated_sql("01_select_rows").await
}
#[tokio::test]
async fn with_predicate() -> Result<(), Box<dyn Error>> {
test_generated_sql("02_with_predicate").await
}
#[tokio::test]
async fn larger_predicate() -> Result<(), Box<dyn Error>> {
test_generated_sql("03_larger_predicate").await
}
#[tokio::test]
async fn limit() -> Result<(), Box<dyn Error>> {
test_generated_sql("04_limit").await
}
#[tokio::test]
async fn offset() -> Result<(), Box<dyn Error>> {
test_generated_sql("05_offset").await
}
#[tokio::test]
async fn limit_offset() -> Result<(), Box<dyn Error>> {
test_generated_sql("06_limit_offset").await
}

#[tokio::test]
async fn order_by() -> Result<(), Box<dyn Error>> {
test_generated_sql("07_order_by").await
}
#[tokio::test]
async fn predicate_limit_offset_order_by() -> Result<(), Box<dyn Error>> {
test_generated_sql("08_predicate_limit_offset_order_by").await
}
}
/// We do not support relationships on nested fileds if an array has been traversed
#[tokio::test]
async fn error_on_relationships_on_array_nested_field() -> Result<(), Box<dyn Error>> {
let err =
QueryBuilderError::NotSupported("Relationships with fields nested in arrays".to_string());
test_error(
"field_selector",
"04_error_on_relationships_on_array_nested_field",
err,
)
.await

#[cfg(test)]
mod relationships {
use super::*;

async fn test_generated_sql(name: &str) -> Result<(), Box<dyn Error>> {
super::test_utils::test_generated_sql("chinook", "02_relationships", name).await
}

#[tokio::test]
async fn object_relationship() -> Result<(), Box<dyn Error>> {
test_generated_sql("01_object_relationship").await
}
#[tokio::test]
async fn array_relationship() -> Result<(), Box<dyn Error>> {
test_generated_sql("02_array_relationship").await
}
#[tokio::test]
async fn parent_predicate() -> Result<(), Box<dyn Error>> {
test_generated_sql("03_parent_predicate").await
}
#[tokio::test]
async fn child_predicate() -> Result<(), Box<dyn Error>> {
test_generated_sql("04_child_predicate").await
}
#[tokio::test]
async fn traverse_relationship_in_predicate() -> Result<(), Box<dyn Error>> {
test_generated_sql("05_traverse_relationship_in_predicate").await
}
#[tokio::test]
async fn traverse_relationship_in_order_by() -> Result<(), Box<dyn Error>> {
test_generated_sql("06_traverse_relationship_in_order_by").await
}
#[tokio::test]
async fn order_by_aggregate_across_relationships() -> Result<(), Box<dyn Error>> {
test_generated_sql("07_order_by_aggregate_across_relationships").await
}
}
#[tokio::test]
async fn complex_example() -> Result<(), Box<dyn Error>> {
test_generated_sql("field_selector", "05_complex_example").await

#[cfg(test)]
mod native_queries {
use super::*;

async fn test_generated_sql(name: &str) -> Result<(), Box<dyn Error>> {
super::test_utils::test_generated_sql("star_schema", "01_native_queries", name).await
}

#[tokio::test]
async fn native_query() -> Result<(), Box<dyn Error>> {
test_generated_sql("01_native_query").await
}
}
#[tokio::test]
async fn no_useless_nested_accessors() -> Result<(), Box<dyn Error>> {
test_generated_sql("field_selector", "06_no_useless_nested_accessors").await

#[cfg(test)]
mod field_selector {
use super::*;

async fn test_generated_sql(name: &str) -> Result<(), Box<dyn Error>> {
super::test_utils::test_generated_sql("complex_columns", "field_selector", name).await
}
async fn test_error(name: &str, err: QueryBuilderError) -> Result<(), Box<dyn Error>> {
super::test_utils::test_error("complex_columns", "field_selector", name, err).await
}

#[tokio::test]
async fn generate_column_accessor() -> Result<(), Box<dyn Error>> {
test_generated_sql("01_generate_column_accessor").await
}
#[tokio::test]
async fn skip_if_not_required() -> Result<(), Box<dyn Error>> {
test_generated_sql("02_skip_if_not_required").await
}
#[tokio::test]
async fn support_relationships_on_nested_field() -> Result<(), Box<dyn Error>> {
test_generated_sql("03_support_relationships_on_nested_field").await
}
/// We do not support relationships on nested fileds if an array has been traversed
#[tokio::test]
async fn error_on_relationships_on_array_nested_field() -> Result<(), Box<dyn Error>> {
let err = QueryBuilderError::NotSupported(
"Relationships with fields nested in arrays".to_string(),
);
test_error("04_error_on_relationships_on_array_nested_field", err).await
}
#[tokio::test]
async fn complex_example() -> Result<(), Box<dyn Error>> {
test_generated_sql("05_complex_example").await
}
#[tokio::test]
async fn no_useless_nested_accessors() -> Result<(), Box<dyn Error>> {
test_generated_sql("06_no_useless_nested_accessors").await
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "../../request.schema.json",
"collection": "Chinook_Album",
"query": {
"fields": {
"albumId": {
"type": "column",
"column": "AlbumId",
"fields": null
},
"artistId": {
"type": "column",
"column": "ArtistId",
"fields": null
},
"title": {
"type": "column",
"column": "Title",
"fields": null
}
}
},
"arguments": {},
"collection_relationships": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
SELECT
toJSONString(
groupArray(
cast(
"_rowset"."_rowset",
'Tuple(rows Array(Tuple("albumId" Int32, "artistId" Int32, "title" String)))'
)
)
) AS "rowsets"
FROM
(
SELECT
tuple(
groupArray(
tuple(
"_row"."_field_albumId",
"_row"."_field_artistId",
"_row"."_field_title"
)
)
) AS "_rowset"
FROM
(
SELECT
"_origin"."AlbumId" AS "_field_albumId",
"_origin"."ArtistId" AS "_field_artistId",
"_origin"."Title" AS "_field_title"
FROM
"Chinook"."Album" AS "_origin"
) AS "_row"
) AS "_rowset" FORMAT TabSeparatedRaw;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "../request.schema.json",
"collection": "Chinook_Album",
"query": {
"fields": {
"albumId": {
"type": "column",
"column": "AlbumId",
"fields": null
},
"artistId": {
"type": "column",
"column": "ArtistId",
"fields": null
},
"title": {
"type": "column",
"column": "Title",
"fields": null
}
},
"predicate": {
"type": "and",
"expressions": [
{
"type": "and",
"expressions": [
{
"type": "binary_comparison_operator",
"column": {
"type": "column",
"name": "ArtistId",
"path": []
},
"operator": "_eq",
"value": {
"type": "scalar",
"value": "1"
}
}
]
}
]
}
},
"arguments": {},
"collection_relationships": {}
}
Loading

0 comments on commit 923cbab

Please sign in to comment.