#![allow(clippy::unwrap_used)] #![allow(clippy::panic)]
use fraiseql_cli::schema::{ConvertOptions, SchemaConverter, intermediate::IntermediateSchema};
use fraiseql_core::schema::{PaginationOrder, SourceKind};
use serde_json::{Value, json};
fn corpus(queries: Value) -> Value {
let mut schema = json!({
"types": [{
"name": "LineItem",
"sql_source": "v_line_item",
"is_input": false,
"fields": [{"name": "label", "type": "String", "nullable": false},
{"name": "amount", "type": "Int", "nullable": false}]
}],
"mutations": [],
});
schema["queries"] = queries;
schema
}
fn line_items() -> Value {
json!([{
"name": "lineItems",
"return_type": "LineItem",
"returns_list": true,
"nullable": false,
"sql_source": "billing.fn_line_items",
"sql_source_kind": "function",
"arguments": [{"name": "assetId", "type": "ID", "nullable": false}],
"inject_params": {"tenant_id": "jwt:tenant_id"}
}])
}
fn line_items_with(key: &str, value: Value) -> Value {
let mut queries = line_items();
queries[0][key] = value;
queries
}
fn convert(corpus: Value) -> anyhow::Result<fraiseql_cli::schema::CompiledArtifact> {
let intermediate: IntermediateSchema =
serde_json::from_value(corpus).expect("the corpus must deserialize");
SchemaConverter::convert_artifact(intermediate, &ConvertOptions::default())
}
fn compiled(corpus: Value) -> fraiseql_core::schema::QueryDefinition {
let artifact = convert(corpus).expect("this declaration must compile");
artifact.schema.queries.into_iter().find(|q| q.name == "lineItems").unwrap()
}
fn refused(corpus: Value) -> String {
let message = match convert(corpus) {
Ok(_) => panic!("this declaration must not compile"),
Err(error) => format!("{error:#}"),
};
assert!(
message.contains("sql_source_kind = \"function\""),
"refused by another rule than the function root's: {message}"
);
message
}
#[test]
fn a_function_root_compiles_as_a_list_read_of_its_function() {
let query = compiled(corpus(line_items()));
assert_eq!(query.sql_source_kind, SourceKind::Function);
assert_eq!(query.sql_source.as_deref(), Some("billing.fn_line_items"));
assert!(query.inject_params.contains_key("tenant_id"), "bound as the last parameter");
assert!(query.auto_params.has_where && query.auto_params.has_limit, "its rows are paged");
assert_eq!(query.function_arity(), 2, "assetId, then tenant_id");
}
#[test]
fn a_function_root_derives_no_identity_order_its_function_cannot_return() {
assert_eq!(compiled(corpus(line_items())).pagination_order, None);
}
#[test]
fn a_function_root_keeps_the_page_order_its_author_declared() {
let query = compiled(corpus(line_items_with("pagination_order", json!("line_no"))));
assert_eq!(query.pagination_order, Some(PaginationOrder::Column("line_no".to_string())));
}
#[test]
fn the_function_kind_is_spelled_out_in_the_artifact_and_the_relation_kind_is_not() {
let function = serde_json::to_value(compiled(corpus(line_items()))).unwrap();
assert_eq!(function["sql_source_kind"], "function", "{function}");
let mut relation = line_items();
relation[0].as_object_mut().unwrap().remove("sql_source_kind");
relation[0]["sql_source"] = json!("v_line_item");
let relation = serde_json::to_value(compiled(corpus(relation))).unwrap();
assert!(relation.get("sql_source_kind").is_none(), "{relation}");
}
#[test]
fn an_unknown_source_kind_does_not_deserialize() {
let result = serde_json::from_value::<IntermediateSchema>(corpus(line_items_with(
"sql_source_kind",
json!("procedure"),
)));
assert!(result.is_err(), "a kind nothing reads must not compile as a relation");
}
#[test]
fn a_function_root_without_a_sql_source_is_refused() {
let mut queries = line_items();
queries[0].as_object_mut().unwrap().remove("sql_source");
assert!(refused(corpus(queries)).contains("names no function"));
}
#[test]
fn relay_beside_a_function_root_is_refused() {
let message = refused(corpus(line_items_with("relay", json!(true))));
assert!(message.contains("relay = true"), "{message}");
}
#[test]
fn a_count_sibling_beside_a_function_root_is_refused() {
let message = refused(corpus(line_items_with("count", json!(true))));
assert!(message.contains("count = true"), "{message}");
}
#[test]
fn a_streamed_export_beside_a_function_root_is_refused() {
let message = refused(corpus(line_items_with("rest_stream", json!(true))));
assert!(message.contains("rest_stream = true"), "{message}");
}
#[test]
fn a_rest_route_beside_a_function_root_is_refused() {
let message = refused(corpus(line_items_with("rest", json!({"path": "/line-items"}))));
assert!(message.contains("a rest block"), "{message}");
}
#[test]
fn the_refuse_posture_asks_a_function_root_for_a_declared_order() {
let mut refuse = corpus(line_items());
refuse["query_defaults"] = json!({"where_clause": true, "order_by": true, "limit": true,
"offset": true, "pagination_order": "refuse"});
let message = refused(refuse.clone());
assert!(message.contains("pagination_order = \"refuse\""), "{message}");
refuse["queries"] = line_items_with("pagination_order", json!("line_no"));
assert_eq!(
compiled(refuse).pagination_order,
Some(PaginationOrder::Column("line_no".to_string()))
);
}