#include #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include "matchers.h" using enum jvalidate::schema::Version; using testing::Not; namespace { auto validate(Json::Value const & schema_doc, Json::Value const & instance_doc, jvalidate::schema::Version version = Draft2020_12) { jvalidate::Schema const schema(schema_doc, version); jvalidate::ValidationResult result; (void)jvalidate::Validator(schema).validate(instance_doc, &result); return result; } } TEST(AnnotationTest, AttachesFormattingAnnotation) { auto const schema = R"({ "format": "uri" })"_json; auto const instance = R"("http://json-schema.org")"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, AnnotationAt("format", "uri")); } TEST(AnnotationTest, AnnotatesAtRootSafely) { auto const schema = R"(false)"_json; auto const instance = R"(4)"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, ErrorAt("", "always false")); } TEST(AnnotationTest, AnnotatesErrors) { auto const schema = R"({ "minimum": 5 })"_json; auto const instance = R"(4)"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, ErrorAt("minimum", "4 < 5")); } TEST(AnnotationTest, AnnotatesDescription) { auto const schema = R"({ "description": "lorem ipsum", "minimum": 5 })"_json; auto const instance = R"(4)"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, AnnotationAt("description", "lorem ipsum")); } TEST(AnnotationTest, CanRecordAnnotationsAsList) { auto const schema = R"({ "items": false })"_json; auto const instance = R"([ 0, 1 ])"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, AnnotationAt("items", std::vector{"0", "1"})); } TEST(AnnotationTest, OnlyErrors) { auto const schema = R"({ "items": false })"_json; auto const instance = R"([ 0, 1 ])"_json; jvalidate::ValidationResult result = validate(schema, instance).only_errors(); EXPECT_THAT(result, Not(HasAnnotationAt(""_jptr, ""_jptr))); } TEST(AnnotationTest, DoesNotAnnotatesValid) { auto const schema = R"({ "minimum": 5 })"_json; auto const instance = R"(6)"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, Not(HasAnnotationsFor(""_jptr))); } TEST(AnnotationTest, NotSchemaFlipsAnnotationRule) { auto const schema = R"({ "not": { "minimum": 5 } })"_json; auto const instance = R"(6)"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, ErrorAt(""_jptr, "/not"_jptr, "minimum", "6 >= 5")); } TEST(AnnotationTest, NotSchemaPropogatesDeeply) { auto const schema = R"({ "not": { "properties": { "A": { "enum": [ 1, 3, 4 ] } } } })"_json; auto const instance = R"({"A": 3})"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, ErrorAt("/A"_jptr, "/not/properties/A"_jptr, "enum", "1")); } TEST(AnnotationTest, PathFollowsSchemaNotConstraintModel) { auto const schema = R"({ "$comment": "disallow is implemented in the form of NotConstraint[TypeConstraint]", "disallow": "string" })"_json; auto const instance = R"("hello")"_json; jvalidate::ValidationResult result = validate(schema, instance, Draft03); EXPECT_THAT(result, ErrorAt("disallow", "string is in types [string]")); } TEST(AnnotationTest, AttachesAlwaysFalseSensibly) { auto const schema = R"({ "properties": { "A": false } })"_json; auto const instance = R"({ "A": null })"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, ErrorAt("/A"_jptr, "/properties/A"_jptr, "", "always false")); } TEST(AnnotationTest, IfThenCanGiveABecauseReason) { auto const schema = R"({ "if": { "properties": { "A": { "const": true } }, "required": [ "A" ] }, "then": { "properties": { "B": { "multipleOf": 3 } } }, "else": { "properties": { "B": { "multipleOf": 5 } } } })"_json; auto const instance = R"({ "A": true, "B": 4 })"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, ErrorAt(""_jptr, "/if"_jptr, "required", "contains all required properties A")); EXPECT_THAT(result, ErrorAt("/A"_jptr, "/if/properties/A"_jptr, "const", "matches value")); EXPECT_THAT(result, ErrorAt("/B"_jptr, "/then/properties/B"_jptr, "multipleOf", "4 is not a multiple of 3")); } TEST(AnnotationTest, IfElseCanGiveABecauseReason) { auto const schema = R"({ "if": { "properties": { "A": { "const": true } }, "required": [ "A" ] }, "then": { "properties": { "B": { "multipleOf": 3 } } }, "else": { "properties": { "B": { "multipleOf": 5 } } } })"_json; auto const instance = R"({ "A": false, "B": 4 })"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, ErrorAt(""_jptr, "/if"_jptr, "required", "contains all required properties A")); EXPECT_THAT(result, ErrorAt("/A"_jptr, "/if/properties/A"_jptr, "const", "true was expected")); EXPECT_THAT(result, ErrorAt("/B"_jptr, "/else/properties/B"_jptr, "multipleOf", "4 is not a multiple of 5")); } TEST(AnnotationTest, OneOfGivesAllReasonsOnTooMany) { auto const schema = R"({ "oneOf": [ { "minimum": 5 }, { "multipleOf": 3 }, { "maximum": 2 } ] })"_json; auto const instance = R"(6)"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, ErrorAt(""_jptr, ""_jptr, "oneOf", "validates multiple subschemas [ 0, 1 ]")); EXPECT_THAT(result, ErrorAt(""_jptr, "/oneOf/0"_jptr, "minimum", "6 >= 5")); EXPECT_THAT(result, ErrorAt(""_jptr, "/oneOf/1"_jptr, "multipleOf", "6 is a multiple of 3")); EXPECT_THAT(result, ErrorAt(""_jptr, "/oneOf/2"_jptr, "maximum", "6 > 2")); } TEST(AnnotationTest, OneOfGivesAllReasonsOnNoMatches) { auto const schema = R"({ "oneOf": [ { "minimum": 5 }, { "multipleOf": 3 }, { "maximum": 2 } ] })"_json; auto const instance = R"(4)"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, ErrorAt(""_jptr, ""_jptr, "oneOf", "validates no subschemas")); EXPECT_THAT(result, ErrorAt(""_jptr, "/oneOf/0"_jptr, "minimum", "4 < 5")); EXPECT_THAT(result, ErrorAt(""_jptr, "/oneOf/1"_jptr, "multipleOf", "4 is not a multiple of 3")); EXPECT_THAT(result, ErrorAt(""_jptr, "/oneOf/2"_jptr, "maximum", "4 > 2")); } TEST(AnnotationTest, OneOfGivesAllReasonsOnMatch) { auto const schema = R"({ "oneOf": [ { "minimum": 5 }, { "multipleOf": 3 }, { "maximum": 2 } ] })"_json; auto const instance = R"(3)"_json; jvalidate::ValidationResult result = validate(schema, instance); EXPECT_THAT(result, Not(HasAnnotationAt(""_jptr, ""_jptr))); EXPECT_THAT(result, ErrorAt(""_jptr, "/oneOf/1"_jptr, "multipleOf", "3 is a multiple of 3")); } TEST(ValidationResultJsonTest, OutputsFormattedJSON) { auto const schema = R"({ "items": false })"_json; auto const instance = R"([ 0, 1 ])"_json; jvalidate::ValidationResult result = validate(schema, instance); std::stringstream ss; ss << result; EXPECT_THAT(ss.str(), R"({ "valid": false, "details": [ { "valid": false, "evaluationPath": "", "instanceLocation": "", "annotations": { "items": [ "0", "1" ] } }, { "valid": false, "evaluationPath": "/items", "instanceLocation": "/0", "errors": { "": "always false" } }, { "valid": false, "evaluationPath": "/items", "instanceLocation": "/1", "errors": { "": "always false" } } ] })"); } #if !defined(JVALIDATE_MONOTEST) int main(int argc, char ** argv) { testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); } #endif