浏览代码

Merge branch 'test-up'

Sam Jaffe 6 年之前
父节点
当前提交
075903988a
共有 24 个文件被更改,包括 989 次插入44 次删除
  1. 25 5
      src/main/lombok/org/leumasjaffe/json/schema/factory/SchemaV6Factory.java
  2. 5 0
      src/main/lombok/org/leumasjaffe/json/schema/tester/AllOfTester.java
  3. 6 1
      src/main/lombok/org/leumasjaffe/json/schema/tester/AnyOfTester.java
  4. 21 5
      src/main/lombok/org/leumasjaffe/json/schema/tester/FormatTester.java
  5. 6 1
      src/main/lombok/org/leumasjaffe/json/schema/tester/OneOfTester.java
  6. 13 5
      src/main/lombok/org/leumasjaffe/json/schema/tester/PropertyTester.java
  7. 5 1
      src/main/lombok/org/leumasjaffe/json/schema/tester/SizeTester.java
  8. 0 15
      src/test/java/org/leumasjaffe/json/JsonTesterSuite.java
  9. 13 0
      src/test/java/org/leumasjaffe/json/schema/JsonFactorySuite.java
  10. 13 0
      src/test/java/org/leumasjaffe/json/schema/JsonSchemaSuite.java
  11. 35 0
      src/test/java/org/leumasjaffe/json/schema/JsonTesterSuite.java
  12. 329 0
      src/test/java/org/leumasjaffe/json/schema/factory/SchemaV6FactoryTest.java
  13. 46 0
      src/test/java/org/leumasjaffe/json/schema/tester/AllOfTesterTest.java
  14. 46 0
      src/test/java/org/leumasjaffe/json/schema/tester/AnyOfTesterTest.java
  15. 41 0
      src/test/java/org/leumasjaffe/json/schema/tester/ContainsTesterTest.java
  16. 118 0
      src/test/java/org/leumasjaffe/json/schema/tester/FormatTesterTest.java
  17. 28 0
      src/test/java/org/leumasjaffe/json/schema/tester/NotTesterTest.java
  18. 29 0
      src/test/java/org/leumasjaffe/json/schema/tester/NumberTesterTest.java
  19. 46 0
      src/test/java/org/leumasjaffe/json/schema/tester/OneOfTesterTest.java
  20. 28 0
      src/test/java/org/leumasjaffe/json/schema/tester/PropertyNameTesterTest.java
  21. 78 0
      src/test/java/org/leumasjaffe/json/schema/tester/PropertyTesterTest.java
  22. 25 6
      src/test/java/org/leumasjaffe/json/tester/SizeTesterTest.java
  23. 1 5
      src/test/java/org/leumasjaffe/json/tester/TypeTesterTest.java
  24. 32 0
      src/test/java/org/leumasjaffe/json/schema/tester/UniqueItemTesterTest.java

+ 25 - 5
src/main/lombok/org/leumasjaffe/json/schema/factory/SchemaV6Factory.java

