package org.leumasjaffe.json.schema; import static com.fasterxml.jackson.databind.node.JsonNodeType.*; import static org.junit.Assert.assertThat; import static org.leumasjaffe.json.schema.matcher.Accepts.accepts; import static org.leumasjaffe.json.schema.matcher.Not.not; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.leumasjaffe.json.schema.tester.AllItemsTester; import org.leumasjaffe.json.schema.tester.ContainsTester; import org.leumasjaffe.json.schema.tester.EqualsTester; import org.leumasjaffe.json.schema.tester.FixedTester; import org.leumasjaffe.json.schema.tester.FormatTester; import org.leumasjaffe.json.schema.tester.ItemsTester; import org.leumasjaffe.json.schema.tester.NumberTester; import org.leumasjaffe.json.schema.tester.PropertyNameTester; import org.leumasjaffe.json.schema.tester.PropertyTester; import org.leumasjaffe.json.schema.tester.SizeTester; import org.leumasjaffe.json.schema.tester.TypeTester; import org.leumasjaffe.json.schema.tester.UniqueItemTester; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.BooleanNode; import com.fasterxml.jackson.databind.node.DoubleNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; // TODO: patternProperties // TODO: dependencies // TODO: $ref // TODO: additionalItems with mixed results public class SchemaTest { private Schema getConstSchema(JsonNode json) { Map tests = new HashMap<>(); tests.put("const", new EqualsTester(json)); return new Schema(tests); } private Schema getEnumSchema(JsonNode... jsons) { Map tests = new HashMap<>(); tests.put("enum", new EqualsTester(jsons)); return new Schema(tests); } private Schema getNumberSchema() { Map tests = new HashMap<>(); tests.put("type", TypeTester.fromType("number")); tests.put("minimum", new NumberTester(d -> d >= 0.0)); tests.put("multipleOf", new NumberTester(d -> Math.abs(d % 0.25) < 1E-7)); tests.put("exclusiveMaximum", new NumberTester(d -> d < 1.0)); return new Schema(tests); } private Schema getStringSchema() { Map tests = new HashMap<>(); tests.put("type", TypeTester.fromType("string")); tests.put("maxLength", new SizeTester(STRING, s -> s <= 30)); tests.put("minLength", new SizeTester(STRING, s -> s >= 10)); tests.put("pattern", j -> j.asText().matches("^https://.*")); tests.put("format", FormatTester.forCode("uri")); return new Schema(tests); } private Schema getArraySchema() { Map tests = new HashMap<>(); tests.put("type", TypeTester.fromType("array")); tests.put("maxItems", new SizeTester(ARRAY, s -> s <= 3)); tests.put("minItems", new SizeTester(ARRAY, s -> s >= 1)); tests.put("uniqueItems", new UniqueItemTester()); tests.put("contains", new ContainsTester(getNumberSchema())); return new Schema(tests); } private Schema getObjectSchema() { Map tests = new HashMap<>(); tests.put("type", TypeTester.fromType("object")); tests.put("maxProperties", new SizeTester(OBJECT, s -> s <= 3)); tests.put("minProperties", new SizeTester(OBJECT, s -> s >= 2)); tests.put("required", json -> json.has("string")); tests.put("propertyNames", new PropertyNameTester(j -> j.asText().matches("^[a-z]*$"))); // TODO Dependencies return new Schema(tests); } @Test public void testRejectsWrongTypeOnlyIfHasTypeArg() { Map tests = new HashMap<>(); tests.put("minProperties", FixedTester.REJECT); assertThat(new Schema(tests), accepts(NullNode.getInstance())); tests.put("type", TypeTester.fromType("string")); assertThat(new Schema(tests), not(accepts(NullNode.getInstance()))); } @Test public void testHandlesUniversalChecksForBoolean() { assertThat(getConstSchema(BooleanNode.TRUE), accepts(BooleanNode.TRUE)); assertThat(getConstSchema(BooleanNode.TRUE), not(accepts(NullNode.getInstance()))); assertThat(getEnumSchema(BooleanNode.TRUE, BooleanNode.FALSE), accepts(BooleanNode.TRUE)); assertThat(getEnumSchema(BooleanNode.TRUE, BooleanNode.FALSE), accepts(BooleanNode.FALSE)); assertThat(getEnumSchema(BooleanNode.TRUE, BooleanNode.FALSE), not(accepts(NullNode.getInstance()))); } @Test public void testHandlesMultipleTestsForNumber() { assertThat(getNumberSchema(), not(accepts(new DoubleNode(-0.25)))); assertThat(getNumberSchema(), accepts(new DoubleNode(0.0))); assertThat(getNumberSchema(), accepts(new DoubleNode(0.5))); assertThat(getNumberSchema(), not(accepts(new DoubleNode(0.6)))); assertThat(getNumberSchema(), not(accepts(new DoubleNode(1.0)))); } @Test public void testHandlesOtherNumberChecks() { Map tests = new HashMap<>(); tests.put("exclusiveMinimum", new NumberTester(d -> d > 0.0)); tests.put("maximum", new NumberTester(d -> d <= 1.0)); Schema schema = new Schema(tests); assertThat(schema, not(accepts(new DoubleNode(0.0)))); assertThat(schema, accepts(new DoubleNode(0.5))); assertThat(schema, accepts(new DoubleNode(0.6))); assertThat(schema, accepts(new DoubleNode(1.0))); assertThat(schema, not(accepts(new DoubleNode(1.1)))); } @Test public void testHandlesUniversalChecksForNumber() { assertThat(getConstSchema(new DoubleNode(0.0)), accepts(new DoubleNode(0.0))); assertThat(getConstSchema(new DoubleNode(0.0)), not(accepts(new DoubleNode(0.5)))); assertThat(getEnumSchema(new DoubleNode(0.0), new DoubleNode(1.0)), accepts(new DoubleNode(0.0))); assertThat(getEnumSchema(new DoubleNode(0.0), new DoubleNode(1.0)), accepts(new DoubleNode(1.0))); assertThat(getEnumSchema(new DoubleNode(0.0), new DoubleNode(1.0)), not(accepts(new DoubleNode(0.5)))); } @Test public void testHandlesMultipleTestsForString() { assertThat(getStringSchema(), not(accepts(new TextNode("https://a")))); assertThat(getStringSchema(), accepts(new TextNode("https://google.com"))); assertThat(getStringSchema(), not(accepts(new TextNode("http://google.com")))); assertThat(getStringSchema(), not(accepts(new TextNode("https://google.com/query?q=Hippopotomonstrosesquippedaliophobia")))); } @Test public void testHandlesUniversalChecksForString() { assertThat(getConstSchema(new TextNode("A")), accepts(new TextNode("A"))); assertThat(getConstSchema(new TextNode("A")), not(accepts(new TextNode("C")))); assertThat(getEnumSchema(new TextNode("A"), new TextNode("B")), accepts(new TextNode("A"))); assertThat(getEnumSchema(new TextNode("A"), new TextNode("B")), accepts(new TextNode("B"))); assertThat(getEnumSchema(new TextNode("A"), new TextNode("B")), not(accepts(new TextNode("C")))); } @Test public void testHandlesMultipleTestsForArray() { final ArrayNode array = new ArrayNode(JsonNodeFactory.instance); assertThat(getArraySchema(), not(accepts(array))); array.add("Test"); assertThat(getArraySchema(), not(accepts(array))); array.add(0.5); assertThat(getArraySchema(), accepts(array)); array.add(0.5); assertThat(getArraySchema(), not(accepts(array))); array.remove(2); array.add(true); array.add(false); assertThat(getArraySchema(), not(accepts(array))); } @Test public void testHandlesAdditionalMatcherWhenArrayItems() { Map tests = new HashMap<>(); tests.put("items", new ItemsTester(getNumberSchema(), getStringSchema())); tests.put("additionalItems", FixedTester.REJECT); Schema schema = new Schema(tests); final ArrayNode node = new ArrayNode(JsonNodeFactory.instance); node.add(0.5); node.add("https://google.com"); assertThat(schema, accepts(node)); node.add(true); assertThat(schema, not(accepts(node))); tests.put("additionalItems", FixedTester.ACCEPT); assertThat(new Schema(tests), accepts(node)); tests.remove("additionalItems"); assertThat(new Schema(tests), accepts(node)); } @Test public void testHandlesAdditionalNotCalledIfFailsInItems() { Map tests = new HashMap<>(); tests.put("items", new ItemsTester(getNumberSchema(), getStringSchema())); tests.put("additionalItems", FixedTester.ACCEPT); Schema schema = new Schema(tests); final ArrayNode node = new ArrayNode(JsonNodeFactory.instance); node.add(0.5); node.add("https://a"); node.add(true); assertThat(schema, not(accepts(node))); } @Test public void testHandlesUniversalChecksForArray() { final ArrayNode expected = new ArrayNode(JsonNodeFactory.instance); expected.add(true); Map tests = new HashMap<>(); tests.put("const", new EqualsTester(expected)); Schema schema = new Schema(tests); final ArrayNode node = new ArrayNode(JsonNodeFactory.instance); assertThat(schema, not(accepts(node))); node.add(true); assertThat(schema, accepts(node)); } @Test public void testHandlesMultipleTestsForObject() { final ObjectNode object = new ObjectNode(JsonNodeFactory.instance); assertThat(getObjectSchema(), not(accepts(object))); object.set("string", new TextNode("https://google.com")); assertThat(getObjectSchema(), not(accepts(object))); object.set("float", new DoubleNode(0.5)); assertThat(getObjectSchema(), accepts(object)); object.set("Caps", BooleanNode.TRUE); assertThat(getObjectSchema(), not(accepts(object))); object.remove("Caps"); object.set("bool", BooleanNode.TRUE); assertThat(getObjectSchema(), accepts(object)); object.set("null", NullNode.getInstance()); assertThat(getObjectSchema(), not(accepts(object))); } @Test public void testHandlesAdditionalMatcherWhenObjectProps() { Map tests = new HashMap<>(); tests.put("properties", new PropertyTester(Arrays.asList( new PropertyTester.Pair("float"::equals, getNumberSchema()), new PropertyTester.Pair("string"::equals, getStringSchema())))); tests.put("additionalProperties", FixedTester.REJECT); Schema schema = new Schema(tests); final ObjectNode node = new ObjectNode(JsonNodeFactory.instance); node.set("float", new DoubleNode(0.5)); node.set("string", new TextNode("https://google.com")); assertThat(schema, accepts(node)); node.set("boolean", BooleanNode.TRUE); assertThat(schema, not(accepts(node))); tests.put("additionalProperties", FixedTester.ACCEPT); assertThat(new Schema(tests), accepts(node)); tests.remove("additionalProperties"); assertThat(new Schema(tests), accepts(node)); } @Test public void testAdditionalPropertiesRunsOnEachUnmatched() { Map tests = new HashMap<>(); tests.put("additionalProperties", new AllItemsTester(OBJECT, TypeTester.fromType("string"))); Schema schema = new Schema(tests); final ObjectNode node = new ObjectNode(JsonNodeFactory.instance); node.set("string", new TextNode("https://google.com")); assertThat(schema, accepts(node)); node.set("float", new DoubleNode(0.5)); assertThat(schema, not(accepts(node))); } @Test public void testHandlesAdditionalNotCalledIfFailsInProps() { Map tests = new HashMap<>(); tests.put("properties", new PropertyTester(Arrays.asList( new PropertyTester.Pair("float"::equals, getNumberSchema()), new PropertyTester.Pair("string"::equals, getStringSchema())))); tests.put("additionalProperties", FixedTester.ACCEPT); Schema schema = new Schema(tests); final ObjectNode node = new ObjectNode(JsonNodeFactory.instance); node.set("float", new DoubleNode(0.5)); node.set("string", new TextNode("https://a")); node.set("boolean", BooleanNode.TRUE); assertThat(schema, not(accepts(node))); } @Test public void testHandlesUniversalChecksForObject() { final ObjectNode expected = new ObjectNode(JsonNodeFactory.instance); expected.set("A", BooleanNode.TRUE); Map tests = new HashMap<>(); tests.put("const", new EqualsTester(expected)); Schema schema = new Schema(tests); final ObjectNode node = new ObjectNode(JsonNodeFactory.instance); assertThat(schema, not(accepts(node))); node.set("A", BooleanNode.TRUE); assertThat(schema, accepts(node)); }}