use async_graphql::Request;
use distributed::graphql::{read, GraphqlEngine, ModelPermissions};
use super::common::{
assert_no_sql_leak, engine_all_columns, error_messages, extension_code, seed_orders, session,
OrderView,
};
#[tokio::test]
async fn default_engine_has_strict_where_on() {
let pool = seed_orders().await;
let engine = engine_all_columns(pool);
assert!(
engine.strict_where(),
"strict_where must default to true for ship API"
);
}
#[tokio::test]
async fn strict_unknown_where_key_errors() {
let pool = seed_orders().await;
let engine = engine_all_columns(pool);
let s = session("user", "x");
let resp = engine
.execute(
&s,
Request::new(
r#"{ orders(where: { not_a_real_column: { _eq: "x" }, status: { _eq: "open" } }) { order_id status } }"#,
),
)
.await;
assert!(
!resp.errors.is_empty(),
"unknown where key must fail under strict default, got data {:?}",
resp.data
);
assert_no_sql_leak(&resp);
let msgs = error_messages(&resp);
assert!(
msgs.contains("unknown") || msgs.contains("not_a_real_column") || msgs.contains("invalid"),
"expected client-safe unknown/invalid error, got {msgs}"
);
}
#[tokio::test]
async fn strict_denied_where_column_errors() {
let pool = seed_orders().await;
let engine = GraphqlEngine::builder(pool)
.roles(&["restricted"])
.model::<OrderView>(
ModelPermissions::new().grant("restricted", read().columns(["order_id", "status"])),
)
.build()
.unwrap();
assert!(engine.strict_where());
let s = session("restricted", "x");
let resp = engine
.execute(
&s,
Request::new(
r#"{ orders(where: { total_cents: { _eq: 100 }, status: { _eq: "open" } }) { order_id status } }"#,
),
)
.await;
assert!(
!resp.errors.is_empty(),
"denied where column must fail under strict default"
);
assert_no_sql_leak(&resp);
let msgs = error_messages(&resp);
assert!(
msgs.contains("total_cents") || msgs.contains("unknown") || msgs.contains("invalid"),
"expected unknown/denied field error, got {msgs}"
);
}
#[tokio::test]
async fn strict_junk_order_by_column_errors() {
let pool = seed_orders().await;
let engine = engine_all_columns(pool);
let s = session("user", "x");
let resp = engine
.execute(
&s,
Request::new(
r#"{ orders(order_by: [{ totally_fake: asc }, { status: desc }]) { order_id status } }"#,
),
)
.await;
assert!(
!resp.errors.is_empty(),
"junk order_by must fail under strict default"
);
assert_no_sql_leak(&resp);
let msgs = error_messages(&resp);
assert!(
msgs.contains("totally_fake") || msgs.contains("unknown") || msgs.contains("invalid"),
"expected unknown order field error, got {msgs}"
);
}
#[tokio::test]
async fn strict_valid_where_and_order_still_work() {
let pool = seed_orders().await;
let engine = engine_all_columns(pool);
let s = session("user", "x");
let resp = engine
.execute(
&s,
Request::new(
r#"{ orders(where: { status: { _eq: "open" } }, order_by: [{ status: asc }]) { order_id status } }"#,
),
)
.await;
assert!(resp.errors.is_empty(), "{:?}", resp.errors);
let data = serde_json::to_value(&resp.data).unwrap();
let orders = data["orders"].as_array().unwrap();
assert!(!orders.is_empty());
assert!(orders.iter().all(|o| o["status"] == "open"), "{data}");
}
#[tokio::test]
async fn soft_skip_mode_is_opt_in_only() {
let pool = seed_orders().await;
let engine = GraphqlEngine::builder(pool)
.roles(&["user"])
.model::<OrderView>(ModelPermissions::new().grant("user", read().all_columns()))
.strict_where(false)
.build()
.unwrap();
assert!(
!engine.strict_where(),
"escape hatch must turn strict_where off"
);
let s = session("user", "x");
let resp = engine
.execute(
&s,
Request::new(r#"{ orders(where: { status: { _eq: "open" } }) { order_id } }"#),
)
.await;
assert!(
resp.errors.is_empty(),
"valid query still works: {:?}",
resp.errors
);
let _ = extension_code;
}