use super::harness::{assert_coerced, assert_rejected, assert_rejected_by_validation};
const ONE_OF: &str = "query ($input: OneOfTestInput!) { test(input: $input) { a b } }";
const INT_ONE_OF: &str = "query ($input: IntOneOf) { intOneOf(input: $input) }";
const ONE_OF_LIST: &str = "query ($input: [OneOfTestInput!]) { oneOfList(input: $input) }";
const ONE_OF_HOLDER: &str = "query ($input: OneOfHolder) { oneOfHolder(input: $input) }";
const RECURSIVE_ONE_OF: &str = "query ($input: RecursiveOneOf) { recursiveOneOf(input: $input) }";
fn one_of_error(type_name: &str) -> String {
format!(
r#"Within OneOf Input Object type "{type_name}", exactly one field must be specified, and the value for that field must be non-null."#
)
}
#[track_caller]
fn rejects(operation: &str, variables: &str, path: &str, reason: &str) {
assert_rejected(
operation,
variables,
&format!(r#"Variable "$input" has invalid value{path}: {reason}"#),
);
}
#[track_caller]
fn accepts(operation: &str, variables: &str) {
assert_coerced(operation, variables, variables);
}
mod valid_values {
use super::*;
#[test]
fn accepts_oneof_with_single_string_member() {
accepts(ONE_OF, r#"{"input":{"a":"abc"}}"#);
}
#[test]
fn accepts_oneof_with_single_int_member() {
accepts(ONE_OF, r#"{"input":{"b":123}}"#);
}
#[test]
fn accepts_oneof_with_falsy_non_null_member() {
accepts(ONE_OF, r#"{"input":{"b":0}}"#);
accepts(ONE_OF, r#"{"input":{"a":""}}"#);
}
#[test]
fn accepts_explicit_null_for_nullable_oneof_variable_with_default() {
accepts(
r#"query ($input: OneOfTestInput = {a: "abc"}) { test(input: $input) { a b } }"#,
r#"{"input":null}"#,
);
}
#[test]
fn does_not_apply_oneof_rule_to_regular_input_object() {
accepts(
"query ($input: ExampleInputObject) { exampleInput(input: $input) }",
r#"{"input":{"a":"abc","b":1}}"#,
);
}
}
mod count_and_null {
use super::*;
#[test]
fn rejects_oneof_with_no_fields() {
rejects(
ONE_OF,
r#"{"input":{}}"#,
"",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn rejects_oneof_with_two_non_null_fields() {
rejects(
ONE_OF,
r#"{"input":{"a":"abc","b":123}}"#,
"",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn rejects_oneof_with_additional_null_field() {
rejects(
ONE_OF,
r#"{"input":{"a":"abc","b":null}}"#,
"",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn rejects_oneof_with_null_field_listed_first() {
rejects(
ONE_OF,
r#"{"input":{"b":null,"a":"abc"}}"#,
"",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn rejects_oneof_with_all_fields_null() {
rejects(
ONE_OF,
r#"{"input":{"a":null,"b":null}}"#,
"",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn rejects_oneof_with_two_invalid_members() {
rejects(
ONE_OF,
r#"{"input":{"a":456,"b":"xyz"}}"#,
"",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn rejects_oneof_with_single_null_member() {
rejects(
ONE_OF,
r#"{"input":{"a":null}}"#,
" at .a",
&one_of_error("OneOfTestInput"),
);
}
}
mod member_values_and_unknown_fields {
use super::*;
#[test]
fn rejects_oneof_member_of_wrong_type() {
rejects(
INT_ONE_OF,
r#"{"input":{"foo":1.5}}"#,
" at .foo",
"Int cannot represent non-integer value: 1.5",
);
}
#[test]
fn rejects_non_object_for_oneof() {
rejects(
INT_ONE_OF,
r#"{"input":123}"#,
"",
r#"Expected value of type "IntOneOf" to be an object, found: 123."#,
);
}
#[test]
fn rejects_array_for_non_list_oneof() {
rejects(
ONE_OF,
r#"{"input":[{"a":"abc"}]}"#,
"",
r#"Expected value of type "OneOfTestInput" to be an object, found: [{ a: "abc" }]."#,
);
}
#[test]
fn rejects_unknown_field_next_to_valid_member() {
rejects(
INT_ONE_OF,
r#"{"input":{"foo":123,"unknownField":123}}"#,
"",
&one_of_error("IntOneOf"),
);
}
#[test]
fn rejects_oneof_with_only_unknown_field() {
rejects(
INT_ONE_OF,
r#"{"input":{"bart":123}}"#,
"",
r#"Expected value of type "IntOneOf" not to include unknown field "bart", found: { bart: 123 }."#,
);
}
}
mod nested {
use super::*;
#[test]
fn accepts_list_of_valid_oneofs() {
accepts(ONE_OF_LIST, r#"{"input":[{"a":"abc"},{"b":1}]}"#);
}
#[test]
fn rejects_list_item_with_two_oneof_fields() {
rejects(
ONE_OF_LIST,
r#"{"input":[{"a":"x"},{"a":"x","b":1}]}"#,
" at [1]",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn rejects_list_item_with_null_oneof_member() {
rejects(
ONE_OF_LIST,
r#"{"input":[{"a":null}]}"#,
" at [0].a",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn accepts_single_oneof_for_list_type_unwrapped() {
accepts(ONE_OF_LIST, r#"{"input":{"a":"abc"}}"#);
}
#[test]
fn rejects_single_empty_oneof_for_list_type() {
rejects(
ONE_OF_LIST,
r#"{"input":{}}"#,
"",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn accepts_oneof_nested_in_input_object() {
accepts(
ONE_OF_HOLDER,
r#"{"input":{"one":{"a":"abc"},"many":[{"b":1}]}}"#,
);
}
#[test]
fn accepts_null_for_nullable_nested_oneof_field() {
accepts(ONE_OF_HOLDER, r#"{"input":{"one":null,"name":"x"}}"#);
}
#[test]
fn rejects_nested_oneof_with_two_fields() {
rejects(
ONE_OF_HOLDER,
r#"{"input":{"one":{"a":"abc","b":1}}}"#,
" at .one",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn rejects_nested_oneof_with_null_member() {
rejects(
ONE_OF_HOLDER,
r#"{"input":{"one":{"a":null}}}"#,
" at .one.a",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn rejects_empty_oneof_in_nested_list() {
rejects(
ONE_OF_HOLDER,
r#"{"input":{"many":[{"a":"x"},{}]}}"#,
" at .many[1]",
&one_of_error("OneOfTestInput"),
);
}
#[test]
fn accepts_recursive_oneof_with_empty_list_member() {
accepts(RECURSIVE_ONE_OF, r#"{"input":{"not":{"and":[]}}}"#);
}
#[test]
fn rejects_invalid_oneof_deep_in_recursive_oneof() {
rejects(
RECURSIVE_ONE_OF,
r#"{"input":{"and":[{"leaf":1},{"not":{}}]}}"#,
" at .and[1].not",
&one_of_error("RecursiveOneOf"),
);
}
#[test]
fn many_invalid_oneof_items_yield_one_request_error() {
let items = vec!["{}"; 1000].join(",");
rejects(
ONE_OF_LIST,
&format!(r#"{{"input":[{items}]}}"#),
" at [0]",
&one_of_error("OneOfTestInput"),
);
}
}
mod defaults {
use super::*;
fn one_of_with_default(default: &str) -> String {
format!("query ($input: OneOfTestInput! = {default}) {{ test(input: $input) {{ a b }} }}")
}
fn test_one_of_with_default(default: &str) -> String {
format!(
"query ($input: TestOneOfInputObject = {default}) {{ fieldWithOneOfObjectInput(input: $input) }}"
)
}
#[test]
fn accepts_good_oneof_default() {
assert_coerced(
&one_of_with_default(r#"{a: "abc"}"#),
"{}",
r#"{"input":{"a":"abc"}}"#,
);
}
#[test]
fn accepts_good_oneof_default_for_second_member() {
assert_coerced(
&test_one_of_with_default(r#"{b: "def"}"#),
"{}",
r#"{"input":{"b":"def"}}"#,
);
}
#[test]
fn oneof_default_with_two_fields_is_rejected_by_validation() {
assert_rejected_by_validation(
&one_of_with_default(r#"{a: "abc", b: 123}"#),
"ValuesOfCorrectType",
);
}
#[test]
fn oneof_default_with_additional_null_field_is_rejected_by_validation() {
assert_rejected_by_validation(
&test_one_of_with_default(r#"{a: "abc", b: null}"#),
"ValuesOfCorrectType",
);
}
#[test]
fn oneof_default_with_single_null_member_is_rejected_by_validation() {
assert_rejected_by_validation(
&test_one_of_with_default("{a: null}"),
"ValuesOfCorrectType",
);
}
#[test]
fn empty_oneof_default_is_rejected_by_validation() {
assert_rejected_by_validation(&test_one_of_with_default("{}"), "ValuesOfCorrectType");
}
#[test]
fn oneof_default_with_unknown_field_is_rejected_by_validation() {
assert_rejected_by_validation(
&test_one_of_with_default(r#"{a: "abc", c: "def"}"#),
"ValuesOfCorrectType",
);
}
}
mod literals {
use super::*;
#[test]
fn oneof_literal_with_two_fields_is_rejected_by_validation() {
assert_rejected_by_validation(
r#"{ test(input: {a: "abc", b: 123}) { a b } }"#,
"ValuesOfCorrectType",
);
}
#[test]
fn oneof_literal_with_null_field_is_rejected_by_validation() {
assert_rejected_by_validation("{ test(input: {a: null}) { a b } }", "ValuesOfCorrectType");
}
#[test]
fn nullable_variable_in_oneof_literal_is_rejected_by_validation() {
assert_rejected_by_validation(
"query ($a: String) { test(input: {a: $a}) { a b } }",
"VariablesInAllowedPosition",
);
}
#[test]
fn defaulted_nullable_variable_in_oneof_literal_is_rejected_by_validation() {
assert_rejected_by_validation(
r#"query ($a: String = "abc") { test(input: {a: $a}) { a b } }"#,
"VariablesInAllowedPosition",
);
}
}
mod variables_in_literals {
use super::*;
const NON_NULL: &str = "query ($a: String!) { test(input: {a: $a}) { a b } }";
#[test]
fn forwards_non_null_variable_in_oneof_literal() {
assert_coerced(NON_NULL, r#"{"a":"abc"}"#, r#"{"a":"abc"}"#);
}
#[test]
fn rejects_null_for_non_null_variable_in_oneof_literal() {
assert_rejected(
NON_NULL,
r#"{"a":null}"#,
r#"Variable "$a" has invalid value: Expected value of non-null type "String!" not to be null."#,
);
}
#[test]
fn rejects_missing_non_null_variable_in_oneof_literal() {
assert_rejected(
NON_NULL,
"{}",
r#"Variable "$a" has invalid value: Expected a value of non-null type "String!" to be provided."#,
);
}
}