@@ -34,15 +34,35 @@ class SchemaV6Factory extends SchemaFactory {
 		case "$id": return Tester.ACCEPT;
 		case "$schema": return Tester.ACCEPT;
 		// case "$ref": ; // TODO Implement reference propagating
-		case "title": return JsonNode::isTextual;
-		case "description": return JsonNode::isTextual;
+		case "title": return Tester.ACCEPT;
+		case "description": return Tester.ACCEPT;
 		case "default": return Tester.ACCEPT;
-		case "examples": return JsonNode::isArray;
+		case "examples": return Tester.ACCEPT;
 		case "multipleOf": return new NumberTester(d -> d % value.asDouble() == 0);
 		case "maximum": return new NumberTester(d -> d <= value.asDouble());
 		case "exclusiveMaximum": return new NumberTester(d -> d < value.asDouble());
 		case "minimum": return new NumberTester(d -> d >= value.asDouble());
 		case "exclusiveMinimum": return new NumberTester(d -> d > value.asDouble());
+		case "maxLength": return new SizeTester(JsonNodeType.STRING, i -> i < value.asInt());
+		case "minLength": return new SizeTester(JsonNodeType.STRING, i -> i >= value.asInt(0));
+		case "pattern": return j -> j.isTextual() && j.asText().matches(value.asText());
+		case "additionalItems": return j -> JsonHelper.toArray(j).stream().allMatch(create(value)::accepts);
+		case "items": 
+			if (value.isArray()) {
+				return j -> {
+					List<Tester> tests = createArray(value);
+					List<JsonNode> data = JsonHelper.toArray(j);
+					for (int i = 0; i < Math.min(value.size(), data.size()); ++i) {
+						if (!tests.get(i).accepts(data.get(i))) {
+							return false;
+						}
+					}
+					return true;
+				};
+			}
+			else {
+				return j -> JsonHelper.toArray(j).stream().allMatch(create(value)::accepts);
+			}
 		case "maxItems": return new SizeTester(JsonNodeType.ARRAY, i -> i < value.asInt());
 		case "minItems": return new SizeTester(JsonNodeType.ARRAY, i -> i >= value.asInt(0));
 		case "uniqueItems": return value.asBoolean() ? UniqueItemTester.INSTANCE : Tester.ACCEPT;
@@ -50,7 +70,7 @@ class SchemaV6Factory extends SchemaFactory {
 		case "maxProperties": return new SizeTester(JsonNodeType.OBJECT, i -> i < value.asInt());
 		case "minProperties": return new SizeTester(JsonNodeType.OBJECT, i -> i >= value.asInt(0));
 		case "required": return json -> JsonHelper.toArray(value, JsonNode::asText).stream().allMatch(json::has);
-		case "additionalProperties": return create(value);
+		case "additionalProperties": return j -> JsonHelper.toArray(j).stream().allMatch(create(value)::accepts);
 		// case "definitions": ; // TODO Implement definitions creation
 		case "properties": {
 			final List<PropertyTester.Pair> list = new ArrayList<>();
@@ -71,7 +91,7 @@ class SchemaV6Factory extends SchemaFactory {
 		// case "dependencies": ; // TODO Implement array(required) and object(schema) versions
 		case "propertyNames": return new PropertyNameTester(create(value));
 		case "const": return value::equals;
-		case "enum": new AnyOfTester(JsonHelper.toArray(value, v -> (Tester) v::equals));
+		case "enum": return new AnyOfTester(JsonHelper.toArray(value, v -> (Tester) v::equals));
 		case "type": return TypeTester.fromType(value.asText());
 		case "format": return FormatTester.forCode(value.asText());
 		case "allOf": return new AllOfTester(createArray(value));

+ 5 - 0
src/main/lombok/org/leumasjaffe/json/schema/tester/AllOfTester.java

@@ -1,5 +1,6 @@
 package org.leumasjaffe.json.schema.tester;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.leumasjaffe.json.schema.Tester;
@@ -15,6 +16,10 @@ import lombok.experimental.FieldDefaults;
 public class AllOfTester implements Tester {
 	List<Tester> children;
 
+	public AllOfTester(Tester...testers) {
+		this(Arrays.asList(testers));
+	}
+
 	public boolean accepts(JsonNode node) {
 		return children.parallelStream().allMatch(t -> t.accepts(node));
 	}

+ 6 - 1
src/main/lombok/org/leumasjaffe/json/schema/tester/AnyOfTester.java

@@ -1,5 +1,6 @@
 package org.leumasjaffe.json.schema.tester;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.leumasjaffe.json.schema.Tester;
@@ -14,8 +15,12 @@ import lombok.experimental.FieldDefaults;
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class AnyOfTester implements Tester {
 	List<Tester> children;
+	
+	public AnyOfTester(Tester...testers) {
+		this(Arrays.asList(testers));
+	}
 
 	public boolean accepts(JsonNode node) {
-		return children.parallelStream().filter(t -> t.accepts(node)).count() == 1L;
+		return children.parallelStream().anyMatch(t -> t.accepts(node));
 	}
 }

+ 21 - 5
src/main/lombok/org/leumasjaffe/json/schema/tester/FormatTester.java

@@ -18,6 +18,7 @@ import lombok.experimental.FieldDefaults;
 @RequiredArgsConstructor
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public abstract class FormatTester implements Tester {
+	// TODO Make these into subclasses instead of this
 	static Tester UUID = new FormatTester("uuid") {
 		@Override
 		public boolean accepts(JsonNode node) {
@@ -68,26 +69,41 @@ public abstract class FormatTester implements Tester {
 	};
 	
 	static Tester URI = new FormatTester("uri") {
+		@Override
+		public boolean accepts(JsonNode node) {
+			if (!node.isTextual()) return false;
+			try {
+				// TODO: RFC 2396 -> RFC 3986
+				return new java.net.URI(node.asText()).isAbsolute();
+			} catch (URISyntaxException e) {
+				return false;
+			}
+		}
+	};
+	
+	static Tester URI_REFERNCE = new FormatTester("uri-reference") {
 		@Override
 		public boolean accepts(JsonNode node) {
 			if (!node.isTextual()) return false;
 			try {
 				// TODO: RFC 2396 -> RFC 3986
 				new java.net.URI(node.asText());
+				return true;
 			} catch (URISyntaxException e) {
 				return false;
 			}
-			return true;
 		}
 	};
 	
-	static Tester URI_REFERNCE = new FormatTester("uri") {
+	static Tester URI_TEMPLATE = new FormatTester("uri-template") {
 		@Override
 		public boolean accepts(JsonNode node) {
 			if (!node.isTextual()) return false;
 			try {
 				// TODO: RFC 2396 -> RFC 3986
-				return !new java.net.URI(node.asText()).isAbsolute();
+				// TODO: Validate inside brackets
+				new java.net.URI(node.asText().replaceAll("\\{.*?\\}", "_temp_"));
+				return true;
 			} catch (URISyntaxException e) {
 				return false;
 			}
@@ -97,7 +113,7 @@ public abstract class FormatTester implements Tester {
 	static Tester JSON_POINTER = new FormatTester("json-pointer") {
 		@Override
 		public boolean accepts(JsonNode node) {
-			final String pattern = "^(/([\\u00-\\u2E\\u30-\\u7D\\u7F-\\u10FFFF]|~0|~1)*)*$";
+			final String pattern = "^(/([\\u0000-\\u002E\\u0030-\\u007D\\u007F-\\u10FFFF]|~0|~1)*)*$";
 			return node.isTextual() && node.asText().matches(pattern);
 		}
 	};
@@ -113,7 +129,7 @@ public abstract class FormatTester implements Tester {
 		case "ipv6": return IPV6;
 		case "uri": return URI;
 		case "uri-reference": return URI_REFERNCE;
-//		case "uri-template":
+		case "uri-template": return URI_TEMPLATE;
 		case "json-pointer": return JSON_POINTER;	
 		case "uuid": return UUID;
 		default: throw new IllegalArgumentException("Unknown format code '" + asText + "'");

+ 6 - 1
src/main/lombok/org/leumasjaffe/json/schema/tester/OneOfTester.java

@@ -1,5 +1,6 @@
 package org.leumasjaffe.json.schema.tester;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.leumasjaffe.json.schema.Tester;
@@ -14,8 +15,12 @@ import lombok.experimental.FieldDefaults;
 @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class OneOfTester implements Tester {
 	List<Tester> children;
+	
+	public OneOfTester(Tester...testers) {
+		this(Arrays.asList(testers));
+	}
 
 	public boolean accepts(JsonNode node) {
-		return children.parallelStream().anyMatch(t -> t.accepts(node));
+		return children.parallelStream().filter(t -> t.accepts(node)).count() == 1;
 	}
 }

+ 13 - 5
src/main/lombok/org/leumasjaffe/json/schema/tester/PropertyTester.java

@@ -1,10 +1,11 @@
 package org.leumasjaffe.json.schema.tester;
 
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
-import java.util.stream.Stream;
+import java.util.stream.Collectors;
 
 import org.leumasjaffe.json.schema.Tester;
 
@@ -25,18 +26,25 @@ public class PropertyTester implements Tester {
 	}
 	
 	List<Pair> schema;
+	
+	public PropertyTester(Pair...pairs) {
+		this(Arrays.asList(pairs));
+	}
 
 	@Override
 	public boolean accepts(final JsonNode node) {
 		final Iterator<Map.Entry<String, JsonNode>> iter = node.fields();
 		while (iter.hasNext()) {
 			final Map.Entry<String, JsonNode> data = iter.next();
-			final Stream<Pair> stream = schema.stream()
-					.filter(p -> p.keyMatches.test(data.getKey()));
-			if (stream.count() > 0) {
+			final List<Pair> stream = schema.stream()
+					.filter(p -> p.keyMatches.test(data.getKey()))
+					.collect(Collectors.toList());
+			if (stream.size() == 0) {
+				continue;
+			} else if (stream.size() > 1) {
 				// TODO: Don't accept multiple
 				return false;
-			} else if (stream.findFirst().map(p -> valueMatches(data, p)).orElse(false)) {
+			} else if (!valueMatches(data, stream.get(0))) {
 				return false;
 			}
 		}

+ 5 - 1
src/main/lombok/org/leumasjaffe/json/schema/tester/SizeTester.java

@@ -19,7 +19,11 @@ public class SizeTester implements Tester {
 
 	@Override
 	public boolean accepts(JsonNode node) {
-		return node.getNodeType() == type && pred.test(node.size());
+		return node.getNodeType() == type && pred.test(getSize(node));
+	}
+
+	private int getSize(JsonNode node) {
+		return node.isTextual() ? node.asText().length() : node.size();
 	}
 
 }

+ 0 - 15
src/test/java/org/leumasjaffe/json/JsonTesterSuite.java

@@ -1,15 +0,0 @@
-package org.leumasjaffe.json;
-
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-import org.leumasjaffe.json.tester.SizeTesterTest;
-import org.leumasjaffe.json.tester.TypeTesterTest;
-
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
-	SizeTesterTest.class, 
-	TypeTesterTest.class
-})
-public class JsonTesterSuite {
-
-}

+ 13 - 0
src/test/java/org/leumasjaffe/json/schema/JsonFactorySuite.java

@@ -0,0 +1,13 @@
+package org.leumasjaffe.json.schema;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.leumasjaffe.json.schema.factory.SchemaV6FactoryTest;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+	SchemaV6FactoryTest.class
+})
+public class JsonFactorySuite {
+
+}

+ 13 - 0
src/test/java/org/leumasjaffe/json/schema/JsonSchemaSuite.java

@@ -0,0 +1,13 @@
+package org.leumasjaffe.json.schema;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+	JsonFactorySuite.class,
+	JsonTesterSuite.class
+})
+public class JsonSchemaSuite {
+
+}

+ 35 - 0
src/test/java/org/leumasjaffe/json/schema/JsonTesterSuite.java

@@ -0,0 +1,35 @@
+package org.leumasjaffe.json.schema;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.leumasjaffe.json.schema.tester.AllOfTesterTest;
+import org.leumasjaffe.json.schema.tester.AnyOfTesterTest;
+import org.leumasjaffe.json.schema.tester.ContainsTesterTest;
+import org.leumasjaffe.json.schema.tester.FormatTesterTest;
+import org.leumasjaffe.json.schema.tester.NotTesterTest;
+import org.leumasjaffe.json.schema.tester.NumberTesterTest;
+import org.leumasjaffe.json.schema.tester.OneOfTesterTest;
+import org.leumasjaffe.json.schema.tester.PropertyNameTesterTest;
+import org.leumasjaffe.json.schema.tester.PropertyTesterTest;
+import org.leumasjaffe.json.schema.tester.SizeTesterTest;
+import org.leumasjaffe.json.schema.tester.TypeTesterTest;
+import org.leumasjaffe.json.schema.tester.UniqueItemTesterTest;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+	AllOfTesterTest.class,
+	AnyOfTesterTest.class,
+	ContainsTesterTest.class,
+	FormatTesterTest.class,
+	NotTesterTest.class,
+	NumberTesterTest.class,
+	OneOfTesterTest.class,
+	PropertyNameTesterTest.class,
+	PropertyTesterTest.class,
+	SizeTesterTest.class, 
+	TypeTesterTest.class,
+	UniqueItemTesterTest.class
+})
+public class JsonTesterSuite {
+
+}

+ 329 - 0
src/test/java/org/leumasjaffe/json/schema/factory/SchemaV6FactoryTest.java

@@ -0,0 +1,329 @@
+package org.leumasjaffe.json.schema.factory;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.leumasjaffe.json.schema.Tester;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+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.IntNode;
+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;
+
+public class SchemaV6FactoryTest {
+	SchemaV6Factory factory;
+	ObjectMapper mapper;
+	
+	@Before
+	public void setUp() {
+		factory = new SchemaV6Factory();
+		mapper = new ObjectMapper();
+	}
+	
+	private JsonNode readTree(String data) {
+		try {
+			return mapper.readTree(data);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+	
+	private Tester fromSingleElement(String data) {
+		final JsonNode schema = readTree(data);
+		final Map.Entry<String, JsonNode> pair = schema.fields().next();
+		return factory.createMapping(pair.getKey(), pair.getValue());
+	}
+
+	@Test
+	public void testIDSchema() {
+		Tester test = fromSingleElement("{ \"$id\": \"test.json\" }");
+		assertTrue(test.accepts(NullNode.getInstance()));
+	}
+	
+	@Test
+	public void testSchemaSchema() {
+		Tester test = fromSingleElement("{ \"$schema\": \"http://json-schema.org/draft-06/schema#\" }");
+		assertTrue(test.accepts(NullNode.getInstance()));
+	}
+	
+	@Test
+	public void testTitleSchema() {
+		Tester test = fromSingleElement("{ \"title\": \"This is a schema\" }");
+		assertTrue(test.accepts(NullNode.getInstance()));
+	}
+	
+	@Test
+	public void testDescriptionSchema() {
+		Tester test = fromSingleElement("{ \"description\": \"This is a schema\" }");
+		assertTrue(test.accepts(NullNode.getInstance()));
+	}
+	
+	@Test
+	public void testDefaultSchema() {
+		Tester test = fromSingleElement("{ \"default\": {} }");
+		assertTrue(test.accepts(NullNode.getInstance()));
+	}
+
+	@Test
+	public void testExamplesSchema() {
+		Tester test = fromSingleElement("{ \"examples\": [] }");
+		assertTrue(test.accepts(NullNode.getInstance()));
+	}
+	
+	@Test
+	public void testMultipleOfSchema() {
+		Tester test = fromSingleElement("{ \"multipleOf\": 1.5 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new DoubleNode(1.5)));
+		assertTrue(test.accepts(new DoubleNode(3.0)));
+		assertTrue(test.accepts(new IntNode(3)));
+		assertFalse(test.accepts(new DoubleNode(2.5)));
+	}
+	
+	@Test
+	public void testMaximumSchema() {
+		Tester test = fromSingleElement("{ \"maximum\": 2.5 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new DoubleNode(1.5)));
+		assertFalse(test.accepts(new DoubleNode(3.0)));
+		assertTrue(test.accepts(new IntNode(2)));
+		assertFalse(test.accepts(new IntNode(3)));
+		assertTrue(test.accepts(new DoubleNode(2.5)));
+	}
+	
+	@Test
+	public void testExclusiveMaximumSchema() {
+		Tester test = fromSingleElement("{ \"exclusiveMaximum\": 2.5 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new DoubleNode(1.5)));
+		assertFalse(test.accepts(new DoubleNode(3.0)));
+		assertTrue(test.accepts(new IntNode(2)));
+		assertFalse(test.accepts(new IntNode(3)));
+		assertFalse(test.accepts(new DoubleNode(2.5)));
+	}
+	
+	@Test
+	public void testMinimumSchema() {
+		Tester test = fromSingleElement("{ \"minimum\": 2.5 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertFalse(test.accepts(new DoubleNode(1.5)));
+		assertTrue(test.accepts(new DoubleNode(3.0)));
+		assertFalse(test.accepts(new IntNode(2)));
+		assertTrue(test.accepts(new IntNode(3)));
+		assertTrue(test.accepts(new DoubleNode(2.5)));
+	}
+	
+	@Test
+	public void testExclusiveMinimumSchema() {
+		Tester test = fromSingleElement("{ \"exclusiveMinimum\": 2.5 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertFalse(test.accepts(new DoubleNode(1.5)));
+		assertTrue(test.accepts(new DoubleNode(3.0)));
+		assertFalse(test.accepts(new IntNode(2)));
+		assertTrue(test.accepts(new IntNode(3)));
+		assertFalse(test.accepts(new DoubleNode(2.5)));
+	}
+	
+	@Test
+	public void testMaxLengthSchema() {
+		Tester test = fromSingleElement("{ \"maxLength\": 2 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertFalse(test.accepts(new DoubleNode(1.5)));
+		assertTrue(test.accepts(new TextNode("")));
+		assertFalse(test.accepts(new TextNode("  ")));
+	}
+	
+	@Test
+	public void testMinLengthSchema() {
+		Tester test = fromSingleElement("{ \"minLength\": 2 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertFalse(test.accepts(new DoubleNode(1.5)));
+		assertFalse(test.accepts(new TextNode("")));
+		assertTrue(test.accepts(new TextNode("  ")));
+	}
+	
+	@Test
+	public void testPatternSchema() {
+		Tester test = fromSingleElement("{ \"pattern\": \"^\\\\s.*\" }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertFalse(test.accepts(new TextNode("S ")));
+		assertTrue(test.accepts(new TextNode(" S")));
+	}
+	
+	@Test
+	public void testAdditionalItemsSchema() {
+		Tester test = fromSingleElement("{ \"additionalItems\": false }");
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertTrue(test.accepts(node));
+		node.add(false);
+		assertFalse(test.accepts(node));
+		node.add(false);
+		assertFalse(test.accepts(node));
+	}
+	
+	@Test
+	public void testItemsSchema() {
+		Tester test = fromSingleElement("{ \"items\": [ true, false ] }");
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertTrue(test.accepts(node));
+		node.add(false);
+		assertTrue(test.accepts(node));
+		node.add(false);
+		assertFalse(test.accepts(node));
+	}
+
+	@Test
+	public void testItemsSchemaSingle() {
+		Tester test = fromSingleElement("{ \"items\": true }");
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertTrue(test.accepts(node));
+		node.add(false);
+		assertTrue(test.accepts(node));
+		node.add(false);
+		assertTrue(test.accepts(node));
+	}
+	
+	@Test
+	public void testMaxItemsSchema() {
+		Tester test = fromSingleElement("{ \"maxItems\": 2 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertFalse(test.accepts(new DoubleNode(1.5)));
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertTrue(test.accepts(node));
+		node.add(false);
+		node.add(false);
+		assertFalse(test.accepts(node));
+	}
+	
+	@Test
+	public void testMinItemsSchema() {
+		Tester test = fromSingleElement("{ \"minItems\": 2 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertFalse(test.accepts(new DoubleNode(1.5)));
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertFalse(test.accepts(node));
+		node.add(false);
+		node.add(false);
+		assertTrue(test.accepts(node));
+	}
+	
+	@Test
+	public void testUniqueItemsSchema() {
+		Tester uniq = fromSingleElement("{ \"uniqueItems\": true }");
+		Tester any = fromSingleElement("{ \"uniqueItems\": false }");
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(false);
+		assertTrue(uniq.accepts(node));
+		assertTrue(any.accepts(node));
+		node.add(false);
+		assertFalse(uniq.accepts(node));
+		assertTrue(any.accepts(node));
+	}
+	
+	@Test
+	public void testContainsSchema() {
+		Tester test = fromSingleElement("{ \"contains\": { } }");
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		
+		assertFalse(test.accepts(node));
+		
+		node.add(new ObjectNode(JsonNodeFactory.instance));
+		assertTrue(test.accepts(node));
+	}
+	
+	@Test
+	public void testMaxPropertiesSchema() {
+		Tester test = fromSingleElement("{ \"maxProperties\": 2 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertFalse(test.accepts(new DoubleNode(1.5)));
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		assertTrue(test.accepts(node));
+		node.set("A", BooleanNode.FALSE);
+		node.set("B", BooleanNode.FALSE);
+		assertFalse(test.accepts(node));
+	}
+	
+	@Test
+	public void testMinPropertiesSchema() {
+		Tester test = fromSingleElement("{ \"minProperties\": 2 }");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertFalse(test.accepts(new DoubleNode(1.5)));
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		assertFalse(test.accepts(node));
+		node.set("A", BooleanNode.FALSE);
+		node.set("B", BooleanNode.FALSE);
+		assertTrue(test.accepts(node));
+	}
+	
+	@Test
+	public void testRequiredSchema() {
+		Tester test = fromSingleElement("{ \"required\": [ \"A\", \"B\" ] }");
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		node.set("A", BooleanNode.FALSE);
+		node.set("C", BooleanNode.FALSE);
+		assertFalse(test.accepts(node));
+		node.set("B", BooleanNode.FALSE);
+		assertTrue(test.accepts(node));
+	}
+
+	@Test
+	public void testAdditionalPropertiesSchema() {
+		Tester test = fromSingleElement("{ \"additionalProperties\": false }");
+		assertTrue(test.accepts(readTree("{ }")));
+		assertFalse(test.accepts(readTree("{ \"A\": {} }")));
+		assertFalse(test.accepts(readTree("{ \"B\": {} }")));
+	}
+	
+	@Test
+	public void testPropertiesSchema() {
+		Tester test = fromSingleElement("{ \"properties\": { \"A\": true, \"B\": false } }");
+		assertTrue(test.accepts(readTree("{ }")));
+		assertTrue(test.accepts(readTree("{ \"A\": {} }")));
+		assertFalse(test.accepts(readTree("{ \"B\": {} }")));
+		assertFalse(test.accepts(readTree("{ \"A\": {}, \"B\": {} }")));
+	}
+	
+	@Test
+	public void testPatternPropertiesSchema() {
+		Tester test = fromSingleElement("{ \"patternProperties\": { \"A\": true, \"A.\": false } }");
+		assertTrue(test.accepts(readTree("{ }")));
+		assertTrue(test.accepts(readTree("{ \"A\": {} }")));
+		assertFalse(test.accepts(readTree("{ \"A.\": {} }")));
+		assertFalse(test.accepts(readTree("{ \"A_\": {} }")));
+	}
+	
+	@Test
+	public void testPropertyNamesSchema() {
+		Tester test = fromSingleElement("{ \"propertyNames\": false }");
+		assertTrue(test.accepts(readTree("{ }")));
+		assertFalse(test.accepts(readTree("{ \"A\": {} }")));
+	}
+	
+	@Test
+	public void testConstSchema() {
+		Tester test = fromSingleElement("{ \"const\": { \"nice\": \"meme\" } }");
+		JsonNode matching = readTree("{ \"nice\": \"meme\" }");
+		assertTrue(test.accepts(matching));
+	}
+	
+	@Test
+	public void testEnumSchema() {
+		Tester test = fromSingleElement("{ \"enum\": [ \"A\", \"B\", \"D\" ] }");
+		assertTrue(test.accepts(new TextNode("A")));
+		assertFalse(test.accepts(new TextNode("a")));
+		assertTrue(test.accepts(new TextNode("B")));
+		assertFalse(test.accepts(new TextNode("AB")));
+		assertFalse(test.accepts(new TextNode("C")));
+		assertTrue(test.accepts(new TextNode("D")));
+	}
+}

+ 46 - 0
src/test/java/org/leumasjaffe/json/schema/tester/AllOfTesterTest.java

@@ -0,0 +1,46 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.leumasjaffe.json.schema.Tester;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
+
+public class AllOfTesterTest {
+	Tester isArray = JsonNode::isArray;
+	Tester isObject = JsonNode::isObject;
+	Tester notEmpty = j -> j.size() != 0;
+
+	@Test
+	public void testFailsIfAllFail() {
+		Tester fails = new AllOfTester(isArray, isObject);
+		assertFalse(fails.accepts(NullNode.getInstance()));
+	}
+
+	@Test
+	public void testPassesIfAllPass() {
+		Tester fails = new AllOfTester(isArray, notEmpty);
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(true);
+		assertTrue(fails.accepts(node));
+	}
+	
+	@Test
+	public void testFailsIfMoreThanOnePass() {
+		Tester fails = new AllOfTester(isArray, isObject, notEmpty);
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(true);
+		assertFalse(fails.accepts(node));
+	}
+	
+	@Test
+	public void testFailsIfPreciselyOnePasses() {
+		Tester fails = new AllOfTester(isArray, notEmpty);
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertFalse(fails.accepts(node));
+	}
+}

+ 46 - 0
src/test/java/org/leumasjaffe/json/schema/tester/AnyOfTesterTest.java

@@ -0,0 +1,46 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.leumasjaffe.json.schema.Tester;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
+
+public class AnyOfTesterTest {
+	Tester isArray = JsonNode::isArray;
+	Tester isObject = JsonNode::isObject;
+	Tester notEmpty = j -> j.size() != 0;
+
+	@Test
+	public void testFailsIfAllFail() {
+		Tester fails = new AnyOfTester(isArray, isObject);
+		assertFalse(fails.accepts(NullNode.getInstance()));
+	}
+
+	@Test
+	public void testPassesIfAllPass() {
+		Tester fails = new AnyOfTester(isArray, notEmpty);
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(true);
+		assertTrue(fails.accepts(node));
+	}
+	
+	@Test
+	public void testPassesIfMoreThanOnePass() {
+		Tester fails = new AnyOfTester(isArray, isObject, notEmpty);
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(true);
+		assertTrue(fails.accepts(node));
+	}
+	
+	@Test
+	public void testPassesIfPreciselyOnePasses() {
+		Tester fails = new AnyOfTester(isArray, notEmpty);
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertTrue(fails.accepts(node));
+	}
+}

+ 41 - 0
src/test/java/org/leumasjaffe/json/schema/tester/ContainsTesterTest.java

@@ -0,0 +1,41 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.leumasjaffe.json.schema.Tester;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
+
+public class ContainsTesterTest {
+
+	@Test
+	public void testRejectsNonArray() {
+		assertFalse(new ContainsTester(Tester.ACCEPT).accepts(NullNode.getInstance()));
+	}
+
+	@Test
+	public void testRejectsEmptyArray() {
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertFalse(new ContainsTester(Tester.ACCEPT).accepts(node));
+	}
+	
+	@Test
+	public void testRejectsIfAllFail() {
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(false);
+		node.add(true);
+		assertFalse(new ContainsTester(Tester.REJECT).accepts(node));
+	}
+	
+	@Test
+	public void testAcceptsIfAnyPass() {
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(false);
+		node.add(true);
+		assertTrue(new ContainsTester(JsonNode::asBoolean).accepts(node));
+	}
+}

+ 118 - 0
src/test/java/org/leumasjaffe/json/schema/tester/FormatTesterTest.java

@@ -0,0 +1,118 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.leumasjaffe.json.schema.Tester;
+
+import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+
+public class FormatTesterTest {
+
+	@Test(expected=IllegalArgumentException.class)
+	public void testThrowsForUnknownFormat() {
+		FormatTester.forCode("unknown");
+	}
+
+	@Test
+	public void testUUIDMatcher() {
+		Tester test = FormatTester.forCode("uuid");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("00000000-0000-0000-0000-000000000000")));
+		assertFalse(test.accepts(new TextNode("0000000-0000-0000-0000-000000000000")));
+		assertFalse(test.accepts(new TextNode("X0000000-0000-0000-0000-000000000000")));
+	}
+	
+	@Test
+	public void testDateTimeMatcher() {
+		Tester test = FormatTester.forCode("date-time");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("2000-01-01T21:10:10Z")));
+		assertTrue(test.accepts(new TextNode("2000-01-01T21:10:10+01:00")));
+		assertTrue(test.accepts(new TextNode("2000-01-01T21:10:10-01:00")));
+		assertFalse(test.accepts(new TextNode("2000-01-01")));
+		assertFalse(test.accepts(new TextNode("21:10:10Z")));
+		assertFalse(test.accepts(new TextNode("21:10:10+01:00")));
+		assertFalse(test.accepts(new TextNode("21:10:10001:00")));
+	}
+	
+	@Test
+	public void testEmailMatcher() {
+		Tester test = FormatTester.forCode("email");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("user@mail.com")));
+		assertFalse(test.accepts(new TextNode("user@mail")));
+		assertFalse(test.accepts(new TextNode("@mail.com")));
+	}
+
+	@Test
+	public void testHostnameMatcher() {
+		Tester test = FormatTester.forCode("hostname");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("google.com")));
+		assertFalse(test.accepts(new TextNode("192.168.0.1")));
+		assertFalse(test.accepts(new TextNode("user@google.com")));
+		assertFalse(test.accepts(new TextNode("http://google.com")));
+	}
+
+	@Test
+	public void testipv4Matcher() {
+		Tester test = FormatTester.forCode("ipv4");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("192.168.0.1")));
+		assertFalse(test.accepts(new TextNode("192.168.0.1/24")));
+		assertFalse(test.accepts(new TextNode("192.168.0.1:80")));
+	}
+	
+	@Test
+	public void testipv6Matcher() {
+		Tester test = FormatTester.forCode("ipv6");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("::1")));
+		assertFalse(test.accepts(new TextNode("[::1]:80")));
+	}
+	
+
+	@Test
+	public void testURIMatcher() {
+		Tester test = FormatTester.forCode("uri");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("file:///var/log/syslog")));
+		assertFalse(test.accepts(new TextNode("#/definitions/schemaArray")));
+		assertTrue(test.accepts(new TextNode("http://google.com")));
+		assertFalse(test.accepts(new TextNode("100:file")));
+		assertFalse(test.accepts(new TextNode("http://google.com/{domain}")));
+	}
+	
+	@Test
+	public void testURIReferenceMatcher() {
+		Tester test = FormatTester.forCode("uri-reference");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("file:///var/log/syslog")));
+		assertTrue(test.accepts(new TextNode("#/definitions/schemaArray")));
+		assertTrue(test.accepts(new TextNode("http://google.com")));
+		assertFalse(test.accepts(new TextNode("100:file")));
+		assertFalse(test.accepts(new TextNode("http://google.com/{domain}")));
+	}
+
+	@Test
+	public void testURITemplateeMatcher() {
+		Tester test = FormatTester.forCode("uri-template");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("file:///var/log/syslog")));
+		assertTrue(test.accepts(new TextNode("#/definitions/schemaArray")));
+		assertTrue(test.accepts(new TextNode("http://google.com")));
+		assertFalse(test.accepts(new TextNode("100:file")));
+		assertTrue(test.accepts(new TextNode("http://google.com/{domain}")));
+	}
+	
+	@Test
+	public void testJsonPointerMatcher() {
+		Tester test = FormatTester.forCode("json-pointer");
+		assertFalse(test.accepts(NullNode.getInstance()));
+		assertTrue(test.accepts(new TextNode("/definitions/schemaArray")));
+		assertFalse(test.accepts(new TextNode("#/definitions/schemaArray")));
+		assertTrue(test.accepts(new TextNode("/definitions/schemas/5/")));
+	}
+}

+ 28 - 0
src/test/java/org/leumasjaffe/json/schema/tester/NotTesterTest.java

@@ -0,0 +1,28 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.leumasjaffe.json.schema.Tester;
+import org.leumasjaffe.json.schema.tester.NotTester;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+
+public class NotTesterTest {
+
+	@Test
+	public void testNotInvertsOutputTrue() {
+		Tester impl = JsonNode::isNull;
+		assertTrue(impl.accepts(NullNode.getInstance()));
+		assertFalse(new NotTester(impl).accepts(NullNode.getInstance()));
+	}
+
+	@Test
+	public void testNotInvertsOutputFalse() {
+		Tester impl = JsonNode::isNull;
+		assertFalse(impl.accepts(BooleanNode.TRUE));
+		assertTrue(new NotTester(impl).accepts(BooleanNode.TRUE));
+	}
+}

+ 29 - 0
src/test/java/org/leumasjaffe/json/schema/tester/NumberTesterTest.java

@@ -0,0 +1,29 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import java.util.function.DoublePredicate;
+
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.node.DoubleNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+
+public class NumberTesterTest {
+	DoublePredicate nonZero = d -> d != 0;
+	
+	@Test
+	public void testRejectsNonNumber() {
+		assertFalse(new NumberTester(nonZero).accepts(NullNode.getInstance()));
+	}
+
+	@Test
+	public void testRejectsFailingPred() {
+		assertFalse(new NumberTester(nonZero).accepts(new DoubleNode(0.0)));
+	}
+
+	@Test
+	public void testAcceptsPassingPred() {
+		assertTrue(new NumberTester(nonZero).accepts(new DoubleNode(1.0)));
+	}
+}

+ 46 - 0
src/test/java/org/leumasjaffe/json/schema/tester/OneOfTesterTest.java

@@ -0,0 +1,46 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.leumasjaffe.json.schema.Tester;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
+
+public class OneOfTesterTest {
+	Tester isArray = JsonNode::isArray;
+	Tester isObject = JsonNode::isObject;
+	Tester notEmpty = j -> j.size() != 0;
+
+	@Test
+	public void testFailsIfAllFail() {
+		Tester fails = new OneOfTester(isArray, isObject);
+		assertFalse(fails.accepts(NullNode.getInstance()));
+	}
+
+	@Test
+	public void testFailsIfAllPass() {
+		Tester fails = new OneOfTester(isArray, notEmpty);
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(true);
+		assertFalse(fails.accepts(node));
+	}
+	
+	@Test
+	public void testFailsIfMoreThanOnePass() {
+		Tester fails = new OneOfTester(isArray, isObject, notEmpty);
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(true);
+		assertFalse(fails.accepts(node));
+	}
+	
+	@Test
+	public void testPassesIfPreciselyOnePasses() {
+		Tester fails = new OneOfTester(isArray, notEmpty);
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertTrue(fails.accepts(node));
+	}
+}

+ 28 - 0
src/test/java/org/leumasjaffe/json/schema/tester/PropertyNameTesterTest.java

@@ -0,0 +1,28 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.leumasjaffe.json.schema.Tester;
+
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class PropertyNameTesterTest {
+
+	@Test
+	public void testPassesEmptyObject() {
+		PropertyNameTester test = new PropertyNameTester(Tester.REJECT);
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		assertTrue(test.accepts(node));
+	}
+	
+	@Test
+	public void testRejectsIfNameFails() {
+		PropertyNameTester test = new PropertyNameTester(Tester.REJECT);
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		node.set("A", NullNode.getInstance());
+		assertFalse(test.accepts(node));
+	}
+}

+ 78 - 0
src/test/java/org/leumasjaffe/json/schema/tester/PropertyTesterTest.java

@@ -0,0 +1,78 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.leumasjaffe.json.schema.Tester;
+import org.leumasjaffe.json.schema.tester.PropertyTester.Pair;
+
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class PropertyTesterTest {
+
+	@Test
+	public void testPassesUnmatchedProperty() {
+		PropertyTester test = new PropertyTester(new Pair[0]);
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		node.set("A", NullNode.getInstance());
+		assertTrue(test.accepts(node));
+	}
+
+	@Test
+	public void testFailsIfMatchFails() {
+		PropertyTester test = new PropertyTester(new Pair(s -> s.equals("A"), Tester.REJECT));
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		node.set("A", NullNode.getInstance());
+		assertFalse(test.accepts(node));
+	}
+	
+	@Test
+	public void testFailsIfAnyMatchFails() {
+		PropertyTester test = new PropertyTester(
+				new Pair(s -> s.equals("A"), Tester.ACCEPT),
+				new Pair(s -> s.equals("B"), Tester.REJECT)
+				);
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		node.set("A", NullNode.getInstance());
+		node.set("B", NullNode.getInstance());
+		assertFalse(test.accepts(node));
+	}
+
+	@Test
+	public void testFailsIfMoreThanOneMatcherForKey() {
+		PropertyTester test = new PropertyTester(
+				new Pair(s -> s.equals("A"), Tester.ACCEPT),
+				new Pair(s -> s.equals("A"), Tester.REJECT)
+				);
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		node.set("A", NullNode.getInstance());
+		node.set("B", NullNode.getInstance());
+		assertFalse(test.accepts(node));
+	}
+	
+	@Test
+	public void testPassesIfAllPass() {
+		PropertyTester test = new PropertyTester(
+				new Pair(s -> s.equals("A"), Tester.ACCEPT),
+				new Pair(s -> s.equals("B"), Tester.ACCEPT)
+				);
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		node.set("A", NullNode.getInstance());
+		node.set("B", NullNode.getInstance());
+		assertTrue(test.accepts(node));
+	}
+	
+	@Test
+	public void testPairMatcherCanOperateOnMultipleKeys() {
+		PropertyTester test = new PropertyTester(
+				new Pair(s -> s.length() == 1, Tester.ACCEPT),
+				new Pair(s -> s.equals("B"), Tester.ACCEPT)
+				);
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		node.set("A", NullNode.getInstance());
+		node.set("B", NullNode.getInstance());
+		assertFalse(test.accepts(node));
+	}
+}

+ 25 - 6
src/test/java/org/leumasjaffe/json/tester/SizeTesterTest.java

@@ -1,4 +1,4 @@
-package org.leumasjaffe.json.tester;
+package org.leumasjaffe.json.schema.tester;
 
 import static org.junit.Assert.*;
 
@@ -6,6 +6,7 @@ import java.util.function.IntPredicate;
 
 import static com.fasterxml.jackson.databind.node.JsonNodeType.ARRAY;
 import static com.fasterxml.jackson.databind.node.JsonNodeType.OBJECT;
+import static com.fasterxml.jackson.databind.node.JsonNodeType.STRING;
 
 import org.junit.Test;
 import org.leumasjaffe.json.schema.tester.SizeTester;
@@ -15,11 +16,8 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
 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;
 
-import lombok.AccessLevel;
-import lombok.experimental.FieldDefaults;
-
-@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class SizeTesterTest {
 	static IntPredicate NON_ZERO = i -> i > 0;
 
@@ -36,6 +34,13 @@ public class SizeTesterTest {
 		final JsonNode node = new ArrayNode(JsonNodeFactory.instance);
 		assertFalse(notEmptyArray.accepts(node));
 	}
+	
+	@Test
+	public void textMatcherRejectsArray() {
+		final SizeTester notEmptyArray = new SizeTester(STRING, NON_ZERO);
+		final JsonNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertFalse(notEmptyArray.accepts(node));
+	}
 
 	@Test
 	public void arrayMatcherRejectsTooSmall() {
@@ -51,6 +56,12 @@ public class SizeTesterTest {
 		assertFalse(notEmptyArray.accepts(node));
 	}
 
+	@Test
+	public void testMatcherRejectsTooSmall() {
+		final SizeTester notEmptyArray = new SizeTester(STRING, NON_ZERO);
+		assertFalse(notEmptyArray.accepts(new TextNode("")));
+	}
+
 	@Test
 	public void arrayMatcherAcceptsGoodSize() {
 		final SizeTester notEmptyArray = new SizeTester(ARRAY, NON_ZERO);
@@ -65,4 +76,12 @@ public class SizeTesterTest {
 		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
 		node.set("_", NullNode.getInstance());
 		assertTrue(notEmptyArray.accepts(node));
-	}}
+	}
+
+	@Test
+	public void testMatcherAcceptsGoodSize() {
+		final SizeTester notEmptyArray = new SizeTester(STRING, NON_ZERO);
+		assertTrue(notEmptyArray.accepts(new TextNode("_")));
+	}
+	
+}

+ 1 - 5
src/test/java/org/leumasjaffe/json/tester/TypeTesterTest.java

@@ -1,4 +1,4 @@
-package org.leumasjaffe.json.tester;
+package org.leumasjaffe.json.schema.tester;
 
 import static org.junit.Assert.*;
 
@@ -16,10 +16,6 @@ import com.fasterxml.jackson.databind.node.NullNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.databind.node.TextNode;
 
-import lombok.AccessLevel;
-import lombok.experimental.FieldDefaults;
-
-@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
 public class TypeTesterTest {
 	JsonNode jNull = NullNode.getInstance();
 	JsonNode bool = BooleanNode.TRUE;

+ 32 - 0
src/test/java/org/leumasjaffe/json/schema/tester/UniqueItemTesterTest.java

@@ -0,0 +1,32 @@
+package org.leumasjaffe.json.schema.tester;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class UniqueItemTesterTest {
+
+	@Test
+	public void testAcceptsEmptyArray() {
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		assertTrue(new UniqueItemTester().accepts(node));
+	}
+
+	@Test
+	public void testRejectsNonArray() {
+		final ObjectNode node = new ObjectNode(JsonNodeFactory.instance);
+		assertFalse(new UniqueItemTester().accepts(node));
+	}
+	
+	@Test
+	public void testRejectsArrayWithDuplicates() {
+		final ArrayNode node = new ArrayNode(JsonNodeFactory.instance);
+		node.add(1.5);
+		node.add(1.5);
+		assertFalse(new UniqueItemTester().accepts(node));
+	}
+}