From 7ced18c1a6c49416cf6a28911cccd195cbbaabc9 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 5 Dec 2024 10:43:29 -0700 Subject: [PATCH] Tests to validate generated XML against XSDs, ensure round-tripping XML works --- .gitignore | 1 + Src/java/cql-to-elm/build.gradle | 2 +- Src/java/elm-jaxb/build.gradle | 2 + .../jaxb/SerializationRoundTripTest.java | 40 ++++++++++ .../jaxb/TestLibrarySourceProvider.java | 36 +++++++++ .../cql/elm/serializing/jaxb/TestUtils.java | 26 +++++++ .../serializing/jaxb/XSDValidationTest.java | 76 +++++++++++++++++++ 7 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/SerializationRoundTripTest.java create mode 100644 Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/TestLibrarySourceProvider.java create mode 100644 Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/TestUtils.java create mode 100644 Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/XSDValidationTest.java diff --git a/.gitignore b/.gitignore index 05eae09d5..417f110ae 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ cql-all.ipr .DS_STORE out .project +.kotlin .classpath .settings build diff --git a/Src/java/cql-to-elm/build.gradle b/Src/java/cql-to-elm/build.gradle index 5b782b882..587df9f02 100644 --- a/Src/java/cql-to-elm/build.gradle +++ b/Src/java/cql-to-elm/build.gradle @@ -15,7 +15,7 @@ dependencies { api 'org.apache.commons:commons-text:1.10.0' // TODO: This dependencies are required due the the fact that the CqlTranslatorOptionsMapper lives - // in the cql-to-elm project. Ideally, we'd factor out all serialization depedencies into common + // in the cql-to-elm project. Ideally, we'd factor out all serialization dependencies into common // libraries such that we could swap out jackson for something else. In the meantime, these are // "implementation" dependencies so that they are not exported downstream. implementation "com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations:${jacksonVersion}" diff --git a/Src/java/elm-jaxb/build.gradle b/Src/java/elm-jaxb/build.gradle index e3f90a2ab..6015904f8 100644 --- a/Src/java/elm-jaxb/build.gradle +++ b/Src/java/elm-jaxb/build.gradle @@ -4,4 +4,6 @@ plugins { dependencies { api project(':elm') + testImplementation project(':cql-to-elm') + testImplementation project(':model-jaxb') } diff --git a/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/SerializationRoundTripTest.java b/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/SerializationRoundTripTest.java new file mode 100644 index 000000000..a1fcb99ad --- /dev/null +++ b/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/SerializationRoundTripTest.java @@ -0,0 +1,40 @@ +package org.cqframework.cql.elm.serializing.jaxb; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class SerializationRoundTripTest { + + public static String[] dataMethod() { + return new String[] { + "OperatorTests/ArithmeticOperators.cql", + "OperatorTests/ComparisonOperators.cql", + "OperatorTests/ListOperators.cql", + }; + } + + private static String pathForFile(String cqlFile) { + return "../cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/" + cqlFile; + } + + private static final ElmXmlLibraryReader reader = new ElmXmlLibraryReader(); + private static final ElmXmlLibraryWriter writer = new ElmXmlLibraryWriter(); + + @ParameterizedTest + @MethodSource("dataMethod") + void roundTrip(String cqlFile) throws IOException { + var translator = TestUtils.createTranslator(pathForFile(cqlFile)); + assertEquals(0, translator.getErrors().size()); + + var xml = translator.toXml(); + var library = reader.read(new StringReader(xml)); + var stringWriter = new StringWriter(); + writer.write(library, stringWriter); + assertEquals(xml, stringWriter.toString()); + } +} diff --git a/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/TestLibrarySourceProvider.java b/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/TestLibrarySourceProvider.java new file mode 100644 index 000000000..7ae22fd60 --- /dev/null +++ b/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/TestLibrarySourceProvider.java @@ -0,0 +1,36 @@ +package org.cqframework.cql.elm.serializing.jaxb; + +import java.io.InputStream; +import org.cqframework.cql.cql2elm.LibraryContentType; +import org.cqframework.cql.cql2elm.LibrarySourceProvider; +import org.hl7.elm.r1.VersionedIdentifier; + +public class TestLibrarySourceProvider implements LibrarySourceProvider { + + private String path = "LibraryTests"; + + public TestLibrarySourceProvider() {} + + public TestLibrarySourceProvider(String path) { + this.path = path; + } + + @Override + public InputStream getLibrarySource(VersionedIdentifier libraryIdentifier) { + return getLibraryContent(libraryIdentifier, LibraryContentType.CQL); + } + + @Override + public InputStream getLibraryContent(VersionedIdentifier libraryIdentifier, LibraryContentType type) { + return TestLibrarySourceProvider.class.getResourceAsStream(getFileName(libraryIdentifier, type)); + } + + private String getFileName(VersionedIdentifier libraryIdentifier, LibraryContentType type) { + return String.format( + "%s/%s%s.%s", + path, + libraryIdentifier.getId(), + libraryIdentifier.getVersion() != null ? ("-" + libraryIdentifier.getVersion()) : "", + type.toString().toLowerCase()); + } +} diff --git a/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/TestUtils.java b/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/TestUtils.java new file mode 100644 index 000000000..19aa1ab27 --- /dev/null +++ b/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/TestUtils.java @@ -0,0 +1,26 @@ +package org.cqframework.cql.elm.serializing.jaxb; + +import java.io.IOException; +import org.cqframework.cql.cql2elm.CqlCompilerOptions; +import org.cqframework.cql.cql2elm.CqlTranslator; +import org.cqframework.cql.cql2elm.LibraryManager; +import org.cqframework.cql.cql2elm.ModelManager; + +public class TestUtils { + public static CqlTranslator createTranslator(String testFileName, CqlCompilerOptions.Options... options) + throws IOException { + return CqlTranslator.fromFile(testFileName, getLibraryManager(options)); + } + + private static LibraryManager getLibraryManager(CqlCompilerOptions.Options... options) { + final ModelManager modelManager = new ModelManager(); + final CqlCompilerOptions compilerOptions = new CqlCompilerOptions(options); + return getLibraryManager(compilerOptions, modelManager); + } + + private static LibraryManager getLibraryManager(CqlCompilerOptions options, ModelManager modelManager) { + final LibraryManager libraryManager = new LibraryManager(modelManager, options); + libraryManager.getLibrarySourceLoader().registerProvider(new TestLibrarySourceProvider()); + return libraryManager; + } +} diff --git a/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/XSDValidationTest.java b/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/XSDValidationTest.java new file mode 100644 index 000000000..2947d63e6 --- /dev/null +++ b/Src/java/elm-jaxb/src/test/java/org/cqframework/cql/elm/serializing/jaxb/XSDValidationTest.java @@ -0,0 +1,76 @@ +package org.cqframework.cql.elm.serializing.jaxb; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.file.Path; +import javax.xml.XMLConstants; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import org.cqframework.cql.cql2elm.CqlTranslator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +class XSDValidationTest { + private static final Logger logger = LoggerFactory.getLogger(XSDValidationTest.class); + + public static String[] dataMethod() { + return new String[] { + "OperatorTests/ArithmeticOperators.cql", + "OperatorTests/ComparisonOperators.cql", + "OperatorTests/ListOperators.cql", + }; + } + + private static final Validator validator = createValidator(); + + private static Validator createValidator() { + try { + var schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + var schema = schemaFactory.newSchema( + Path.of("../../cql-lm/schema/elm/library.xsd").toFile()); + return schema.newValidator(); + } catch (SAXException e) { + throw new RuntimeException(e); + } + } + + private static String pathForFile(String cqlFile) { + return "../cql-to-elm/src/test/resources/org/cqframework/cql/cql2elm/" + cqlFile; + } + + private static boolean validateXMLAgainstXSD(String cqlFile, String xml) { + try { + validator.validate(new StreamSource(new StringReader(xml))); + return true; + } catch (IOException | SAXException e) { + logger.error("error validating XML against XSD for file {}", cqlFile, e); + } + return false; + } + + @Test + void ensureValidatorFailsForBadXml() { + var xml = ""; + var source = new StreamSource(new StringReader(xml)); + assertThrows(SAXException.class, () -> validator.validate(source)); + } + + @ParameterizedTest + @MethodSource("dataMethod") + void validateXML(String cqlFile) throws IOException { + var path = pathForFile(cqlFile); + var t = TestUtils.createTranslator(path); + assertTrue(t.getErrors().isEmpty()); + + var xml = CqlTranslator.convertToXml(t.toELM()); + assertTrue(validateXMLAgainstXSD(cqlFile, xml)); + } +}