diff --git a/README.md b/README.md
index 5c3a493..678d82a 100644
--- a/README.md
+++ b/README.md
@@ -90,11 +90,11 @@ This is a simplified backport with the following changes from the original:
These vulnerabilities exist in the upstream OpenJDK sandbox implementation and are reported here for transparency.
-## JSON Schema Validator (2020-12)
+## JSON Schema Validator
By including a basic schema validator that demonstrates how to build a realistic feature out of the core API. To demonstrate the power of the core API, it follows Data Oriented Programming principles: it parses JSON Schema into an immutable structure of records, then for validation it parses the JSON to the generic structure and uses the thread-safe parsed schema as the model to validate the JSON being checked.
-A simple JSON Schema (2020-12 subset) validator is included (module: json-java21-schema).
+A simple JSON Schema validator is included (module: json-java21-schema).
```java
var schema = io.github.simbo1905.json.schema.JsonSchema.compile(
@@ -328,12 +328,9 @@ This backport includes a compatibility report tool that tests against the [JSON
### Running the Compatibility Report
-First, build the project and download the test suite:
+The test data is bundled as ZIP files and extracted automatically at runtime:
```bash
-# Build project and download test suite
-mvn clean compile generate-test-resources -pl json-compatibility-suite
-
# Run human-readable report
mvn exec:java -pl json-compatibility-suite
diff --git a/json-compatibility-suite/pom.xml b/json-compatibility-suite/pom.xml
index 342f8e2..338522f 100644
--- a/json-compatibility-suite/pom.xml
+++ b/json-compatibility-suite/pom.xml
@@ -50,30 +50,30 @@
- com.googlecode.maven-download-plugin
- download-maven-plugin
-
-
- download-json-test-suite
- pre-integration-test
-
- wget
-
-
- https://github.com/nst/JSONTestSuite/archive/refs/heads/master.zip
- ${project.build.directory}/test-resources
- json-test-suite.zip
- true
-
-
-
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ src/test/resources/**/*.java
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/JSONTestSuite-20250921/**/*.java
+ **/parsers/**/*.java
+
+
org.codehaus.mojo
exec-maven-plugin
3.4.1
- jdk.sandbox.compatibility.JsonTestSuiteSummary
+ jdk.sandbox.compatibility.JsonCompatibilitySummary
false
diff --git a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonTestSuiteSummary.java b/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonCompatibilitySummary.java
similarity index 78%
rename from json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonTestSuiteSummary.java
rename to json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonCompatibilitySummary.java
index b53f295..3661454 100644
--- a/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonTestSuiteSummary.java
+++ b/json-compatibility-suite/src/main/java/jdk/sandbox/compatibility/JsonCompatibilitySummary.java
@@ -15,17 +15,42 @@
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
/// Generates a conformance summary report.
/// Run with: mvn exec:java -pl json-compatibility-suite
-public class JsonTestSuiteSummary {
+/// Test data location: see src/test/resources/json-test-suite-data.zip
+public class JsonCompatibilitySummary {
- private static final Logger LOGGER = Logger.getLogger(JsonTestSuiteSummary.class.getName());
- private static final Path TEST_DIR = Paths.get("json-compatibility-suite/target/test-resources/JSONTestSuite-master/test_parsing");
+ private static final Logger LOGGER = Logger.getLogger(JsonCompatibilitySummary.class.getName());
+ private static final Path ZIP_FILE = findZipFile();
+ private static final Path TARGET_TEST_DIR = Paths.get("target/test-data/json-test-suite/test_parsing");
+
+ private static Path findZipFile() {
+ // Try different possible locations for the ZIP file
+ Path[] candidates = {
+ Paths.get("src/test/resources/json-test-suite-data.zip"),
+ Paths.get("json-compatibility-suite/src/test/resources/json-test-suite-data.zip"),
+ Paths.get("../json-compatibility-suite/src/test/resources/json-test-suite-data.zip")
+ };
+
+ for (Path candidate : candidates) {
+ if (Files.exists(candidate)) {
+ return candidate;
+ }
+ }
+
+ // If none found, return the first candidate and let it fail with a clear message
+ return candidates[0];
+ }
public static void main(String[] args) throws Exception {
boolean jsonOutput = args.length > 0 && "--json".equals(args[0]);
- JsonTestSuiteSummary summary = new JsonTestSuiteSummary();
+ JsonCompatibilitySummary summary = new JsonCompatibilitySummary();
+ summary.extractTestData();
if (jsonOutput) {
summary.generateJsonReport();
} else {
@@ -33,6 +58,28 @@ public static void main(String[] args) throws Exception {
}
}
+ void extractTestData() throws IOException {
+ if (!Files.exists(ZIP_FILE)) {
+ throw new RuntimeException("Test data ZIP file not found: " + ZIP_FILE.toAbsolutePath());
+ }
+
+ // Create target directory
+ Files.createDirectories(TARGET_TEST_DIR.getParent());
+
+ // Extract ZIP file
+ try (ZipInputStream zis = new ZipInputStream(new FileInputStream(ZIP_FILE.toFile()))) {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ if (!entry.isDirectory() && entry.getName().startsWith("test_parsing/")) {
+ Path outputPath = TARGET_TEST_DIR.getParent().resolve(entry.getName());
+ Files.createDirectories(outputPath.getParent());
+ Files.copy(zis, outputPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+ }
+ zis.closeEntry();
+ }
+ }
+ }
+
void generateConformanceReport() throws Exception {
LOGGER.fine(() -> "Starting conformance report generation");
TestResults results = runTests();
@@ -84,9 +131,9 @@ void generateJsonReport() throws Exception {
}
private TestResults runTests() throws Exception {
- LOGGER.fine(() -> "Walking test files under: " + TEST_DIR.toAbsolutePath());
- if (!Files.exists(TEST_DIR)) {
- throw new RuntimeException("Test suite not downloaded. Run: ./mvnw clean compile generate-test-resources -pl json-compatibility-suite");
+ LOGGER.fine(() -> "Walking test files under: " + TARGET_TEST_DIR.toAbsolutePath());
+ if (!Files.exists(TARGET_TEST_DIR)) {
+ throw new RuntimeException("Test data not extracted. Run extractTestData() first.");
}
List shouldPassButFailed = new ArrayList<>();
@@ -98,7 +145,7 @@ private TestResults runTests() throws Exception {
int iAccept = 0, iReject = 0;
List files;
- try (var stream = Files.walk(TEST_DIR)) {
+ try (var stream = Files.walk(TARGET_TEST_DIR)) {
files = stream
.filter(p -> p.toString().endsWith(".json"))
.sorted()
diff --git a/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/DownloadVerificationTest.java b/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/DownloadVerificationTest.java
index 2a9a176..5edede1 100644
--- a/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/DownloadVerificationTest.java
+++ b/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/DownloadVerificationTest.java
@@ -9,15 +9,26 @@
public class DownloadVerificationTest {
@Test
void testSuiteDownloaded() {
- Path testDir = Paths.get("target/test-resources/JSONTestSuite-master/test_parsing");
- assertThat(testDir)
- .as("JSON Test Suite should be downloaded and extracted")
- .exists()
- .isDirectory();
-
- // Verify some test files exist
- assertThat(testDir.resolve("y_structure_whitespace_array.json"))
- .as("Should contain valid test files")
- .exists();
+ // The test data is now extracted from ZIP at runtime
+ // Create a summary instance and extract the data manually for testing
+ try {
+ JsonCompatibilitySummary summary = new JsonCompatibilitySummary();
+ summary.extractTestData();
+
+ // Verify the target directory exists after extraction
+ Path targetDir = Paths.get("target/test-data/json-test-suite/test_parsing");
+ assertThat(targetDir)
+ .as("JSON Test Suite should be extracted to target directory")
+ .exists()
+ .isDirectory();
+
+ // Verify some test files exist
+ assertThat(targetDir.resolve("y_valid_sample.json"))
+ .as("Should contain valid test files")
+ .exists();
+
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to extract JSON test suite data", e);
+ }
}
}
diff --git a/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/JsonTestSuiteTest.java b/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/JsonTestSuiteTest.java
deleted file mode 100644
index 88a04bc..0000000
--- a/json-compatibility-suite/src/test/java/jdk/sandbox/compatibility/JsonTestSuiteTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package jdk.sandbox.compatibility;
-
-import jdk.sandbox.java.util.json.Json;
-import jdk.sandbox.java.util.json.JsonParseException;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.DynamicTest;
-import org.junit.jupiter.api.TestFactory;
-
-import java.nio.charset.MalformedInputException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.logging.Logger;
-import java.util.stream.Stream;
-
-import static org.assertj.core.api.Assertions.*;
-
-/// Runs the JSON Test Suite against our implementation.
-/// Files are categorized:
-/// - y_*.json: Valid JSON that MUST parse successfully
-/// - n_*.json: Invalid JSON that MUST fail to parse
-/// - i_*.json: Implementation-defined (may accept or reject)
-public class JsonTestSuiteTest {
-
- private static final Logger LOGGER = Logger.getLogger(JsonTestSuiteTest.class.getName());
- private static final Path TEST_DIR = Paths.get("target/test-resources/JSONTestSuite-master/test_parsing");
-
- @TestFactory
- @Disabled("This is now a reporting tool, not a blocking test. Use JsonTestSuiteSummary instead.")
- Stream runJsonTestSuite() throws Exception {
- if (!Files.exists(TEST_DIR)) {
- System.err.println("Test suite not found. Run: mvn test-compile");
- return Stream.empty();
- }
-
- return Files.walk(TEST_DIR)
- .filter(p -> p.toString().endsWith(".json"))
- .sorted()
- .map(this::createTest);
- }
-
- private DynamicTest createTest(Path file) {
- String filename = file.getFileName().toString();
-
- return DynamicTest.dynamicTest(filename, () -> {
- String content = null;
- char[] charContent = null;
-
- try {
- content = Files.readString(file, StandardCharsets.UTF_8);
- charContent = content.toCharArray();
- } catch (MalformedInputException e) {
- LOGGER.warning("UTF-8 failed for " + filename + ", using robust encoding detection");
- try {
- byte[] rawBytes = Files.readAllBytes(file);
- charContent = RobustCharDecoder.decodeToChars(rawBytes, filename);
- } catch (Exception ex) {
- throw new RuntimeException("Failed to read test file " + filename + " - this is a fundamental I/O failure, not an encoding issue: " + ex.getMessage(), ex);
- }
- }
-
- if (filename.startsWith("y_")) {
- // Valid JSON - must parse successfully
- testValidJson(filename, content, charContent);
-
- } else if (filename.startsWith("n_")) {
- // Invalid JSON - must fail to parse
- testInvalidJson(filename, content, charContent);
-
- } else if (filename.startsWith("i_")) {
- // Implementation defined - just verify no crash
- testImplementationDefinedJson(filename, content, charContent);
- }
- });
- }
-
- private void testValidJson(String filename, String content, char[] charContent) {
- // Test String API if content is available
- if (content != null) {
- assertThatCode(() -> Json.parse(content))
- .as("File %s should parse successfully with String API", filename)
- .doesNotThrowAnyException();
- }
-
- // Test char[] API
- assertThatCode(() -> Json.parse(charContent))
- .as("File %s should parse successfully with char[] API", filename)
- .doesNotThrowAnyException();
- }
-
- private void testInvalidJson(String filename, String content, char[] charContent) {
- // Test String API if content is available
- if (content != null) {
- assertThatThrownBy(() -> Json.parse(content))
- .as("File %s should fail to parse with String API", filename)
- .satisfiesAnyOf(
- e -> assertThat(e).isInstanceOf(JsonParseException.class),
- e -> assertThat(e).isInstanceOf(StackOverflowError.class)
- .describedAs("StackOverflowError is acceptable for deeply nested structures like " + filename)
- );
- }
-
- // Test char[] API
- assertThatThrownBy(() -> Json.parse(charContent))
- .as("File %s should fail to parse with char[] API", filename)
- .satisfiesAnyOf(
- e -> assertThat(e).isInstanceOf(JsonParseException.class),
- e -> assertThat(e).isInstanceOf(StackOverflowError.class)
- .describedAs("StackOverflowError is acceptable for deeply nested structures like " + filename)
- );
- }
-
- private void testImplementationDefinedJson(String filename, String content, char[] charContent) {
- // Test String API if content is available
- if (content != null) {
- testImplementationDefinedSingle(filename + " (String API)", () -> Json.parse(content));
- }
-
- // Test char[] API
- testImplementationDefinedSingle(filename + " (char[] API)", () -> Json.parse(charContent));
- }
-
- private void testImplementationDefinedSingle(String description, Runnable parseAction) {
- try {
- parseAction.run();
- // OK - we accepted it
- } catch (JsonParseException e) {
- // OK - we rejected it
- } catch (StackOverflowError e) {
- // OK - acceptable for deeply nested structures
- LOGGER.warning("StackOverflowError on implementation-defined: " + description);
- } catch (Exception e) {
- // NOT OK - unexpected exception type
- fail("Unexpected exception for %s: %s", description, e);
- }
- }
-}
diff --git a/json-compatibility-suite/src/test/resources/json-test-suite-data.zip b/json-compatibility-suite/src/test/resources/json-test-suite-data.zip
new file mode 100644
index 0000000..dc02024
Binary files /dev/null and b/json-compatibility-suite/src/test/resources/json-test-suite-data.zip differ
diff --git a/json-java21-schema/pom.xml b/json-java21-schema/pom.xml
index 043a720..c861681 100644
--- a/json-java21-schema/pom.xml
+++ b/json-java21-schema/pom.xml
@@ -96,36 +96,6 @@
-
-
-
- org.apache.maven.plugins
- maven-antrun-plugin
- 3.1.0
-
-
- fetch-json-schema-suite
- pre-integration-test
-
- run
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/json-java21-schema/src/main/java/io/github/simbo1905/json/schema/JsonSchema.java b/json-java21-schema/src/main/java/io/github/simbo1905/json/schema/JsonSchema.java
index 461f0cc..2f5511a 100644
--- a/json-java21-schema/src/main/java/io/github/simbo1905/json/schema/JsonSchema.java
+++ b/json-java21-schema/src/main/java/io/github/simbo1905/json/schema/JsonSchema.java
@@ -663,9 +663,9 @@ static boolean scheduleRemoteIfUnseen(Deque workStack,
// Detect remote cycles by walking parent chain
if (formsRemoteCycle(parentMap, currentDocUri, targetDocUri)) {
- String cycleMessage = "ERROR: CYCLE: remote $ref cycle current=" + currentDocUri + ", target=" + targetDocUri;
+ String cycleMessage = "ERROR: CYCLE: remote $ref cycle detected current=" + currentDocUri + ", target=" + targetDocUri;
LOG.severe(() -> cycleMessage);
- throw new IllegalArgumentException(cycleMessage);
+ throw new IllegalStateException(cycleMessage);
}
// Check if already built or already in work stack
@@ -1995,9 +1995,9 @@ private static JsonSchema compileInternalWithContext(Session session, JsonValue
LOG.fine(() -> "Remote ref scheduling from docUri=" + docUri + " to target=" + targetDocUri);
LOG.finest(() -> "Remote ref parentMap before cycle check: " + session.parentMap);
if (formsRemoteCycle(session.parentMap, docUri, targetDocUri)) {
- String cycleMessage = "ERROR: CYCLE: remote $ref cycle current=" + docUri + ", target=" + targetDocUri;
+ String cycleMessage = "ERROR: CYCLE: remote $ref cycle detected current=" + docUri + ", target=" + targetDocUri;
LOG.severe(() -> cycleMessage);
- throw new IllegalArgumentException(cycleMessage);
+ throw new IllegalStateException(cycleMessage);
}
boolean alreadySeen = seenUris.contains(targetDocUri);
LOG.finest(() -> "Remote ref alreadySeen=" + alreadySeen + " for target=" + targetDocUri);
diff --git a/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaCheckIT.java b/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaCheckIT.java
index 497dc82..9cfb13c 100644
--- a/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaCheckIT.java
+++ b/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaCheckIT.java
@@ -9,8 +9,13 @@
import org.junit.jupiter.api.Assumptions;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Stream;
@@ -22,10 +27,11 @@
/// By default, this is lenient and will SKIP mismatches and unsupported schemas
/// to provide a compatibility signal without breaking the build. Enable strict
/// mode with -Djson.schema.strict=true to make mismatches fail the build.
+/// Test data location: see src/test/resources/JSONSchemaTestSuite-20250921/DOWNLOAD_COMMANDS.md
public class JsonSchemaCheckIT {
- private static final File SUITE_ROOT =
- new File("target/json-schema-test-suite/tests/draft2020-12");
+ private static final Path ZIP_FILE = Paths.get("src/test/resources/json-schema-test-suite-data.zip");
+ private static final Path TARGET_SUITE_DIR = Paths.get("target/test-data/draft2020-12");
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final boolean STRICT = Boolean.getBoolean("json.schema.strict");
private static final String METRICS_FMT = System.getProperty("json.schema.metrics", "").trim();
@@ -34,15 +40,63 @@ public class JsonSchemaCheckIT {
@SuppressWarnings("resource")
@TestFactory
Stream runOfficialSuite() throws Exception {
- return Files.walk(SUITE_ROOT.toPath())
+ extractTestData();
+ return Files.walk(TARGET_SUITE_DIR)
.filter(p -> p.toString().endsWith(".json"))
.flatMap(this::testsFromFile);
}
- private Stream testsFromFile(Path file) {
+ static void extractTestData() throws IOException {
+ if (!Files.exists(ZIP_FILE)) {
+ throw new RuntimeException("Test data ZIP file not found: " + ZIP_FILE.toAbsolutePath());
+ }
+
+ // Create target directory
+ Files.createDirectories(TARGET_SUITE_DIR.getParent());
+
+ // Extract ZIP file
+ try (ZipInputStream zis = new ZipInputStream(new FileInputStream(ZIP_FILE.toFile()))) {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ if (!entry.isDirectory() && (entry.getName().startsWith("draft2020-12/") || entry.getName().startsWith("remotes/"))) {
+ Path outputPath = TARGET_SUITE_DIR.resolve(entry.getName());
+ Files.createDirectories(outputPath.getParent());
+ Files.copy(zis, outputPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
+ }
+ zis.closeEntry();
+ }
+ }
+
+ // Verify the target directory exists after extraction
+ if (!Files.exists(TARGET_SUITE_DIR)) {
+ throw new RuntimeException("Extraction completed but target directory not found: " + TARGET_SUITE_DIR.toAbsolutePath());
+ }
+ }
+
+ Stream testsFromFile(Path file) {
try {
final var root = MAPPER.readTree(file.toFile());
+ /// The JSON Schema Test Suite contains two types of files:
+ /// 1. Test suite files: Arrays containing test groups with description, schema, and tests fields
+ /// 2. Remote reference files: Plain JSON schema files used as remote references by test cases
+ ///
+ /// We only process test suite files. Remote reference files (like remotes/baseUriChangeFolder/folderInteger.json)
+ /// are just schema documents that get loaded via $ref during test execution, not test cases themselves.
+
+ /// Validate that this is a test suite file (array of objects with description, schema, tests)
+ if (!root.isArray() || root.isEmpty()) {
+ // Not a test suite file, skip it
+ return Stream.empty();
+ }
+
+ /// Validate first group has required fields
+ final var firstGroup = root.get(0);
+ if (!firstGroup.has("description") || !firstGroup.has("schema") || !firstGroup.has("tests")) {
+ // Not a test suite file, skip it
+ return Stream.empty();
+ }
+
/// Count groups and tests discovered
final var groupCount = root.size();
METRICS.groupsDiscovered.add(groupCount);
@@ -140,12 +194,12 @@ private Stream testsFromFile(Path file) {
}
}
- private static StrictMetrics.FileCounters perFile(Path file) {
+ static StrictMetrics.FileCounters perFile(Path file) {
return METRICS.perFile.computeIfAbsent(file.getFileName().toString(), k -> new StrictMetrics.FileCounters());
}
/// Helper to check if we're running in strict mode
- private static boolean isStrict() {
+ static boolean isStrict() {
return STRICT;
}
@@ -195,7 +249,7 @@ static void printAndPersistMetrics() throws Exception {
}
}
- private static String buildJsonSummary(boolean strict, String timestamp) {
+ static String buildJsonSummary(boolean strict, String timestamp) {
var totals = new StringBuilder();
totals.append("{\n");
totals.append(" \"mode\": \"").append(strict ? "STRICT" : "LENIENT").append("\",\n");
@@ -238,7 +292,7 @@ private static String buildJsonSummary(boolean strict, String timestamp) {
return totals.toString();
}
- private static String buildCsvSummary(boolean strict, String timestamp) {
+ static String buildCsvSummary(boolean strict, String timestamp) {
var csv = new StringBuilder();
csv.append("mode,timestamp,groupsDiscovered,testsDiscovered,validationsRun,passed,failed,skippedUnsupported,skipTestException,skippedMismatch\n");
csv.append(strict ? "STRICT" : "LENIENT").append(",");
diff --git a/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaRemoteRefTest.java b/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaRemoteRefTest.java
index 136d58f..5955145 100644
--- a/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaRemoteRefTest.java
+++ b/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaRemoteRefTest.java
@@ -303,7 +303,7 @@ void detects_cross_document_cycle() {
"""),
JsonSchema.Options.DEFAULT,
options
- )).isInstanceOf(IllegalArgumentException.class)
+ )).isInstanceOf(IllegalStateException.class)
.hasMessageContaining("ERROR: CYCLE: remote $ref cycle");
assertThat(logs.lines().stream().anyMatch(line -> line.startsWith("ERROR: CYCLE:"))).isTrue();
}
diff --git a/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaRemoteServerRefTest.java b/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaRemoteServerRefTest.java
index 3c34228..8325551 100644
--- a/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaRemoteServerRefTest.java
+++ b/json-java21-schema/src/test/java/io/github/simbo1905/json/schema/JsonSchemaRemoteServerRefTest.java
@@ -25,16 +25,17 @@ void resolves_pointer_inside_remote_doc_via_http() {
}
@Test
- void remote_cycle_handles_gracefully() {
+ void remote_cycle_detected_and_throws() {
var policy = JsonSchema.FetchPolicy.defaults().withAllowedSchemes(Set.of("http","https"));
var options = JsonSchema.CompileOptions.remoteDefaults(new VirtualThreadHttpFetcher()).withFetchPolicy(policy);
- // Compilation should succeed despite the cycle
- var compiled = JsonSchema.compile(Json.parse("{\"$ref\":\"" + SERVER.url("/cycle1.json") + "#\"}"), JsonSchema.Options.DEFAULT, options);
-
- // Validation should succeed by gracefully handling the cycle
- var result = compiled.validate(Json.parse("\"test\""));
- assertThat(result.valid()).isTrue();
+ // Cycles should be detected and throw an exception regardless of scheme
+ assertThatThrownBy(() -> JsonSchema.compile(
+ Json.parse("{\"$ref\":\"" + SERVER.url("/cycle1.json") + "#\"}"),
+ JsonSchema.Options.DEFAULT,
+ options
+ )).isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining("ERROR: CYCLE: remote $ref cycle");
}
}
diff --git a/json-java21-schema/src/test/resources/json-schema-test-suite-data.zip b/json-java21-schema/src/test/resources/json-schema-test-suite-data.zip
new file mode 100644
index 0000000..938fed0
Binary files /dev/null and b/json-java21-schema/src/test/resources/json-schema-test-suite-data.zip differ