Skip to content

Commit 7363dc5

Browse files
committed
more tests
1 parent 0141ee6 commit 7363dc5

File tree

6 files changed

+1850
-0
lines changed

6 files changed

+1850
-0
lines changed

json-java21-schema/json-schema-core-202012.txt

Lines changed: 1473 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.github.simbo1905.json.schema;
2+
3+
import jdk.sandbox.java.util.json.Json;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.*;
7+
8+
/**
9+
* Covers annotation-only keywords from JSON Schema such as
10+
* title, description, $comment, and examples. These MUST NOT
11+
* affect validation (they are informational).
12+
*/
13+
class JsonSchemaAnnotationsTest extends JsonSchemaLoggingConfig {
14+
15+
@Test
16+
void examplesDoNotAffectValidation() {
17+
String schemaJson = """
18+
{
19+
"type": "object",
20+
"title": "User",
21+
"description": "A simple user object",
22+
"$comment": "Examples are informational only",
23+
"examples": [
24+
{"id": 1, "name": "Alice"},
25+
{"id": 2, "name": "Bob"}
26+
],
27+
"properties": {
28+
"id": {"type": "integer", "minimum": 0},
29+
"name": {"type": "string", "minLength": 1}
30+
},
31+
"required": ["id", "name"]
32+
}
33+
""";
34+
35+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
36+
37+
// Valid instance should pass regardless of examples
38+
var ok = schema.validate(Json.parse("""
39+
{"id": 10, "name": "Jane"}
40+
"""));
41+
assertThat(ok.valid()).isTrue();
42+
43+
// Invalid instance should still fail regardless of examples
44+
var bad = schema.validate(Json.parse("""
45+
{"id": -1}
46+
"""));
47+
assertThat(bad.valid()).isFalse();
48+
assertThat(bad.errors()).isNotEmpty();
49+
assertThat(bad.errors().get(0).message())
50+
.satisfiesAnyOf(
51+
m -> assertThat(m).contains("Missing required property: name"),
52+
m -> assertThat(m).contains("Below minimum")
53+
);
54+
}
55+
56+
@Test
57+
void unknownAnnotationKeywordsAreIgnored() {
58+
String schemaJson = """
59+
{
60+
"type": "string",
61+
"description": "A labeled string",
62+
"title": "Label",
63+
"$comment": "Arbitrary annotations should be ignored by validation",
64+
"x-internal": true
65+
}
66+
""";
67+
68+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
69+
assertThat(schema.validate(Json.parse("\"hello\""))).isNotNull();
70+
assertThat(schema.validate(Json.parse("\"hello\""))).extracting("valid").isEqualTo(true);
71+
assertThat(schema.validate(Json.parse("123"))).extracting("valid").isEqualTo(false);
72+
}
73+
}
74+
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package io.github.simbo1905.json.schema;
2+
3+
import jdk.sandbox.java.util.json.Json;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.*;
7+
8+
class JsonSchemaCombinatorsTest extends JsonSchemaLoggingConfig {
9+
10+
@Test
11+
void anyOfRequiresOneBranchValid() {
12+
String schemaJson = """
13+
{
14+
"anyOf": [
15+
{"type": "string", "minLength": 3},
16+
{"type": "number", "minimum": 10}
17+
]
18+
}
19+
""";
20+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
21+
22+
assertThat(schema.validate(Json.parse("\"abc\""))).extracting("valid").isEqualTo(true);
23+
assertThat(schema.validate(Json.parse("12"))).extracting("valid").isEqualTo(true);
24+
25+
var bad = schema.validate(Json.parse("\"x\""));
26+
assertThat(bad.valid()).isFalse();
27+
assertThat(bad.errors()).isNotEmpty();
28+
}
29+
30+
@Test
31+
void notInvertsValidation() {
32+
String schemaJson = """
33+
{ "not": {"type": "integer"} }
34+
""";
35+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
36+
37+
assertThat(schema.validate(Json.parse("\"ok\""))).extracting("valid").isEqualTo(true);
38+
assertThat(schema.validate(Json.parse("1"))).extracting("valid").isEqualTo(false);
39+
}
40+
41+
@Test
42+
void unresolvedRefFailsCompilation() {
43+
String schemaJson = """
44+
{"$ref": "#/$defs/missing"}
45+
""";
46+
assertThatThrownBy(() -> JsonSchema.compile(Json.parse(schemaJson)))
47+
.isInstanceOf(IllegalArgumentException.class)
48+
.hasMessageContaining("Unresolved $ref");
49+
}
50+
51+
@Test
52+
void nestedErrorPathsAreClear() {
53+
String schemaJson = """
54+
{
55+
"type": "object",
56+
"properties": {
57+
"user": {
58+
"type": "object",
59+
"properties": {"name": {"type": "string"}},
60+
"required": ["name"]
61+
}
62+
}
63+
}
64+
""";
65+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
66+
67+
var bad = schema.validate(Json.parse("""
68+
{"user": {}}
69+
"""));
70+
assertThat(bad.valid()).isFalse();
71+
assertThat(bad.errors().get(0).path()).isEqualTo("user");
72+
assertThat(bad.errors().get(0).message()).contains("Missing required property: name");
73+
}
74+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package io.github.simbo1905.json.schema;
2+
3+
import jdk.sandbox.java.util.json.Json;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.*;
7+
8+
class JsonSchemaErrorMessagesTest extends JsonSchemaLoggingConfig {
9+
10+
@Test
11+
void typeMismatchMessages() {
12+
JsonSchema sString = JsonSchema.compile(Json.parse("""
13+
{"type":"string"}
14+
"""));
15+
var r1 = sString.validate(Json.parse("123"));
16+
assertThat(r1.valid()).isFalse();
17+
assertThat(r1.errors().get(0).message()).contains("Expected string");
18+
19+
JsonSchema sArray = JsonSchema.compile(Json.parse("""
20+
{"type":"array"}
21+
"""));
22+
var r2 = sArray.validate(Json.parse("{}"));
23+
assertThat(r2.valid()).isFalse();
24+
assertThat(r2.errors().get(0).message()).contains("Expected array");
25+
26+
JsonSchema sObject = JsonSchema.compile(Json.parse("""
27+
{"type":"object"}
28+
"""));
29+
var r3 = sObject.validate(Json.parse("[]"));
30+
assertThat(r3.valid()).isFalse();
31+
assertThat(r3.errors().get(0).message()).contains("Expected object");
32+
}
33+
34+
@Test
35+
void numericConstraintMessages() {
36+
String schemaJson = """
37+
{"type":"number","minimum":1,"maximum":2,"multipleOf": 2}
38+
""";
39+
JsonSchema s = JsonSchema.compile(Json.parse(schemaJson));
40+
41+
var below = s.validate(Json.parse("0"));
42+
assertThat(below.valid()).isFalse();
43+
assertThat(below.errors().get(0).message()).contains("Below minimum");
44+
45+
var above = s.validate(Json.parse("3"));
46+
assertThat(above.valid()).isFalse();
47+
assertThat(above.errors().get(0).message()).contains("Above maximum");
48+
49+
var notMultiple = s.validate(Json.parse("1"));
50+
assertThat(notMultiple.valid()).isFalse();
51+
assertThat(notMultiple.errors().get(0).message()).contains("Not multiple of");
52+
}
53+
54+
@Test
55+
void arrayIndexAppearsInPath() {
56+
String schemaJson = """
57+
{"type":"array","items":{"type":"integer"}}
58+
""";
59+
JsonSchema s = JsonSchema.compile(Json.parse(schemaJson));
60+
61+
var bad = s.validate(Json.parse("""
62+
[1, "two", 3]
63+
"""));
64+
assertThat(bad.valid()).isFalse();
65+
// Expect failing path to point to the non-integer element
66+
assertThat(bad.errors().get(0).path()).isEqualTo("[1]");
67+
assertThat(bad.errors().get(0).message()).contains("Expected number");
68+
}
69+
70+
@Test
71+
void patternAndEnumMessages() {
72+
String schemaJson = """
73+
{"type":"string","pattern":"^x+$","enum":["x","xx","xxx"]}
74+
""";
75+
JsonSchema s = JsonSchema.compile(Json.parse(schemaJson));
76+
77+
var badEnum = s.validate(Json.parse("\"xxxx\""));
78+
assertThat(badEnum.valid()).isFalse();
79+
assertThat(badEnum.errors().get(0).message()).satisfiesAnyOf(
80+
m -> assertThat(m).contains("Not in enum"),
81+
m -> assertThat(m).contains("Pattern mismatch")
82+
);
83+
}
84+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.github.simbo1905.json.schema;
2+
3+
import jdk.sandbox.java.util.json.Json;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.*;
7+
8+
class JsonSchemaNumberKeywordsTest extends JsonSchemaLoggingConfig {
9+
10+
@Test
11+
void exclusiveMinimumAndMaximumAreHonored() {
12+
String schemaJson = """
13+
{
14+
"type": "number",
15+
"minimum": 0,
16+
"maximum": 10,
17+
"exclusiveMinimum": true,
18+
"exclusiveMaximum": true
19+
}
20+
""";
21+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
22+
23+
// Boundary values should fail when exclusive
24+
assertThat(schema.validate(Json.parse("0")).valid()).isFalse();
25+
assertThat(schema.validate(Json.parse("10")).valid()).isFalse();
26+
27+
// Inside range should pass
28+
assertThat(schema.validate(Json.parse("5")).valid()).isTrue();
29+
}
30+
31+
@Test
32+
void multipleOfForDecimals() {
33+
String schemaJson = """
34+
{"type":"number", "multipleOf": 0.1}
35+
""";
36+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
37+
38+
assertThat(schema.validate(Json.parse("0.3")).valid()).isTrue();
39+
assertThat(schema.validate(Json.parse("0.25")).valid()).isFalse();
40+
}
41+
}
42+
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package io.github.simbo1905.json.schema;
2+
3+
import jdk.sandbox.java.util.json.Json;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.*;
7+
8+
class JsonSchemaObjectKeywordsTest extends JsonSchemaLoggingConfig {
9+
10+
@Test
11+
void additionalPropertiesFalseDisallowsUnknown() {
12+
String schemaJson = """
13+
{
14+
"type": "object",
15+
"properties": {"name": {"type": "string"}},
16+
"additionalProperties": false
17+
}
18+
""";
19+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
20+
21+
var result = schema.validate(Json.parse("""
22+
{"name":"Alice","extra": 123}
23+
"""));
24+
assertThat(result.valid()).isFalse();
25+
assertThat(result.errors()).isNotEmpty();
26+
assertThat(result.errors().get(0).path()).isEqualTo("extra");
27+
}
28+
29+
@Test
30+
void additionalPropertiesSchemaValidatesUnknown() {
31+
String schemaJson = """
32+
{
33+
"type": "object",
34+
"properties": {"id": {"type": "integer"}},
35+
"additionalProperties": {"type": "string"}
36+
}
37+
""";
38+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
39+
40+
// invalid because extra is not a string
41+
var bad = schema.validate(Json.parse("""
42+
{"id": 1, "extra": 999}
43+
"""));
44+
assertThat(bad.valid()).isFalse();
45+
assertThat(bad.errors().get(0).path()).isEqualTo("extra");
46+
assertThat(bad.errors().get(0).message()).contains("Expected string");
47+
48+
// valid because extra is a string
49+
var ok = schema.validate(Json.parse("""
50+
{"id": 1, "extra": "note"}
51+
"""));
52+
assertThat(ok.valid()).isTrue();
53+
}
54+
55+
@Test
56+
void minAndMaxPropertiesAreEnforced() {
57+
String schemaJson = """
58+
{
59+
"type": "object",
60+
"minProperties": 2,
61+
"maxProperties": 3
62+
}
63+
""";
64+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
65+
66+
var tooFew = schema.validate(Json.parse("""
67+
{"a": 1}
68+
"""));
69+
assertThat(tooFew.valid()).isFalse();
70+
assertThat(tooFew.errors().get(0).message()).contains("Too few properties");
71+
72+
var ok = schema.validate(Json.parse("""
73+
{"a": 1, "b": 2}
74+
"""));
75+
assertThat(ok.valid()).isTrue();
76+
77+
var tooMany = schema.validate(Json.parse("""
78+
{"a":1, "b":2, "c":3, "d":4}
79+
"""));
80+
assertThat(tooMany.valid()).isFalse();
81+
assertThat(tooMany.errors().get(0).message()).contains("Too many properties");
82+
}
83+
84+
@Test
85+
void objectKeywordsWithoutExplicitTypeAreTreatedAsObject() {
86+
String schemaJson = """
87+
{
88+
"properties": {"name": {"type": "string"}},
89+
"required": ["name"]
90+
}
91+
""";
92+
JsonSchema schema = JsonSchema.compile(Json.parse(schemaJson));
93+
94+
var bad = schema.validate(Json.parse("{}"));
95+
assertThat(bad.valid()).isFalse();
96+
assertThat(bad.errors().get(0).message()).contains("Missing required property: name");
97+
98+
var ok = schema.validate(Json.parse("""
99+
{"name":"x"}
100+
"""));
101+
assertThat(ok.valid()).isTrue();
102+
}
103+
}

0 commit comments

Comments
 (0)