use proptest::prelude::*;
use crate::ast;
use crate::tests::property_tests::generators::documents::arb_executable_document;
use crate::tests::property_tests::generators::documents::arb_schema_document;
use crate::tests::property_tests::proptest_config;
use crate::GraphQLParser;
use crate::GraphQLParserConfig;
fn is_valid_name(name: &str) -> bool {
let mut chars = name.chars();
match chars.next() {
Some(c) if c == '_' || c.is_ascii_alphabetic() => {},
_ => return false,
}
chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}
fn collect_definition_names(doc: ast::Document<'_>) -> Vec<String> {
let mut names = Vec::new();
for def in doc.definitions {
match def {
ast::Definition::OperationDefinition(op) => {
if let Some(name) = op.name {
names.push(name.value.into_owned());
}
},
ast::Definition::FragmentDefinition(frag) => {
names.push(frag.name.value.into_owned());
},
ast::Definition::TypeDefinition(td) => {
names.push(type_def_name_owned(td));
},
ast::Definition::TypeExtension(te) => {
names.push(type_ext_name_owned(te));
},
ast::Definition::DirectiveDefinition(dd) => {
names.push(dd.name.value.into_owned());
},
ast::Definition::SchemaDefinition(_)
| ast::Definition::SchemaExtension(_) => {},
}
}
names
}
fn type_def_name_owned(td: ast::TypeDefinition<'_>) -> String {
match td {
ast::TypeDefinition::Enum(e) => e.name.value.into_owned(),
ast::TypeDefinition::InputObject(io) => io.name.value.into_owned(),
ast::TypeDefinition::Interface(i) => i.name.value.into_owned(),
ast::TypeDefinition::Object(o) => o.name.value.into_owned(),
ast::TypeDefinition::Scalar(s) => s.name.value.into_owned(),
ast::TypeDefinition::Union(u) => u.name.value.into_owned(),
}
}
fn type_ext_name_owned(te: ast::TypeExtension<'_>) -> String {
match te {
ast::TypeExtension::Enum(e) => e.name.value.into_owned(),
ast::TypeExtension::InputObject(io) => io.name.value.into_owned(),
ast::TypeExtension::Interface(i) => i.name.value.into_owned(),
ast::TypeExtension::Object(o) => o.name.value.into_owned(),
ast::TypeExtension::Scalar(s) => s.name.value.into_owned(),
ast::TypeExtension::Union(u) => u.name.value.into_owned(),
}
}
fn has_executable_defs(doc: &ast::Document<'_>) -> bool {
doc.definitions.iter().any(|def| {
matches!(
def,
ast::Definition::OperationDefinition(_)
| ast::Definition::FragmentDefinition(_),
)
})
}
fn has_type_system_defs(doc: &ast::Document<'_>) -> bool {
doc.definitions.iter().any(|def| {
matches!(
def,
ast::Definition::TypeDefinition(_)
| ast::Definition::TypeExtension(_)
| ast::Definition::SchemaDefinition(_)
| ast::Definition::SchemaExtension(_)
| ast::Definition::DirectiveDefinition(_),
)
})
}
proptest! {
#![proptest_config(proptest_config())]
#[test]
fn schema_doc_names_are_valid(source in arb_schema_document(4)) {
let result = GraphQLParser::new(&source).parse_schema_document();
prop_assert!(
!result.has_errors(),
"Generated schema document should parse without errors.\n\
Source:\n{}",
source,
);
let names = collect_definition_names(result.into_ast());
for name in &names {
prop_assert!(
is_valid_name(name),
"Invalid name '{}' in schema document.\nSource:\n{}",
name,
source,
);
}
}
#[test]
fn executable_doc_names_are_valid(source in arb_executable_document(4)) {
let result = GraphQLParser::new(&source).parse_executable_document();
prop_assert!(
!result.has_errors(),
"Generated executable document should parse without errors.\n\
Source:\n{}",
source,
);
let names = collect_definition_names(result.into_ast());
for name in &names {
prop_assert!(
is_valid_name(name),
"Invalid name '{}' in executable document.\nSource:\n{}",
name,
source,
);
}
}
#[test]
fn operations_have_non_empty_selection_sets(
source in arb_executable_document(4)
) {
let result = GraphQLParser::new(&source).parse_executable_document();
prop_assert!(
!result.has_errors(),
"Generated executable document should parse without errors.\n\
Source:\n{}",
source,
);
let has_empty = result.into_ast().definitions.iter().any(|def| {
if let ast::Definition::OperationDefinition(op) = def {
op.selection_set.selections.is_empty()
} else {
false
}
});
prop_assert!(
!has_empty,
"Operation has empty selection set.\nSource:\n{}",
source,
);
}
#[test]
fn schema_docs_have_no_executable_definitions(
source in arb_schema_document(4)
) {
let result = GraphQLParser::new(&source).parse_schema_document();
prop_assert!(
!result.has_errors(),
"Generated schema document should parse without errors.\n\
Source:\n{}",
source,
);
prop_assert!(
!has_executable_defs(&result.into_ast()),
"Schema doc should not contain executable defs.\n\
Source:\n{}",
source,
);
}
#[test]
fn executable_docs_have_no_type_system_definitions(
source in arb_executable_document(4)
) {
let result = GraphQLParser::new(&source).parse_executable_document();
prop_assert!(
!result.has_errors(),
"Generated executable document should parse without errors.\n\
Source:\n{}",
source,
);
prop_assert!(
!has_type_system_defs(&result.into_ast()),
"Executable doc should not contain type-system defs.\n\
Source:\n{}",
source,
);
}
#[test]
fn syntax_populated_in_default_mode(source in arb_schema_document(3)) {
let result = GraphQLParser::new(&source).parse_schema_document();
prop_assert!(
!result.has_errors(),
"Generated schema document should parse without errors.\n\
Source:\n{}",
source,
);
prop_assert!(
result.into_ast().syntax.is_some(),
"Document syntax should be populated in default mode.\n\
Source:\n{}",
source,
);
}
#[test]
fn syntax_not_populated_in_lean_mode(source in arb_schema_document(3)) {
let result = GraphQLParser::with_config(
&source,
GraphQLParserConfig::lean(),
).parse_schema_document();
prop_assert!(
!result.has_errors(),
"Generated schema document should parse without errors.\n\
Source:\n{}",
source,
);
prop_assert!(
result.into_ast().syntax.is_none(),
"Document syntax should be None in lean mode.\n\
Source:\n{}",
source,
);
}
#[test]
fn enum_values_not_reserved_names(source in arb_schema_document(4)) {
let result = GraphQLParser::new(&source).parse_schema_document();
prop_assert!(
!result.has_errors(),
"Generated schema document should parse without errors.\n\
Source:\n{}",
source,
);
let has_reserved = result.into_ast().definitions.iter().any(|def| {
if let ast::Definition::TypeDefinition(
ast::TypeDefinition::Enum(enum_def),
) = def {
enum_def.values.iter().any(|val| {
let name = val.name.value.as_ref();
name == "true" || name == "false" || name == "null"
})
} else {
false
}
});
prop_assert!(
!has_reserved,
"Enum value has reserved name in:\n{}",
source,
);
}
}