use std::path::PathBuf;
use apollo_router::_private::create_test_service_factory_from_yaml;
use serde_json::json;
use tower::ServiceExt;
use crate::integration::IntegrationTest;
use crate::integration::common::Query;
#[tokio::test]
async fn test_supergraph_validation_errors_are_passed_on() {
create_test_service_factory_from_yaml(
include_str!("../../src/testdata/invalid_supergraph.graphql"),
"supergraph:\n introspection: true\n",
)
.await;
}
#[tokio::test]
async fn test_request_extensions_is_null() {
let request =
serde_json::json!({"query": "{__typename}", "extensions": serde_json::Value::Null});
let request = apollo_router::services::router::Request::fake_builder()
.body(request.to_string())
.method(hyper::Method::POST)
.header("content-type", "application/json")
.build()
.unwrap();
let response = apollo_router::TestHarness::builder()
.schema(include_str!("../fixtures/supergraph.graphql"))
.build_router()
.await
.unwrap()
.oneshot(request)
.await
.unwrap()
.next_response()
.await
.unwrap()
.unwrap();
assert_eq!(
String::from_utf8_lossy(&response),
r#"{"data":{"__typename":"Query"}}"#
);
}
#[tokio::test]
async fn test_syntax_error() {
let request = serde_json::json!({"query": "{__typename"});
let request = apollo_router::services::router::Request::fake_builder()
.body(request.to_string())
.method(hyper::Method::POST)
.header("content-type", "application/json")
.build()
.unwrap();
let response = apollo_router::TestHarness::builder()
.schema(include_str!("../fixtures/supergraph.graphql"))
.build_router()
.await
.unwrap()
.oneshot(request)
.await
.unwrap()
.next_response()
.await
.unwrap()
.unwrap();
let v: serde_json::Value = serde_json::from_slice(&response).unwrap();
insta::assert_json_snapshot!(v, @r###"
{
"errors": [
{
"message": "parsing error: syntax error: expected R_CURLY, got EOF",
"locations": [
{
"line": 1,
"column": 12
}
],
"extensions": {
"code": "PARSING_ERROR"
}
}
]
}
"###);
}
#[tokio::test]
async fn test_validation_error() {
let request = serde_json::json!({"query": "{...a} fragment unused on Query { me { id } } fragment a on Query{me {id} topProducts(first: 5.5) {id}}"});
let request = apollo_router::services::router::Request::fake_builder()
.body(request.to_string())
.method(hyper::Method::POST)
.header("content-type", "application/json")
.build()
.unwrap();
let response = apollo_router::TestHarness::builder()
.schema(include_str!("../fixtures/supergraph.graphql"))
.build_router()
.await
.unwrap()
.oneshot(request)
.await
.unwrap()
.next_response()
.await
.unwrap()
.unwrap();
let v: serde_json::Value = serde_json::from_slice(&response).unwrap();
insta::assert_json_snapshot!(v, @r###"
{
"errors": [
{
"message": "Fragment \"unused\" is never used.",
"locations": [
{
"line": 1,
"column": 8
}
],
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED"
}
},
{
"message": "Field \"topProducts\" of type \"Query\" must have a selection of subfields. Did you mean \"topProducts { ... }\"?",
"locations": [
{
"line": 1,
"column": 75
}
],
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED"
}
},
{
"message": "Int cannot represent value: 5.5",
"locations": [
{
"line": 1,
"column": 94
}
],
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED"
}
},
{
"message": "Cannot query field \"id\" on type \"Product\".",
"locations": [
{
"line": 1,
"column": 100
}
],
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED"
}
}
]
}
"###);
}
#[tokio::test]
async fn test_lots_of_validation_errors() {
let query = format!(
"{{ __typename {} }}",
"@a".repeat(4_000), );
let request = serde_json::json!({ "query": query });
let request = apollo_router::services::router::Request::fake_builder()
.body(request.to_string())
.method(hyper::Method::POST)
.header("content-type", "application/json")
.build()
.unwrap();
let response = apollo_router::TestHarness::builder()
.schema(include_str!("../fixtures/supergraph.graphql"))
.build_router()
.await
.unwrap()
.oneshot(request)
.await
.unwrap()
.next_response()
.await
.unwrap()
.unwrap();
let v: serde_json::Value = serde_json::from_slice(&response).unwrap();
let errors = v["errors"].as_array().unwrap();
assert!(!errors.is_empty(), "should have errors");
assert_eq!(
errors.first().unwrap()["extensions"]["code"],
serde_json::Value::from("GRAPHQL_VALIDATION_FAILED")
);
assert!(errors.len() <= 100, "should return limited error count");
}
#[rstest::rstest]
#[case::enforce(Some("enforce"), true, false)]
#[case::measure(Some("measure"), false, true)]
#[case::missing(None, true, false)]
#[tokio::test(flavor = "multi_thread")]
async fn variable_validation_mode_propagates_fully(
#[case] strict_variable_validation: Option<&str>,
#[case] response_should_be_error: bool,
#[case] logs_should_contain_warning: bool,
) {
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_suffix(strict_variable_validation.unwrap_or("missing"));
settings.set_sort_maps(true);
let _guard = settings.bind_to_scope();
let mut config = json!({"supergraph": {}});
if let Some(strict_variable_validation) = strict_variable_validation {
config["supergraph"] = json!({ "strict_variable_validation": strict_variable_validation });
}
let mut router = IntegrationTest::builder()
.config(serde_yaml::to_string(&config).unwrap())
.supergraph(PathBuf::from(
"tests/fixtures/supergraph_input_variables.graphql",
))
.build()
.await;
router.start().await;
router.assert_started().await;
let (_trace_id, response) = router
.execute_query(
Query::builder()
.body(json!({
"query": "query($msg: MessageInput) { send(message: $msg) { id } }",
"variables": {
"msg": {
"content": "Hello",
"author": "Me",
"canvas": [{"input": 4, "innput": 4}],
}
}
}))
.build(),
)
.await;
assert_eq!(
response.status().is_client_error(),
response_should_be_error
);
let response_body: serde_json::Value =
serde_json::from_slice(response.text().await.unwrap().as_bytes()).unwrap();
insta::assert_json_snapshot!(response_body);
router.read_logs();
const VALIDATION_MESSAGE: &str = "encountered unexpected variable(s)";
if logs_should_contain_warning {
router.assert_log_contained(VALIDATION_MESSAGE);
} else {
router.assert_log_not_contained(VALIDATION_MESSAGE);
}
router.graceful_shutdown().await;
}