diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c3de11587..49e8a00ca 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,4 +8,5 @@ ## Other code changes -* no changes \ No newline at end of file +* Improved output for unit test comparisons + diff --git a/org.hl7.fhir.convertors/src/test/java/org/hl7/fhir/convertors/conv40_50/AuditEvent40_50Test.java b/org.hl7.fhir.convertors/src/test/java/org/hl7/fhir/convertors/conv40_50/AuditEvent40_50Test.java new file mode 100644 index 000000000..ab8955db4 --- /dev/null +++ b/org.hl7.fhir.convertors/src/test/java/org/hl7/fhir/convertors/conv40_50/AuditEvent40_50Test.java @@ -0,0 +1,91 @@ +package org.hl7.fhir.convertors.conv40_50; + +import org.apache.commons.codec.binary.Base64; +import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AuditEvent40_50Test { + + public static final String THE_BASE_64_BINARY_STRING = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; + public static final byte[] THE_BASE_64_BINARY_BYTE_ARRAY = Base64.decodeBase64(THE_BASE_64_BINARY_STRING.getBytes(ca.uhn.fhir.rest.api.Constants.CHARSET_UTF8)); + + public static final String INVALID_BASE_64_BINARY_STRING = "Picard was the best starship captain"; + public static final byte[] INVALID_BASE_64_BINARY_BYTE_ARRAY = Base64.decodeBase64(INVALID_BASE_64_BINARY_STRING.getBytes(ca.uhn.fhir.rest.api.Constants.CHARSET_UTF8)); + + + @Test + @DisplayName("Test r5 -> r4 AuditEvent conversion.") + public void testR5_R4() throws IOException { + InputStream r5_input = this.getClass().getResourceAsStream("/auditevent_50_with_base64binary.xml"); + + org.hl7.fhir.r5.model.AuditEvent r5_actual = (org.hl7.fhir.r5.model.AuditEvent) new org.hl7.fhir.r5.formats.XmlParser().parse(r5_input); + org.hl7.fhir.r4.model.Resource r4_conv = VersionConvertorFactory_40_50.convertResource(r5_actual); + + org.hl7.fhir.r4.formats.XmlParser r4_parser = new org.hl7.fhir.r4.formats.XmlParser(); + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + r4_parser.compose(stream, r4_conv); + + org.hl7.fhir.r4.model.Resource r4_streamed = (org.hl7.fhir.r4.model.AuditEvent) new org.hl7.fhir.r4.formats.XmlParser().parse(new ByteArrayInputStream(stream.toByteArray())); + + assertArrayEquals(((org.hl7.fhir.r4.model.AuditEvent)r4_conv).getEntity().get(0).getQuery(), THE_BASE_64_BINARY_BYTE_ARRAY); + assertArrayEquals(((org.hl7.fhir.r4.model.AuditEvent)r4_streamed).getEntity().get(0).getQuery(), THE_BASE_64_BINARY_BYTE_ARRAY); + } + + @Test + @DisplayName("Test r5 -> r4 AuditEvent conversion.") + public void testR4_R5() throws IOException { + InputStream r4_input = this.getClass().getResourceAsStream("/auditevent_40_with_base64binary.xml"); + + org.hl7.fhir.r4.model.AuditEvent r4_actual = (org.hl7.fhir.r4.model.AuditEvent) new org.hl7.fhir.r4.formats.XmlParser().parse(r4_input); + org.hl7.fhir.r5.model.Resource r5_conv = VersionConvertorFactory_40_50.convertResource(r4_actual); + + org.hl7.fhir.r5.formats.XmlParser r5_parser = new org.hl7.fhir.r5.formats.XmlParser(); + + ByteArrayOutputStream stream + = new ByteArrayOutputStream(); + + r5_parser.compose(stream, r5_conv); + + org.hl7.fhir.r5.model.Resource r5_streamed = (org.hl7.fhir.r5.model.AuditEvent) new org.hl7.fhir.r5.formats.XmlParser().parse(new ByteArrayInputStream(stream.toByteArray())); + + assertArrayEquals(((org.hl7.fhir.r5.model.AuditEvent)r5_conv).getEntity().get(0).getQuery(), THE_BASE_64_BINARY_BYTE_ARRAY); + assertArrayEquals(((org.hl7.fhir.r5.model.AuditEvent)r5_streamed).getEntity().get(0).getQuery(), THE_BASE_64_BINARY_BYTE_ARRAY); + + } + + @Test + @DisplayName("Test r5 -> r4 AuditEvent conversion with invalid Base64Binary.") + public void testR4_R5BadBase64Binary() throws IOException { + InputStream r4_input = this.getClass().getResourceAsStream("/auditevent_40_with_invalid_base64binary.xml"); + + org.hl7.fhir.r4.model.AuditEvent r4_actual = (org.hl7.fhir.r4.model.AuditEvent) new org.hl7.fhir.r4.formats.XmlParser().parse(r4_input); + + org.hl7.fhir.r5.model.Resource r5_conv = VersionConvertorFactory_40_50.convertResource(r4_actual); + + org.hl7.fhir.r5.formats.XmlParser r5_parser = new org.hl7.fhir.r5.formats.XmlParser(); + + ByteArrayOutputStream stream + = new ByteArrayOutputStream(); + + r5_parser.compose(stream, r5_conv); + + org.hl7.fhir.r5.model.Resource r5_streamed = (org.hl7.fhir.r5.model.AuditEvent) new org.hl7.fhir.r5.formats.XmlParser().parse(new ByteArrayInputStream(stream.toByteArray())); + + System.out.println(((org.hl7.fhir.r5.model.AuditEvent)r5_conv).getEntity().get(0).getQueryElement().getValueAsString()); + + //FIXME we should not be even getting this far. + assertArrayEquals(((org.hl7.fhir.r5.model.AuditEvent)r5_conv).getEntity().get(0).getQuery(), INVALID_BASE_64_BINARY_BYTE_ARRAY); + assertArrayEquals(((org.hl7.fhir.r5.model.AuditEvent)r5_streamed).getEntity().get(0).getQuery(), INVALID_BASE_64_BINARY_BYTE_ARRAY); + + } +} diff --git a/org.hl7.fhir.convertors/src/test/resources/auditevent_40_with_base64binary.xml b/org.hl7.fhir.convertors/src/test/resources/auditevent_40_with_base64binary.xml new file mode 100644 index 000000000..812edeb05 --- /dev/null +++ b/org.hl7.fhir.convertors/src/test/resources/auditevent_40_with_base64binary.xml @@ -0,0 +1,95 @@ + + + + + + +
Application Start for under service login "Grahame" (id: Grahame's Test HL7Connect)
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/test/resources/auditevent_40_with_invalid_base64binary.xml b/org.hl7.fhir.convertors/src/test/resources/auditevent_40_with_invalid_base64binary.xml new file mode 100644 index 000000000..c60c9750c --- /dev/null +++ b/org.hl7.fhir.convertors/src/test/resources/auditevent_40_with_invalid_base64binary.xml @@ -0,0 +1,95 @@ + + + + + + +
Application Start for under service login "Grahame" (id: Grahame's Test HL7Connect)
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/test/resources/auditevent_50_with_base64binary.xml b/org.hl7.fhir.convertors/src/test/resources/auditevent_50_with_base64binary.xml new file mode 100644 index 000000000..3730ef7cd --- /dev/null +++ b/org.hl7.fhir.convertors/src/test/resources/auditevent_50_with_base64binary.xml @@ -0,0 +1,100 @@ + + + + + + +
Application Start for under service login "Grahame" (id: Grahame's Test HL7Connect)
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/model/Base64BinaryTypeTest.java b/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/model/Base64BinaryTypeTest.java index fef6661ff..1d9ed516f 100644 --- a/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/model/Base64BinaryTypeTest.java +++ b/org.hl7.fhir.dstu2/src/test/java/org/hl7/fhir/dstu2/model/Base64BinaryTypeTest.java @@ -1,6 +1,7 @@ package org.hl7.fhir.dstu2.model; import ca.uhn.fhir.parser.DataFormatException; +import org.apache.commons.codec.binary.Base64; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,6 +12,8 @@ class Base64BinaryTypeTest { static final String NON_BASE_64 = "Picard was the best starship captain."; static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; + static final byte[] VALID_BASE_64_BYTES = Base64.decodeBase64(VALID_BASE_64.getBytes(ca.uhn.fhir.rest.api.Constants.CHARSET_UTF8)); + @Test @DisplayName("Passing a non Base64 encoded String to constructor causes exception.") public void testNonBase64String() { @@ -45,6 +48,15 @@ class Base64BinaryTypeTest { Assertions.assertNull(b64.getValueAsString()); } + @Test + @DisplayName("Valid Base64 String creates non-null instance with non-null bytes.") + public void testValidBytes() { + Base64BinaryType b64 = new Base64BinaryType(VALID_BASE_64_BYTES); + Assertions.assertNotNull(b64); + Assertions.assertNotNull(b64.getValue()); + Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); + } + @Test @DisplayName("Valid Base64 String creates non-null instance with non-null values.") public void testValid() { diff --git a/org.hl7.fhir.dstu2016may/src/test/java/org/hl7/fhir/dstu2016may/model/Base64BinaryTypeTest.java b/org.hl7.fhir.dstu2016may/src/test/java/org/hl7/fhir/dstu2016may/model/Base64BinaryTypeTest.java index 529cb0425..f8d6021e7 100644 --- a/org.hl7.fhir.dstu2016may/src/test/java/org/hl7/fhir/dstu2016may/model/Base64BinaryTypeTest.java +++ b/org.hl7.fhir.dstu2016may/src/test/java/org/hl7/fhir/dstu2016may/model/Base64BinaryTypeTest.java @@ -1,6 +1,7 @@ package org.hl7.fhir.dstu2016may.model; import ca.uhn.fhir.parser.DataFormatException; +import org.apache.commons.codec.binary.Base64; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,6 +12,8 @@ class Base64BinaryTypeTest { static final String NON_BASE_64 = "Picard was the best starship captain."; static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; + static final byte[] VALID_BASE_64_BYTES = Base64.decodeBase64(VALID_BASE_64.getBytes(ca.uhn.fhir.rest.api.Constants.CHARSET_UTF8)); + @Test @DisplayName("Passing a non Base64 encoded String to constructor causes exception.") public void testNonBase64String() { @@ -45,6 +48,15 @@ class Base64BinaryTypeTest { Assertions.assertNull(b64.getValueAsString()); } + @Test + @DisplayName("Valid Base64 String creates non-null instance with non-null bytes.") + public void testValidBytes() { + Base64BinaryType b64 = new Base64BinaryType(VALID_BASE_64_BYTES); + Assertions.assertNotNull(b64); + Assertions.assertNotNull(b64.getValue()); + Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); + } + @Test @DisplayName("Valid Base64 String creates non-null instance with non-null values.") public void testValid() { diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/Base64BinaryType.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/Base64BinaryType.java index b4c3d5293..99b7f325d 100644 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/Base64BinaryType.java +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/model/Base64BinaryType.java @@ -130,7 +130,7 @@ public class Base64BinaryType extends PrimitiveType implements IPrimitiv @Override public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException { myValue = theValue; - return this; + return (Base64BinaryType) super.setValue(theValue); } @Override diff --git a/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/model/Base64BinaryTypeTest.java b/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/model/Base64BinaryTypeTest.java index a0d879bde..37b5249e8 100644 --- a/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/model/Base64BinaryTypeTest.java +++ b/org.hl7.fhir.dstu3/src/test/java/org/hl7/fhir/dstu3/model/Base64BinaryTypeTest.java @@ -1,6 +1,7 @@ package org.hl7.fhir.dstu3.model; import ca.uhn.fhir.parser.DataFormatException; +import org.apache.commons.codec.binary.Base64; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,6 +12,8 @@ class Base64BinaryTypeTest { static final String NON_BASE_64 = "Picard was the best starship captain."; static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; + static final byte[] VALID_BASE_64_BYTES = Base64.decodeBase64(VALID_BASE_64.getBytes(ca.uhn.fhir.rest.api.Constants.CHARSET_UTF8)); + @Test @DisplayName("Passing a non Base64 encoded String to constructor causes exception.") public void testNonBase64String() { @@ -54,6 +57,15 @@ class Base64BinaryTypeTest { Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); } + @Test + @DisplayName("Valid Base64 String creates non-null instance with non-null bytes.") + public void testValidBytes() { + Base64BinaryType b64 = new Base64BinaryType(VALID_BASE_64_BYTES); + Assertions.assertNotNull(b64); + Assertions.assertNotNull(b64.getValue()); + Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); + } + @Test @DisplayName("Valid Base64 String creates non-null instance with non-null values.") public void testValidSetValueAsString() { diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Base64BinaryType.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Base64BinaryType.java index f51356a52..7cdceedf7 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Base64BinaryType.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/model/Base64BinaryType.java @@ -130,7 +130,7 @@ public class Base64BinaryType extends PrimitiveType implements IPrimitiv @Override public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException { myValue = theValue; - return this; + return (Base64BinaryType) super.setValue(theValue); } @Override diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/model/Base64BinaryTypeTest.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/model/Base64BinaryTypeTest.java index 3e33c79d2..72a469d5a 100644 --- a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/model/Base64BinaryTypeTest.java +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/model/Base64BinaryTypeTest.java @@ -1,6 +1,7 @@ package org.hl7.fhir.r4.model; import ca.uhn.fhir.parser.DataFormatException; +import org.apache.commons.codec.binary.Base64; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,6 +12,8 @@ class Base64BinaryTypeTest { static final String NON_BASE_64 = "Picard was the best starship captain."; static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; + static final byte[] VALID_BASE_64_BYTES = Base64.decodeBase64(VALID_BASE_64.getBytes(ca.uhn.fhir.rest.api.Constants.CHARSET_UTF8)); + @Test @DisplayName("Passing a non Base64 encoded String to constructor causes exception.") public void testNonBase64String() { @@ -54,6 +57,15 @@ class Base64BinaryTypeTest { Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); } + @Test + @DisplayName("Valid Base64 String creates non-null instance with non-null bytes.") + public void testValidBytes() { + Base64BinaryType b64 = new Base64BinaryType(VALID_BASE_64_BYTES); + Assertions.assertNotNull(b64); + Assertions.assertNotNull(b64.getValue()); + Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); + } + @Test @DisplayName("Valid Base64 String creates non-null instance with non-null values.") public void testValidSetValueAsString() { diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/model/Base64BinaryType.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/model/Base64BinaryType.java index 4f413fbd5..a1c73f821 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/model/Base64BinaryType.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/model/Base64BinaryType.java @@ -129,7 +129,7 @@ public class Base64BinaryType extends PrimitiveType implements IPrimitiv @Override public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException { myValue = theValue; - return this; + return (Base64BinaryType) super.setValue(theValue); } @Override diff --git a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/model/Base64BinaryTypeTest.java b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/model/Base64BinaryTypeTest.java index 5bc9deae9..5bb7e854f 100644 --- a/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/model/Base64BinaryTypeTest.java +++ b/org.hl7.fhir.r4b/src/test/java/org/hl7/fhir/r4b/model/Base64BinaryTypeTest.java @@ -2,6 +2,7 @@ package org.hl7.fhir.r4b.model; import static org.junit.jupiter.api.Assertions.assertThrows; +import org.apache.commons.codec.binary.Base64; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,6 +13,8 @@ class Base64BinaryTypeTest { static final String NON_BASE_64 = "Picard was the best starship captain."; static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; + static final byte[] VALID_BASE_64_BYTES = Base64.decodeBase64(VALID_BASE_64.getBytes(ca.uhn.fhir.rest.api.Constants.CHARSET_UTF8)); + @Test @DisplayName("Passing a non Base64 encoded String to constructor causes exception.") public void testNonBase64String() { @@ -55,6 +58,15 @@ class Base64BinaryTypeTest { Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); } + @Test + @DisplayName("Valid Base64 String creates non-null instance with non-null bytes.") + public void testValidBytes() { + Base64BinaryType b64 = new Base64BinaryType(VALID_BASE_64_BYTES); + Assertions.assertNotNull(b64); + Assertions.assertNotNull(b64.getValue()); + Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); + } + @Test @DisplayName("Valid Base64 String creates non-null instance with non-null values.") public void testValidSetValueAsString() { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base64BinaryType.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base64BinaryType.java index 66473144b..4e83d1a8b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base64BinaryType.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base64BinaryType.java @@ -129,7 +129,7 @@ public class Base64BinaryType extends PrimitiveType implements IPrimitiv @Override public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException { myValue = theValue; - return this; + return (Base64BinaryType) super.setValue(theValue); } @Override diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java index 3e07a1819..9b8df876b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java @@ -154,7 +154,7 @@ public class ValueSetRenderer extends TerminologyRenderer { private boolean generateExpansion(XhtmlNode x, ValueSet vs, boolean header, List maps) throws FHIRFormatError, DefinitionException, IOException { boolean hasExtensions = false; List langs = new ArrayList(); - + Map designations = new HashMap<>(); // map of url = description, where url is the designation code. Designations that are for languages won't make it into this list if (header) { XhtmlNode h = x.addTag(getHeader()); @@ -219,20 +219,24 @@ public class ValueSetRenderer extends TerminologyRenderer { tr.td().b().tx("System"); XhtmlNode tdDisp = tr.td(); tdDisp.b().tx("Display"); - boolean doLangs = false; + boolean doDesignations = false; for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { - scanForLangs(c, langs); + scanForDesignations(c, langs, designations); } if (doDefinition) { tr.td().b().tx("Definition"); - doLangs = false; + doDesignations = false; } else { // if we're not doing definitions and we don't have too many languages, we'll do them in line - if (langs.size() < MAX_DESIGNATIONS_IN_LINE) { - doLangs = true; + doDesignations = langs.size() + designations.size() < MAX_DESIGNATIONS_IN_LINE; + + if (doDesignations) { if (vs.hasLanguage()) { tdDisp.tx(" - "+describeLang(vs.getLanguage())); } + for (String url : designations.keySet()) { + tr.td().b().addText(designations.get(url)); + } for (String lang : langs) { tr.td().b().addText(describeLang(lang)); } @@ -242,22 +246,31 @@ public class ValueSetRenderer extends TerminologyRenderer { addMapHeaders(tr, maps); for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { - addExpansionRowToTable(t, c, 1, doLevel, doSystem, doDefinition, maps, allCS, langs, doLangs); + addExpansionRowToTable(t, c, 1, doLevel, doSystem, doDefinition, maps, allCS, langs, designations, doDesignations); } // now, build observed languages - if (!doLangs && langs.size() > 0) { + if (!doDesignations && langs.size() + designations.size() > 0) { Collections.sort(langs); - x.para().b().tx("Additional Language Displays"); - t = x.table( "codes"); + if (designations.size() == 0) { + x.para().b().tx("Additional Language Displays"); + } else if (langs.size() == 0) { + x.para().b().tx("Additional Designations"); + } else { + x.para().b().tx("Additional Designations and Language Displays"); + } + t = x.table("codes"); tr = t.tr(); tr.td().b().tx("Code"); + for (String url : designations.keySet()) { + tr.td().b().addText(designations.get(url)); + } for (String lang : langs) { tr.td().b().addText(describeLang(lang)); } for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { - addLanguageRow(c, t, langs); + addDesignationRow(c, t, langs, designations); } } @@ -556,12 +569,27 @@ public class ValueSetRenderer extends TerminologyRenderer { return false; } - private void addLanguageRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List langs) { + private void addDesignationRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List langs, Map designations) { XhtmlNode tr = t.tr(); tr.td().addText(c.getCode()); + addDesignationsToRow(c, designations, tr); addLangaugesToRow(c, langs, tr); for (ValueSetExpansionContainsComponent cc : c.getContains()) { - addLanguageRow(cc, t, langs); + addDesignationRow(cc, t, langs, designations); + } + } + + public void addDesignationsToRow(ValueSetExpansionContainsComponent c, Map designations, XhtmlNode tr) { + for (String url : designations.keySet()) { + String d = null; + if (d == null) { + for (ConceptReferenceDesignationComponent dd : c.getDesignation()) { + if (url.equals(getUrlForDesignation(dd))) { + d = dd.getValue(); + } + } + } + tr.td().addText(d == null ? "" : d); } } @@ -632,6 +660,36 @@ public class ValueSetRenderer extends TerminologyRenderer { return ref.replace("\\", "/"); } + private void scanForDesignations(ValueSetExpansionContainsComponent c, List langs, Map designations) { + for (Extension ext : c.getExtension()) { + if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { + String lang = ToolingExtensions.readStringExtension(ext, "lang"); + if (!Utilities.noString(lang) && !langs.contains(lang)) { + langs.add(lang); + } + } + } + for (ConceptReferenceDesignationComponent d : c.getDesignation()) { + String lang = d.getLanguage(); + if (!Utilities.noString(lang) && !langs.contains(lang)) { + langs.add(lang); + } else { + // can we present this as a designation that we know? + String disp = getDisplayForDesignation(d); + String url = getUrlForDesignation(d); + if (disp == null) { + disp = getDisplayForUrl(url); + } + if (disp != null && !designations.containsKey(url)) { + designations.put(url, disp); + } + } + } + for (ValueSetExpansionContainsComponent cc : c.getContains()) { + scanForDesignations(cc, langs, designations); + } + } + private void scanForLangs(ValueSetExpansionContainsComponent c, List langs) { for (Extension ext : c.getExtension()) { if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { @@ -651,8 +709,8 @@ public class ValueSetRenderer extends TerminologyRenderer { scanForLangs(cc, langs); } } - - private void addExpansionRowToTable(XhtmlNode t, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doSystem, boolean doDefinition, List maps, CodeSystem allCS, List langs, boolean doLangs) { + + private void addExpansionRowToTable(XhtmlNode t, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doSystem, boolean doDefinition, List maps, CodeSystem allCS, List langs, Map designations, boolean doDesignations) { XhtmlNode tr = t.tr(); XhtmlNode td = tr.td(); @@ -697,11 +755,12 @@ public class ValueSetRenderer extends TerminologyRenderer { td.i().tx("("+mapping.comp.getComment()+")"); } } - if (doLangs) { + if (doDesignations) { + addDesignationsToRow(c, designations, tr); addLangaugesToRow(c, langs, tr); } for (ValueSetExpansionContainsComponent cc : c.getContains()) { - addExpansionRowToTable(t, cc, i+1, doLevel, doSystem, doDefinition, maps, allCS, langs, doLangs); + addExpansionRowToTable(t, cc, i+1, doLevel, doSystem, doDefinition, maps, allCS, langs, designations, doDesignations); } } @@ -721,7 +780,7 @@ public class ValueSetRenderer extends TerminologyRenderer { private void addCodeToTable(boolean isAbstract, String system, String code, String display, XhtmlNode td) { CodeSystem e = getContext().getWorker().fetchCodeSystem(system); - if (e == null || e.getContent() != org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode.COMPLETE) { + if (e == null || (e.getContent() != org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode.COMPLETE && e.getContent() != org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode.FRAGMENT)) { if (isAbstract) td.i().setAttribute("title", ABSTRACT_CODE_HINT).addText(code); else if ("http://snomed.info/sct".equals(system)) { @@ -824,7 +883,7 @@ public class ValueSetRenderer extends TerminologyRenderer { } for (ConceptSetComponent c : vs.getCompose().getInclude()) { for (ConceptReferenceComponent cc : c.getConcept()) { - addLanguageRow(cc, t, langs); + addDesignationRow(cc, t, langs, designations); } } } @@ -939,9 +998,12 @@ public class ValueSetRenderer extends TerminologyRenderer { if (!Utilities.noString(lang) && !langs.contains(lang)) { langs.add(lang); } else { - // can we present this as a designation that we know? - String url = getUrlForDesignation(d); - String disp = getDisplayForUrl(url); + // can we present this as a designation that we know? + String disp = getDisplayForDesignation(d); + String url = getUrlForDesignation(d); + if (disp == null) { + disp = getDisplayForUrl(url); + } if (disp != null && !designations.containsKey(url)) { designations.put(url, disp); } @@ -960,7 +1022,8 @@ public class ValueSetRenderer extends TerminologyRenderer { case "http://snomed.info/sct#900000000000013009": return "Synonym"; default: - return null; + // As specified in http://www.hl7.org/fhir/valueset-definitions.html#ValueSet.compose.include.concept.designation.use and in http://www.hl7.org/fhir/codesystem-definitions.html#CodeSystem.concept.designation.use the terminology binding is extensible. + return url; } } @@ -972,6 +1035,14 @@ public class ValueSetRenderer extends TerminologyRenderer { } } + private String getDisplayForDesignation(ConceptReferenceDesignationComponent d) { + if (d.hasUse() && d.getUse().hasDisplay()) { + return d.getUse().getDisplay(); + } else { + return null; + } + } + private boolean genInclude(XhtmlNode ul, ConceptSetComponent inc, String type, List langs, boolean doDesignations, List maps, Map designations, int index) throws FHIRException, IOException { boolean hasExtensions = false; XhtmlNode li; @@ -1243,18 +1314,12 @@ public class ValueSetRenderer extends TerminologyRenderer { } - private void addLanguageRow(ConceptReferenceComponent c, XhtmlNode t, List langs) { + + private void addDesignationRow(ConceptReferenceComponent c, XhtmlNode t, List langs, Map designations) { XhtmlNode tr = t.tr(); tr.td().addText(c.getCode()); - for (String lang : langs) { - String d = null; - for (ConceptReferenceDesignationComponent cd : c.getDesignation()) { - String l = cd.getLanguage(); - if (lang.equals(l)) - d = cd.getValue(); - } - tr.td().addText(d == null ? "" : d); - } + addDesignationsToRow(c, designations, tr); + addLangaugesToRow(c, langs, tr); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java new file mode 100644 index 000000000..be707455d --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/CompareUtilities.java @@ -0,0 +1,357 @@ +package org.hl7.fhir.r5.test.utils; + +import org.apache.commons.codec.binary.Base64; +import org.hl7.fhir.utilities.CSFile; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.ToolGlobalSettings; +import org.hl7.fhir.utilities.Utilities; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; +import org.hl7.fhir.utilities.tests.BaseTestingUtilities; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class CompareUtilities extends BaseTestingUtilities { + + private static final boolean SHOW_DIFF = true; + + public static String createNotEqualMessage(final String message, final String expected, final String actual) { + return new StringBuilder() + .append(message).append('\n') + .append("Expected :").append(expected).append('\n') + .append("Actual :").append(actual).toString(); + } + + public static String checkXMLIsSame(InputStream expected, InputStream actual) throws Exception { + String result = compareXml(expected, actual); + return result; + } + + public static String checkXMLIsSame(String expected, String actual) throws Exception { + String result = compareXml(expected, actual); + if (result != null && SHOW_DIFF) { + String diff = ToolGlobalSettings.hasComparePath() ? ToolGlobalSettings.getComparePath() : Utilities.path(System.getenv("ProgramFiles"), "WinMerge", "WinMergeU.exe"); + if (new File(diff).exists() || Utilities.isToken(diff)) { + Runtime.getRuntime().exec(new String[]{diff, expected, actual}); + } + } + return result; + } + + private static String compareXml(InputStream expected, InputStream actual) throws Exception { + return compareElements("", loadXml(expected).getDocumentElement(), loadXml(actual).getDocumentElement()); + } + + private static String compareXml(String expected, String actual) throws Exception { + return compareElements("", loadXml(expected).getDocumentElement(), loadXml(actual).getDocumentElement()); + } + + private static String compareElements(String path, Element expectedElement, Element actualElement) { + if (!namespacesMatch(expectedElement.getNamespaceURI(), actualElement.getNamespaceURI())) + return createNotEqualMessage("Namespaces differ at " + path, expectedElement.getNamespaceURI(), actualElement.getNamespaceURI()); + if (!expectedElement.getLocalName().equals(actualElement.getLocalName())) + return createNotEqualMessage("Names differ at " + path , expectedElement.getLocalName(), actualElement.getLocalName()); + path = path + "/" + expectedElement.getLocalName(); + String s = compareAttributes(path, expectedElement.getAttributes(), actualElement.getAttributes()); + if (!Utilities.noString(s)) + return s; + s = compareAttributes(path, expectedElement.getAttributes(), actualElement.getAttributes()); + if (!Utilities.noString(s)) + return s; + + Node expectedChild = expectedElement.getFirstChild(); + Node actualChild = actualElement.getFirstChild(); + expectedChild = skipBlankText(expectedChild); + actualChild = skipBlankText(actualChild); + while (expectedChild != null && actualChild != null) { + if (expectedChild.getNodeType() != actualChild.getNodeType()) + return createNotEqualMessage("node type mismatch in children of " + path, Short.toString(expectedElement.getNodeType()), Short.toString(actualElement.getNodeType())); + if (expectedChild.getNodeType() == Node.TEXT_NODE) { + if (!normalise(expectedChild.getTextContent()).equals(normalise(actualChild.getTextContent()))) + return createNotEqualMessage("Text differs at " + path, normalise(expectedChild.getTextContent()).toString(), normalise(actualChild.getTextContent()).toString()); + } else if (expectedChild.getNodeType() == Node.ELEMENT_NODE) { + s = compareElements(path, (Element) expectedChild, (Element) actualChild); + if (!Utilities.noString(s)) + return s; + } + + expectedChild = skipBlankText(expectedChild.getNextSibling()); + actualChild = skipBlankText(actualChild.getNextSibling()); + } + if (expectedChild != null) + return "node mismatch - more nodes in actual in children of " + path; + if (actualChild != null) + return "node mismatch - more nodes in expected in children of " + path; + return null; + } + + private static boolean namespacesMatch(String ns1, String ns2) { + return ns1 == null ? ns2 == null : ns1.equals(ns2); + } + + private static Object normalise(String text) { + String result = text.trim().replace('\r', ' ').replace('\n', ' ').replace('\t', ' '); + while (result.contains(" ")) + result = result.replace(" ", " "); + return result; + } + + private static String compareAttributes(String path, NamedNodeMap expected, NamedNodeMap actual) { + for (int i = 0; i < expected.getLength(); i++) { + + Node expectedNode = expected.item(i); + String expectedNodeName = expectedNode.getNodeName(); + if (!(expectedNodeName.equals("xmlns") || expectedNodeName.startsWith("xmlns:"))) { + Node actualNode = actual.getNamedItem(expectedNodeName); + if (actualNode == null) + return "Attributes differ at " + path + ": missing attribute " + expectedNodeName; + if (!normalise(expectedNode.getTextContent()).equals(normalise(actualNode.getTextContent()))) { + byte[] b1 = unBase64(expectedNode.getTextContent()); + byte[] b2 = unBase64(actualNode.getTextContent()); + if (!sameBytes(b1, b2)) + return createNotEqualMessage("Attributes differ at " + path, normalise(expectedNode.getTextContent()).toString(), normalise(actualNode.getTextContent()).toString()) ; + } + } + } + return null; + } + + private static boolean sameBytes(byte[] b1, byte[] b2) { + if (b1.length == 0 || b2.length == 0) + return false; + if (b1.length != b2.length) + return false; + for (int i = 0; i < b1.length; i++) + if (b1[i] != b2[i]) + return false; + return true; + } + + private static byte[] unBase64(String text) { + return Base64.decodeBase64(text); + } + + private static Node skipBlankText(Node node) { + while (node != null && (((node.getNodeType() == Node.TEXT_NODE) && Utilities.isWhitespace(node.getTextContent())) || (node.getNodeType() == Node.COMMENT_NODE))) + node = node.getNextSibling(); + return node; + } + + private static Document loadXml(String fn) throws Exception { + return loadXml(new FileInputStream(fn)); + } + + private static Document loadXml(InputStream fn) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(fn); + } + + public static String checkJsonSrcIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException { + return checkJsonSrcIsSame(expected, actual, true); + } + + public static String checkJsonSrcIsSame(String expectedString, String actualString, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException { + String result = compareJsonSrc(expectedString, actualString); + if (result != null && SHOW_DIFF && showDiff) { + String diff = null; + if (System.getProperty("os.name").contains("Linux")) + diff = Utilities.path("/", "usr", "bin", "meld"); + else { + if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge"), "\\WinMergeU.exe", null)) + diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe"); + else if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld"), "\\Meld.exe", null)) + diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld", "Meld.exe"); + } + if (diff == null || diff.isEmpty()) + return result; + + List command = new ArrayList(); + String expected = Utilities.path("[tmp]", "expected" + expectedString.hashCode() + ".json"); + String actual = Utilities.path("[tmp]", "actual" + actualString.hashCode() + ".json"); + TextFile.stringToFile(expectedString, expected); + TextFile.stringToFile(actualString, actual); + command.add(diff); + if (diff.toLowerCase().contains("meld")) + command.add("--newtab"); + command.add(expected); + command.add(actual); + + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(new CSFile(Utilities.path("[tmp]"))); + builder.start(); + + } + return result; + } + + public static String checkJsonIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException { + String result = compareJson(expected, actual); + if (result != null && SHOW_DIFF) { + String diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe"); + List command = new ArrayList(); + command.add("\"" + diff + "\" \"" + expected + "\" \"" + actual + "\""); + + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(new CSFile("c:\\temp")); + builder.start(); + + } + return result; + } + + private static String compareJsonSrc(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException { + JsonObject actualJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(actual); + JsonObject expectedJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(expected); + return compareObjects("", expectedJsonObject, actualJsonObject); + } + + private static String compareJson(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException { + JsonObject actualJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(actual)); + JsonObject expectedJsonObject = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(expected)); + return compareObjects("", expectedJsonObject, actualJsonObject); + } + + private static String compareObjects(String path, JsonObject expectedJsonObject, JsonObject actualJsonObject) { + for (Map.Entry en : actualJsonObject.entrySet()) { + String n = en.getKey(); + if (!n.equals("fhir_comments")) { + if (expectedJsonObject.has(n)) { + String s = compareNodes(path + '.' + n, expectedJsonObject.get(n), en.getValue()); + if (!Utilities.noString(s)) + return s; + } else + return "properties differ at " + path + ": missing property " + n; + } + } + for (Map.Entry en : expectedJsonObject.entrySet()) { + String n = en.getKey(); + if (!n.equals("fhir_comments")) { + if (!actualJsonObject.has(n)) + return "properties differ at " + path + ": missing property " + n; + } + } + return null; + } + + private static String compareNodes(String path, JsonElement expectedJsonElement, JsonElement actualJsonElement) { + if (actualJsonElement.getClass() != expectedJsonElement.getClass()) + return createNotEqualMessage("properties differ at " + path, expectedJsonElement.getClass().getName(), actualJsonElement.getClass().getName()); + else if (actualJsonElement instanceof JsonPrimitive) { + JsonPrimitive actualJsonPrimitive = (JsonPrimitive) actualJsonElement; + JsonPrimitive expectedJsonPrimitive = (JsonPrimitive) expectedJsonElement; + if (actualJsonPrimitive.isBoolean() && expectedJsonPrimitive.isBoolean()) { + if (actualJsonPrimitive.getAsBoolean() != expectedJsonPrimitive.getAsBoolean()) + return createNotEqualMessage("boolean property values differ at " + path , expectedJsonPrimitive.getAsString(), actualJsonPrimitive.getAsString()); + } else if (actualJsonPrimitive.isString() && expectedJsonPrimitive.isString()) { + String actualJsonString = actualJsonPrimitive.getAsString(); + String expectedJsonString = expectedJsonPrimitive.getAsString(); + if (!(actualJsonString.contains(" command = new ArrayList(); + String actual = Utilities.path("[tmp]", "actual" + actualString.hashCode() + ".json"); + String expected = Utilities.path("[tmp]", "expected" + expectedString.hashCode() + ".json"); + TextFile.stringToFile(expectedString, expected); + TextFile.stringToFile(actualString, actual); + command.add(diff); + if (diff.toLowerCase().contains("meld")) + command.add("--newtab"); + command.add(expected); + command.add(actual); + + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(new CSFile(Utilities.path("[tmp]"))); + builder.start(); + + } + return result; + } + + + private static String compareText(String expectedString, String actualString) { + for (int i = 0; i < Integer.min(expectedString.length(), actualString.length()); i++) { + if (expectedString.charAt(i) != actualString.charAt(i)) + return createNotEqualMessage("Strings differ at character " + Integer.toString(i), String.valueOf(expectedString.charAt(i)), String.valueOf(actualString.charAt(i))); + } + if (expectedString.length() != actualString.length()) + return createNotEqualMessage("Strings differ in length but match to the end of the shortest.", Integer.toString(expectedString.length()), Integer.toString(actualString.length())); + return null; + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestingUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestingUtilities.java index d57c785e1..4f8f56ee5 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestingUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestingUtilities.java @@ -71,7 +71,6 @@ import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; public class TestingUtilities extends BaseTestingUtilities { - private static final boolean SHOW_DIFF = true; static public Map fcontexts; @@ -178,346 +177,5 @@ public class TestingUtilities extends BaseTestingUtilities { throw new Error("FHIR US directory not configured"); } - public static String checkXMLIsSame(InputStream f1, InputStream f2) throws Exception { - String result = compareXml(f1, f2); - return result; - } - public static String checkXMLIsSame(String f1, String f2) throws Exception { - String result = compareXml(f1, f2); - if (result != null && SHOW_DIFF) { - String diff = ToolGlobalSettings.hasComparePath() ? ToolGlobalSettings.getComparePath() : Utilities.path(System.getenv("ProgramFiles"), "WinMerge", "WinMergeU.exe"); - if (new File(diff).exists() || Utilities.isToken(diff)) { - List command = new ArrayList(); - Process p = Runtime.getRuntime().exec(new String[]{diff, f1, f2}); - } - } - return result; - } - - private static String compareXml(InputStream f1, InputStream f2) throws Exception { - return compareElements("", loadXml(f1).getDocumentElement(), loadXml(f2).getDocumentElement()); - } - - private static String compareXml(String f1, String f2) throws Exception { - return compareElements("", loadXml(f1).getDocumentElement(), loadXml(f2).getDocumentElement()); - } - - private static String compareElements(String path, Element e1, Element e2) { - if (!namespacesMatch(e1.getNamespaceURI(), e2.getNamespaceURI())) - return "Namespaces differ at " + path + ": " + e1.getNamespaceURI() + "/" + e2.getNamespaceURI(); - if (!e1.getLocalName().equals(e2.getLocalName())) - return "Names differ at " + path + ": " + e1.getLocalName() + "/" + e2.getLocalName(); - path = path + "/" + e1.getLocalName(); - String s = compareAttributes(path, e1.getAttributes(), e2.getAttributes()); - if (!Utilities.noString(s)) - return s; - s = compareAttributes(path, e2.getAttributes(), e1.getAttributes()); - if (!Utilities.noString(s)) - return s; - - Node c1 = e1.getFirstChild(); - Node c2 = e2.getFirstChild(); - c1 = skipBlankText(c1); - c2 = skipBlankText(c2); - while (c1 != null && c2 != null) { - if (c1.getNodeType() != c2.getNodeType()) - return "node type mismatch in children of " + path + ": " + Integer.toString(e1.getNodeType()) + "/" + Integer.toString(e2.getNodeType()); - if (c1.getNodeType() == Node.TEXT_NODE) { - if (!normalise(c1.getTextContent()).equals(normalise(c2.getTextContent()))) - return "Text differs at " + path + ": " + normalise(c1.getTextContent()) + "/" + normalise(c2.getTextContent()); - } else if (c1.getNodeType() == Node.ELEMENT_NODE) { - s = compareElements(path, (Element) c1, (Element) c2); - if (!Utilities.noString(s)) - return s; - } - - c1 = skipBlankText(c1.getNextSibling()); - c2 = skipBlankText(c2.getNextSibling()); - } - if (c1 != null) - return "node mismatch - more nodes in source in children of " + path; - if (c2 != null) - return "node mismatch - more nodes in target in children of " + path; - return null; - } - - private static boolean namespacesMatch(String ns1, String ns2) { - return ns1 == null ? ns2 == null : ns1.equals(ns2); - } - - private static Object normalise(String text) { - String result = text.trim().replace('\r', ' ').replace('\n', ' ').replace('\t', ' '); - while (result.contains(" ")) - result = result.replace(" ", " "); - return result; - } - - private static String compareAttributes(String path, NamedNodeMap src, NamedNodeMap tgt) { - for (int i = 0; i < src.getLength(); i++) { - - Node sa = src.item(i); - String sn = sa.getNodeName(); - if (!(sn.equals("xmlns") || sn.startsWith("xmlns:"))) { - Node ta = tgt.getNamedItem(sn); - if (ta == null) - return "Attributes differ at " + path + ": missing attribute " + sn; - if (!normalise(sa.getTextContent()).equals(normalise(ta.getTextContent()))) { - byte[] b1 = unBase64(sa.getTextContent()); - byte[] b2 = unBase64(ta.getTextContent()); - if (!sameBytes(b1, b2)) - return "Attributes differ at " + path + ": value " + normalise(sa.getTextContent()) + "/" + normalise(ta.getTextContent()); - } - } - } - return null; - } - - private static boolean sameBytes(byte[] b1, byte[] b2) { - if (b1.length == 0 || b2.length == 0) - return false; - if (b1.length != b2.length) - return false; - for (int i = 0; i < b1.length; i++) - if (b1[i] != b2[i]) - return false; - return true; - } - - private static byte[] unBase64(String text) { - return Base64.decodeBase64(text); - } - - private static Node skipBlankText(Node node) { - while (node != null && (((node.getNodeType() == Node.TEXT_NODE) && Utilities.isWhitespace(node.getTextContent())) || (node.getNodeType() == Node.COMMENT_NODE))) - node = node.getNextSibling(); - return node; - } - - private static Document loadXml(String fn) throws Exception { - return loadXml(new FileInputStream(fn)); - } - - private static Document loadXml(InputStream fn) throws Exception { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - factory.setFeature("http://xml.org/sax/features/external-general-entities", false); - factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - factory.setXIncludeAware(false); - factory.setExpandEntityReferences(false); - - factory.setNamespaceAware(true); - DocumentBuilder builder = factory.newDocumentBuilder(); - return builder.parse(fn); - } - - public static String checkJsonSrcIsSame(String s1, String s2) throws JsonSyntaxException, FileNotFoundException, IOException { - return checkJsonSrcIsSame(s1, s2, true); - } - - public static String checkJsonSrcIsSame(String s1, String s2, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException { - String result = compareJsonSrc(s1, s2); - if (result != null && SHOW_DIFF && showDiff) { - String diff = null; - if (System.getProperty("os.name").contains("Linux")) - diff = Utilities.path("/", "usr", "bin", "meld"); - else { - if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge"), "\\WinMergeU.exe", null)) - diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe"); - else if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld"), "\\Meld.exe", null)) - diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld", "Meld.exe"); - } - if (diff == null || diff.isEmpty()) - return result; - - List command = new ArrayList(); - String f1 = Utilities.path("[tmp]", "input" + s1.hashCode() + ".json"); - String f2 = Utilities.path("[tmp]", "output" + s2.hashCode() + ".json"); - TextFile.stringToFile(s1, f1); - TextFile.stringToFile(s2, f2); - command.add(diff); - if (diff.toLowerCase().contains("meld")) - command.add("--newtab"); - command.add(f1); - command.add(f2); - - ProcessBuilder builder = new ProcessBuilder(command); - builder.directory(new CSFile(Utilities.path("[tmp]"))); - builder.start(); - - } - return result; - } - - public static String checkJsonIsSame(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException { - String result = compareJson(f1, f2); - if (result != null && SHOW_DIFF) { - String diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe"); - List command = new ArrayList(); - command.add("\"" + diff + "\" \"" + f1 + "\" \"" + f2 + "\""); - - ProcessBuilder builder = new ProcessBuilder(command); - builder.directory(new CSFile("c:\\temp")); - builder.start(); - - } - return result; - } - - private static String compareJsonSrc(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException { - JsonObject o1 = (JsonObject) new com.google.gson.JsonParser().parse(f1); - JsonObject o2 = (JsonObject) new com.google.gson.JsonParser().parse(f2); - return compareObjects("", o1, o2); - } - - private static String compareJson(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException { - JsonObject o1 = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(f1)); - JsonObject o2 = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(f2)); - return compareObjects("", o1, o2); - } - - private static String compareObjects(String path, JsonObject o1, JsonObject o2) { - for (Map.Entry en : o1.entrySet()) { - String n = en.getKey(); - if (!n.equals("fhir_comments")) { - if (o2.has(n)) { - String s = compareNodes(path + '.' + n, en.getValue(), o2.get(n)); - if (!Utilities.noString(s)) - return s; - } else - return "properties differ at " + path + ": missing property " + n; - } - } - for (Map.Entry en : o2.entrySet()) { - String n = en.getKey(); - if (!n.equals("fhir_comments")) { - if (!o1.has(n)) - return "properties differ at " + path + ": missing property " + n; - } - } - return null; - } - - private static String compareNodes(String path, JsonElement n1, JsonElement n2) { - if (n1.getClass() != n2.getClass()) - return "properties differ at " + path + ": type " + n1.getClass().getName() + "/" + n2.getClass().getName(); - else if (n1 instanceof JsonPrimitive) { - JsonPrimitive p1 = (JsonPrimitive) n1; - JsonPrimitive p2 = (JsonPrimitive) n2; - if (p1.isBoolean() && p2.isBoolean()) { - if (p1.getAsBoolean() != p2.getAsBoolean()) - return "boolean property values differ at " + path + ": type " + p1.getAsString() + "/" + p2.getAsString(); - } else if (p1.isString() && p2.isString()) { - String s1 = p1.getAsString(); - String s2 = p2.getAsString(); - if (!(s1.contains(" command = new ArrayList(); - String f1 = Utilities.path("[tmp]", "input" + s1.hashCode() + ".json"); - String f2 = Utilities.path("[tmp]", "output" + s2.hashCode() + ".json"); - TextFile.stringToFile(s1, f1); - TextFile.stringToFile(s2, f2); - command.add(diff); - if (diff.toLowerCase().contains("meld")) - command.add("--newtab"); - command.add(f1); - command.add(f2); - - ProcessBuilder builder = new ProcessBuilder(command); - builder.directory(new CSFile(Utilities.path("[tmp]"))); - builder.start(); - - } - return result; - } - - - private static String compareText(String s1, String s2) { - for (int i = 0; i < Integer.min(s1.length(), s2.length()); i++) { - if (s1.charAt(i) != s2.charAt(i)) - return "Strings differ at character " + Integer.toString(i) + ": '" + s1.charAt(i) + "' vs '" + s2.charAt(i) + "'"; - } - if (s1.length() != s2.length()) - return "Strings differ in length: " + Integer.toString(s1.length()) + " vs " + Integer.toString(s2.length()) + " but match to the end of the shortest"; - return null; - } - - public static String tempFile(String folder, String name) throws IOException { - String tmp = tempFolder(folder); - return Utilities.path(tmp, name); - } - - public static String tempFolder(String name) throws IOException { - File tmp = new File("C:\\temp"); - if (tmp.exists() && tmp.isDirectory()) { - String path = Utilities.path("C:\\temp", name); - Utilities.createDirectory(path); - return path; - } else if (ToolGlobalSettings.hasTempPath()) { - return ToolGlobalSettings.getTempPath(); - } else if (new File("/tmp").exists()) { - String path = Utilities.path("/tmp", name); - Utilities.createDirectory(path); - return path; - } else { - String path = Utilities.path(System.getProperty("java.io.tmpdir"), name); - Utilities.createDirectory(path); - return path; - } - } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/model/Base64BinaryTypeTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/model/Base64BinaryTypeTest.java index 36a7aa96a..3f5a164b3 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/model/Base64BinaryTypeTest.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/model/Base64BinaryTypeTest.java @@ -2,16 +2,20 @@ package org.hl7.fhir.r5.model; import static org.junit.jupiter.api.Assertions.assertThrows; +import org.apache.commons.codec.binary.Base64; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import ca.uhn.fhir.parser.DataFormatException; +import java.nio.charset.StandardCharsets; + class Base64BinaryTypeTest { static final String NON_BASE_64 = "Picard was the best starship captain."; static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; + static final byte[] VALID_BASE_64_BYTES = Base64.decodeBase64(VALID_BASE_64.getBytes(ca.uhn.fhir.rest.api.Constants.CHARSET_UTF8)); @Test @DisplayName("Passing a non Base64 encoded String to constructor causes exception.") public void testNonBase64String() { @@ -55,6 +59,15 @@ class Base64BinaryTypeTest { Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); } + @Test + @DisplayName("Valid Base64 String creates non-null instance with non-null bytes.") + public void testValidBytes() { + Base64BinaryType b64 = new Base64BinaryType(VALID_BASE_64_BYTES); + Assertions.assertNotNull(b64); + Assertions.assertNotNull(b64.getValue()); + Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); + } + @Test @DisplayName("Valid Base64 String creates non-null instance with non-null values.") public void testValidSetValueAsString() { diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/GraphQLEngineTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/GraphQLEngineTests.java index fa20643d6..ceda2b032 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/GraphQLEngineTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/GraphQLEngineTests.java @@ -20,6 +20,7 @@ import org.hl7.fhir.r5.model.Bundle.BundleLinkComponent; import org.hl7.fhir.r5.model.Bundle.SearchEntryMode; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.test.utils.CompareUtilities; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.GraphQLEngine; import org.hl7.fhir.utilities.TextFile; @@ -88,10 +89,10 @@ public class GraphQLEngineTests implements IGraphQLStorageServices { StringBuilder str = new StringBuilder(); gql.getOutput().setWriteWrapper(false); gql.getOutput().write(str, 0); - IOUtils.copy(TestingUtilities.loadTestResourceStream("r5", "graphql", source), new FileOutputStream(TestingUtilities.tempFile("graphql", source))); - IOUtils.copy(TestingUtilities.loadTestResourceStream("r5", "graphql", output), new FileOutputStream(TestingUtilities.tempFile("graphql", output))); - TextFile.stringToFile(str.toString(), TestingUtilities.tempFile("graphql", output+".out")); - msg = TestingUtilities.checkJsonIsSame(TestingUtilities.tempFile("graphql", output+".out"), TestingUtilities.tempFile("graphql", output)); + IOUtils.copy(CompareUtilities.loadTestResourceStream("r5", "graphql", source), new FileOutputStream(CompareUtilities.tempFile("graphql", source))); + IOUtils.copy(CompareUtilities.loadTestResourceStream("r5", "graphql", output), new FileOutputStream(CompareUtilities.tempFile("graphql", output))); + TextFile.stringToFile(str.toString(), CompareUtilities.tempFile("graphql", output+".out")); + msg = CompareUtilities.checkJsonIsSame(CompareUtilities.tempFile("graphql", output), CompareUtilities.tempFile("graphql", output+".out")); Assertions.assertTrue(Utilities.noString(msg), msg); } else diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java index e3b04f2c7..d31bfae21 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java @@ -22,6 +22,7 @@ import org.hl7.fhir.r5.renderers.utils.ElementWrappers; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ITypeParser; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; +import org.hl7.fhir.r5.test.utils.CompareUtilities; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.utilities.TerminologyServiceOptions; import org.hl7.fhir.utilities.TextFile; @@ -134,11 +135,11 @@ public class NarrativeGenerationTests { XhtmlNode x = RendererFactory.factory(source, rc).build(source); String expected = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".html")); String actual = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER; - String expectedFileName = TestingUtilities.tempFile("narrative", test.getId() + ".expected.html"); - String actualFileName = TestingUtilities.tempFile("narrative", test.getId() + ".actual.html"); + String expectedFileName = CompareUtilities.tempFile("narrative", test.getId() + ".expected.html"); + String actualFileName = CompareUtilities.tempFile("narrative", test.getId() + ".actual.html"); TextFile.stringToFile(expected, expectedFileName); TextFile.stringToFile(actual, actualFileName); - String msg = TestingUtilities.checkXMLIsSame(actualFileName, expectedFileName); + String msg = CompareUtilities.checkXMLIsSame(expectedFileName, actualFileName); Assertions.assertTrue(msg == null, "Output does not match expected: "+msg); if (test.isMeta()) { @@ -147,9 +148,9 @@ public class NarrativeGenerationTests { expected = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html")); actual = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER; - actualFileName = TestingUtilities.tempFile("narrative", test.getId() + "-meta.actual.html"); + actualFileName = CompareUtilities.tempFile("narrative", test.getId() + "-meta.actual.html"); TextFile.stringToFile(actual, actualFileName); - msg = TestingUtilities.checkXMLIsSame(actualFileName, expectedFileName); + msg = CompareUtilities.checkXMLIsSame(expectedFileName, actualFileName); Assertions.assertTrue(msg == null, "Meta output does not match expected: "+msg); } } diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java index eef9b272b..2920e19fd 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java @@ -17,6 +17,7 @@ import org.hl7.fhir.r5.renderers.DataRenderer; import org.hl7.fhir.r5.renderers.RendererFactory; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; +import org.hl7.fhir.r5.test.utils.CompareUtilities; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.EOperationOutcome; import org.hl7.fhir.utilities.Utilities; diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/OpenApiGeneratorTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/OpenApiGeneratorTest.java index f43ede9be..bcebb3622 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/OpenApiGeneratorTest.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/OpenApiGeneratorTest.java @@ -10,6 +10,7 @@ import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.model.CapabilityStatement; import org.hl7.fhir.r5.openapi.OpenApiGenerator; import org.hl7.fhir.r5.openapi.Writer; +import org.hl7.fhir.r5.test.utils.CompareUtilities; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.junit.jupiter.api.Test; diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ParsingTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ParsingTests.java index 40b03a41e..00046e736 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ParsingTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ParsingTests.java @@ -12,6 +12,7 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.test.utils.CompareUtilities; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; @@ -57,7 +58,7 @@ public class ParsingTests { r = new XmlParser().parse(b); b = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeBytes(r); String output = new String(b); - String msg = TestingUtilities.checkJsonSrcIsSame(src, output); + String msg = CompareUtilities.checkJsonSrcIsSame(src, output); Assertions.assertTrue(msg == null, msg); } diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java index fb25a400e..14a766903 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java @@ -24,6 +24,7 @@ import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.RendererFactory; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; +import org.hl7.fhir.r5.test.utils.CompareUtilities; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.EOperationOutcome; import org.junit.jupiter.api.Test; diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/VocabTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/VocabTests.java index 32d8e364b..18e5d428c 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/VocabTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/VocabTests.java @@ -12,6 +12,7 @@ import javax.xml.parsers.ParserConfigurationException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.test.utils.CompareUtilities; import org.hl7.fhir.r5.test.utils.TestPackageLoader; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.JsonParser; @@ -146,11 +147,11 @@ public class VocabTests { outcome.getValueset().getExpansion().setTimestamp(null); String expected = new XmlParser().setOutputStyle(OutputStyle.PRETTY).composeString(targetVS); String actual = new XmlParser().setOutputStyle(OutputStyle.PRETTY).composeString(outcome.getValueset()); - String expectedFileName = TestingUtilities.tempFile("vocab", test.getId() + ".expected.html"); - String actualFileName = TestingUtilities.tempFile("vocab", test.getId() + ".actual.html"); + String expectedFileName = CompareUtilities.tempFile("vocab", test.getId() + ".expected.html"); + String actualFileName = CompareUtilities.tempFile("vocab", test.getId() + ".actual.html"); TextFile.stringToFile(expected, expectedFileName); TextFile.stringToFile(actual, actualFileName); - String msg = TestingUtilities.checkXMLIsSame(actualFileName, expectedFileName); + String msg = CompareUtilities.checkXMLIsSame(expectedFileName, actualFileName); Assertions.assertTrue(msg == null, "Output does not match expected: "+msg); } else { Assertions.fail("Expansion Failed: "+outcome.getError()); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/utils/CompareUtilitiesTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/utils/CompareUtilitiesTest.java new file mode 100644 index 000000000..bda4f0cc0 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/utils/CompareUtilitiesTest.java @@ -0,0 +1,99 @@ +package org.hl7.fhir.r5.test.utils; + +import org.apache.commons.io.IOUtils; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class CompareUtilitiesTest { + + public static final Path ROOT_TEST_PATH = Paths.get("src","test","resources", "testUtilities"); + + public static final Path ROOT_XML_TEST_PATH = ROOT_TEST_PATH.resolve("xml"); + public static final Path ROOT_JSON_TEST_PATH = ROOT_TEST_PATH.resolve("json"); + + + public String getResourceAsString(String path) throws IOException { + InputStream inputStream = new FileInputStream(path); + String contents = IOUtils.toString(inputStream, java.nio.charset.StandardCharsets.UTF_8); + return contents.trim(); + } + + private static Stream getCompareXMLParams() { + return Stream.of( + Arguments.of("expected.xml", "expected.xml", null), + Arguments.of("expected.xml", "actualDiffText.xml", "actualDiffText.xml.error"), + Arguments.of("expected.xml", "actualMissingAttribute.xml", "actualMissingAttribute.xml.error"), + Arguments.of("expected.xml", "actualDiffAttribute.xml", "actualDiffAttribute.xml.error"), + Arguments.of("expected.xml", "actualDiffNodeType.xml", "actualDiffNodeType.xml.error"), + Arguments.of("expected.xml", "actualDiffTextEmpty.xml", "actualDiffTextEmpty.xml.error"), + Arguments.of("expected.xml", "actualExtraNode.xml", "actualExtraNode.xml.error"), + Arguments.of("expected.xml", "actualMissingNode.xml", "actualMissingNode.xml.error"), + Arguments.of("expected.xml", "actualDiffNamespace.xml", "actualDiffNamespace.xml.error"), + Arguments.of("expected.xml", "actualDiffLocalName.xml", "actualDiffLocalName.xml.error") + ); + } + + private static String normalizeNewlines(String input) { + return input.replaceAll("\\r\\n?", "\n"); + } + + @ParameterizedTest + @MethodSource("getCompareXMLParams") + public void testCompareXML(String expectedFileName, String actualFileName, String expectedOutputFileName) throws Exception { + final String expectedXMLPath = ROOT_XML_TEST_PATH.resolve(expectedFileName).toAbsolutePath().toString(); + final String actualXMLPath = ROOT_XML_TEST_PATH.resolve(actualFileName).toAbsolutePath().toString(); + + final String actualOutput = CompareUtilities.checkXMLIsSame(expectedXMLPath, actualXMLPath); + + if (expectedOutputFileName == null) { + assertNull(actualOutput); + } else { + final String expectedOutputPath = ROOT_XML_TEST_PATH.resolve(expectedOutputFileName).toAbsolutePath().toString(); + String expectedOutput = normalizeNewlines(getResourceAsString(expectedOutputPath)); + assertEquals(expectedOutput, normalizeNewlines(actualOutput)); + } + } + + private static Stream getCompareJSONParams() { + return Stream.of( + Arguments.of("expected.json", "expected.json", null), + Arguments.of("expected.json", "actualDiffValue.json", "actualDiffValue.json.error"), + Arguments.of("expected.json", "actualDiffType.json", "actualDiffType.json.error"), + Arguments.of("expected.json", "actualDiffArrayContent.json", "actualDiffArrayContent.json.error"), + Arguments.of("expected.json", "actualDiffBoolean.json", "actualDiffBoolean.json.error"), + Arguments.of("expected.json", "actualMissingProperty.json", "actualMissingProperty.json.error"), + Arguments.of("expected.json", "actualDiffArraySize.json", "actualDiffArraySize.json.error"), + Arguments.of("expected.json", "actualDiffNumber.json", "actualDiffNumber.json.error"), + Arguments.of("expected.json", "actualMissingSubProperty.json", "actualMissingSubProperty.json.error"), + Arguments.of("expected.json", "actualExtraProperty.json", "actualExtraProperty.json.error") + ); + } + + @ParameterizedTest + @MethodSource("getCompareJSONParams") + public void testCompareJSON(String expectedFileName, String actualFileName, String expectedOutputFileName) throws IOException { + final String expectedJSONPath = ROOT_JSON_TEST_PATH.resolve(expectedFileName).toAbsolutePath().toString(); + final String actualJSONPath = ROOT_JSON_TEST_PATH.resolve(actualFileName).toAbsolutePath().toString(); + + final String actualOutput = CompareUtilities.checkJsonSrcIsSame(getResourceAsString(expectedJSONPath), getResourceAsString(actualJSONPath), false); + if (expectedOutputFileName == null) { + assertNull(actualOutput); + } else { + final String expectedOutputPath = ROOT_JSON_TEST_PATH.resolve(expectedOutputFileName).toAbsolutePath().toString(); + String expectedOutput = normalizeNewlines(getResourceAsString(expectedOutputPath)); + assertEquals(expectedOutput, normalizeNewlines(actualOutput)); + } + } +} diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json new file mode 100644 index 000000000..0c4957882 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json @@ -0,0 +1,12 @@ +{ + "expectedString" : "expected value", + "expectedBoolean" : true, + "expectedNumber" : 123, + "expectedArray" : [ + "unexpectedValue 1", + "expectedValue 2" + ], + "property" : { + "subField" : 1 + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json.error new file mode 100644 index 000000000..656af6e10 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArrayContent.json.error @@ -0,0 +1,3 @@ +string property values differ at .expectedArray[0] +Expected :expectedValue 1 +Actual :unexpectedValue 1 \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json new file mode 100644 index 000000000..a40ca377e --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json @@ -0,0 +1,11 @@ +{ + "expectedString" : "expected value", + "expectedBoolean" : true, + "expectedNumber" : 123, + "expectedArray" : [ + "expectedValue 1" + ], + "property" : { + "subField" : 1 + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json.error new file mode 100644 index 000000000..b8aee7189 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffArraySize.json.error @@ -0,0 +1,3 @@ +array properties count differs at .expectedArray +Expected :2 +Actual :1 \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json new file mode 100644 index 000000000..6b5e1d546 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json @@ -0,0 +1,12 @@ +{ + "expectedString" : "expected value", + "expectedBoolean" : false, + "expectedNumber" : 123, + "expectedArray" : [ + "expectedValue 1", + "expectedValue 2" + ], + "property" : { + "subField" : 1 + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json.error new file mode 100644 index 000000000..3f541ed8e --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffBoolean.json.error @@ -0,0 +1,3 @@ +boolean property values differ at .expectedBoolean +Expected :true +Actual :false \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json new file mode 100644 index 000000000..461ef84d2 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json @@ -0,0 +1,12 @@ +{ + "expectedString" : "expected value", + "expectedBoolean" : true, + "expectedNumber" : 789, + "expectedArray" : [ + "expectedValue 1", + "expectedValue 2" + ], + "property" : { + "subField" : 1 + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json.error new file mode 100644 index 000000000..10722946a --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffNumber.json.error @@ -0,0 +1,3 @@ +number property values differ at .expectedNumber +Expected :123 +Actual :789 \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json new file mode 100644 index 000000000..73157cce0 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json @@ -0,0 +1,12 @@ +{ + "expectedString" : 1, + "expectedBoolean" : true, + "expectedNumber" : 123, + "expectedArray" : [ + "expectedValue 1", + "expectedValue 2" + ], + "property" : { + "subField" : 1 + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json.error new file mode 100644 index 000000000..43c2e86b1 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffType.json.error @@ -0,0 +1,3 @@ +property types differ at .expectedString +Expected :expected value +Actual :1 \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json new file mode 100644 index 000000000..7fe228f77 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json @@ -0,0 +1,12 @@ +{ + "expectedString" : "unexpected value", + "expectedBoolean" : true, + "expectedNumber" : 123, + "expectedArray" : [ + "expectedValue 1", + "expectedValue 2" + ], + "property" : { + "subField" : 1 + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json.error new file mode 100644 index 000000000..9e0f9eb92 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualDiffValue.json.error @@ -0,0 +1,3 @@ +string property values differ at .expectedString +Expected :expected value +Actual :unexpected value \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualExtraProperty.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualExtraProperty.json new file mode 100644 index 000000000..66229ac4a --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualExtraProperty.json @@ -0,0 +1,13 @@ +{ + "expectedString" : "expected value", + "expectedBoolean" : true, + "expectedNumber" : 123, + "unexpectedProperty" : "totally unexpected", + "expectedArray" : [ + "expectedValue 1", + "expectedValue 2" + ], + "property" : { + "subField" : 1 + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualExtraProperty.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualExtraProperty.json.error new file mode 100644 index 000000000..dc127601c --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualExtraProperty.json.error @@ -0,0 +1 @@ +properties differ at : missing property unexpectedProperty \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingProperty.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingProperty.json new file mode 100644 index 000000000..6c5805a0a --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingProperty.json @@ -0,0 +1,11 @@ +{ + "expectedString" : "expected value", + "expectedNumber" : 123, + "expectedArray" : [ + "expectedValue 1", + "expectedValue 2" + ], + "property" : { + "subField" : 1 + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingProperty.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingProperty.json.error new file mode 100644 index 000000000..fc9c55552 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingProperty.json.error @@ -0,0 +1 @@ +properties differ at : missing property expectedBoolean \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingSubProperty.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingSubProperty.json new file mode 100644 index 000000000..18abf2cc4 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingSubProperty.json @@ -0,0 +1,12 @@ +{ + "expectedString" : "expected value", + "expectedBoolean" : true, + "expectedNumber" : 123, + "expectedArray" : [ + "expectedValue 1", + "expectedValue 2" + ], + "property" : { + + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingSubProperty.json.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingSubProperty.json.error new file mode 100644 index 000000000..1c0f84b46 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/actualMissingSubProperty.json.error @@ -0,0 +1 @@ +properties differ at .property: missing property subField \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/json/expected.json b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/expected.json new file mode 100644 index 000000000..50ce1f573 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/json/expected.json @@ -0,0 +1,12 @@ +{ + "expectedString" : "expected value", + "expectedBoolean" : true, + "expectedNumber" : 123, + "expectedArray" : [ + "expectedValue 1", + "expectedValue 2" + ], + "property" : { + "subField" : 1 + } +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml new file mode 100644 index 000000000..5c6d7df86 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml @@ -0,0 +1,7 @@ + + + expected + + + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml.error new file mode 100644 index 000000000..c027d4bf6 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffAttribute.xml.error @@ -0,0 +1,3 @@ +Attributes differ at /root/blah +Expected :dummyAtt +Actual :wrongwrongwrong \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml new file mode 100644 index 000000000..b5502b259 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml @@ -0,0 +1,7 @@ + + + expected + + + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml.error new file mode 100644 index 000000000..51e12abf9 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffLocalName.xml.error @@ -0,0 +1,3 @@ +Names differ at /root +Expected :nameSpacedNode +Actual :wrongNameSpacedNode \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml new file mode 100644 index 000000000..f8da5d75c --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml @@ -0,0 +1,7 @@ + + + expected + + + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml.error new file mode 100644 index 000000000..4c5341706 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNamespace.xml.error @@ -0,0 +1,3 @@ +Namespaces differ at /root +Expected :http://www.example.com/FOO +Actual :http://www.example.com/BAR \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml new file mode 100644 index 000000000..899f046fe --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml.error new file mode 100644 index 000000000..3236aba11 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffNodeType.xml.error @@ -0,0 +1,3 @@ +node type mismatch in children of /root/blah +Expected :1 +Actual :1 \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml new file mode 100644 index 000000000..8b376a580 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml @@ -0,0 +1,7 @@ + + + different + + + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml.error new file mode 100644 index 000000000..77e06731b --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffText.xml.error @@ -0,0 +1,3 @@ +Text differs at /root/blah +Expected :expected +Actual :different \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffTextEmpty.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffTextEmpty.xml new file mode 100644 index 000000000..53b52de5f --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffTextEmpty.xml @@ -0,0 +1,7 @@ + + + expected + +not empty + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffTextEmpty.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffTextEmpty.xml.error new file mode 100644 index 000000000..c34c59382 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualDiffTextEmpty.xml.error @@ -0,0 +1 @@ +node mismatch - more nodes in expected in children of /root/emptyNode \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualExtraNode.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualExtraNode.xml new file mode 100644 index 000000000..1331a2348 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualExtraNode.xml @@ -0,0 +1,8 @@ + + + expected + + + + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualExtraNode.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualExtraNode.xml.error new file mode 100644 index 000000000..10ce57cc5 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualExtraNode.xml.error @@ -0,0 +1 @@ +node mismatch - more nodes in expected in children of /root \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingAttribute.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingAttribute.xml new file mode 100644 index 000000000..a714676d0 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingAttribute.xml @@ -0,0 +1,7 @@ + + + expected + + + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingAttribute.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingAttribute.xml.error new file mode 100644 index 000000000..c2e6b400d --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingAttribute.xml.error @@ -0,0 +1 @@ +Attributes differ at /root/blah: missing attribute myAtt \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingNode.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingNode.xml new file mode 100644 index 000000000..9e3a917fb --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingNode.xml @@ -0,0 +1,6 @@ + + + expected + + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingNode.xml.error b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingNode.xml.error new file mode 100644 index 000000000..032ddd553 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/actualMissingNode.xml.error @@ -0,0 +1 @@ +node mismatch - more nodes in actual in children of /root \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/expected.xml b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/expected.xml new file mode 100644 index 000000000..99f1de9c5 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/resources/testUtilities/xml/expected.xml @@ -0,0 +1,7 @@ + + + expected + + + + \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/tests/BaseTestingUtilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/tests/BaseTestingUtilities.java index 3619d213b..a86d06aeb 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/tests/BaseTestingUtilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/tests/BaseTestingUtilities.java @@ -101,4 +101,28 @@ public class BaseTestingUtilities { } } } + + public static String tempFile(String folder, String name) throws IOException { + String tmp = tempFolder(folder); + return Utilities.path(tmp, name); + } + + public static String tempFolder(String name) throws IOException { + File tmp = new File("C:\\temp"); + if (tmp.exists() && tmp.isDirectory()) { + String path = Utilities.path("C:\\temp", name); + Utilities.createDirectory(path); + return path; + } else if (ToolGlobalSettings.hasTempPath()) { + return ToolGlobalSettings.getTempPath(); + } else if (new File("/tmp").exists()) { + String path = Utilities.path("/tmp", name); + Utilities.createDirectory(path); + return path; + } else { + String path = Utilities.path(System.getProperty("java.io.tmpdir"), name); + Utilities.createDirectory(path); + return path; + } + } } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java index 562dbcb96..2641840bb 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java @@ -69,11 +69,37 @@ public class IgLoader { this.isDebug = isDebug; } + /** + * + * @param igs + * @param binaries + * @param src Source of the IG + * + * @param recursive + * @throws IOException + * @throws FHIRException + * + * @see IgLoader#loadIgSource(String, boolean, boolean) loadIgSource for detailed description of the src parameter + */ public void loadIg(List igs, Map binaries, String src, boolean recursive) throws IOException, FHIRException { - NpmPackage npm = src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX_OPT) && !new File(src).exists() ? getPackageCacheManager().loadPackage(src, null) : null; + + final String explicitFhirVersion; + final String srcPackage; + if (src.startsWith("[") && src.indexOf(']', 1) > 1) { + explicitFhirVersion = src.substring(1,src.indexOf(']', 1)); + srcPackage = src.substring(src.indexOf(']',1) + 1); + if (VersionUtilities.isSupportedVersion(explicitFhirVersion)) { + throw new FHIRException("Unsupported FHIR Version: " + explicitFhirVersion + " valid versions are " + VersionUtilities.listSupportedVersions()); + } + } else { + explicitFhirVersion = null; + srcPackage = src; + } + + NpmPackage npm = srcPackage.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX_OPT) && !new File(srcPackage).exists() ? getPackageCacheManager().loadPackage(srcPackage, null) : null; if (npm != null) { for (String s : npm.dependencies()) { if (!getContext().getLoadedPackages().contains(s)) { @@ -82,17 +108,17 @@ public class IgLoader { } } } - System.out.print(" Load " + src); - if (!src.contains("#")) { + System.out.print(" Load " + srcPackage); + if (!srcPackage.contains("#")) { System.out.print("#" + npm.version()); } int count = getContext().loadFromPackage(npm, ValidatorUtils.loaderForVersion(npm.fhirVersion())); System.out.println(" - " + count + " resources (" + getContext().clock().milestone() + ")"); } else { - System.out.print(" Load " + src); + System.out.print(" Load " + srcPackage); String canonical = null; int count = 0; - Map source = loadIgSource(src, recursive, true); + Map source = loadIgSource(srcPackage, recursive, true); String version = Constants.VERSION; if (getVersion() != null) { version = getVersion(); @@ -100,6 +126,10 @@ public class IgLoader { if (source.containsKey("version.info")) { version = readInfoVersion(source.get("version.info")); } + if (explicitFhirVersion != null) { + version = explicitFhirVersion; + } + for (Map.Entry t : source.entrySet()) { String fn = t.getKey(); if (!exemptFile(fn)) { @@ -126,6 +156,18 @@ public class IgLoader { } } + /** + * + * @param source + * @param opName + * @param asIg + * @return + * @throws FHIRException + * @throws IOException + * + * * @see IgLoader#loadIgSource(String, boolean, boolean) loadIgSource for detailed description of the src parameter + */ + public Content loadContent(String source, String opName, boolean asIg) throws FHIRException, IOException { Map s = loadIgSource(source, false, asIg); Content res = new Content(); @@ -150,18 +192,23 @@ public class IgLoader { } /** - * explore should be true if we're trying to load an -ig parameter, and false if we're loading source * + * @param src can be one of the following: + *
- a canonical url for an ig - this will be converted to a package id and loaded into the cache + *
- a package id for an ig - this will be loaded into the cache + *
- a direct reference to a package ("package.tgz") - this will be extracted by the cache manager, but not put in the cache + *
- a folder containing resources - these will be loaded directly + * @param recursive if true and src resolves to a folder, recursively find and load IgSources from that directory + * @param explore should be true if we're trying to load an -ig parameter, and false if we're loading source + * + * @return + * @throws FHIRException * @throws IOException - **/ + */ public Map loadIgSource(String src, boolean recursive, boolean explore) throws FHIRException, IOException { - // src can be one of the following: - // - a canonical url for an ig - this will be converted to a package id and loaded into the cache - // - a package id for an ig - this will be loaded into the cache - // - a direct reference to a package ("package.tgz") - this will be extracted by the cache manager, but not put in the cache - // - a folder containing resources - these will be loaded directly + // if (Common.isNetworkPath(src)) { String v = null; if (src.contains("|")) { @@ -627,7 +674,7 @@ public class IgLoader { return Utilities.existsInList(fn, EXEMPT_FILES); } - private Resource loadFileWithErrorChecking(String version, Map.Entry t, String fn) { + protected Resource loadFileWithErrorChecking(String version, Map.Entry t, String fn) { log("* load file: " + fn); Resource r = null; try { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 8dd098234..12e3adba1 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -266,6 +266,19 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP } } + /** + * + * @param src + * @param recursive + * @param terminologyCachePath + * @param userAgent + * @param tt + * @param loggingService + * @throws FHIRException + * @throws IOException + * + * @see IgLoader#loadIgSource(String, boolean, boolean) loadIgSource for detailed description of the src parameter + */ private void loadCoreDefinitions(String src, boolean recursive, String terminologyCachePath, String userAgent, TimeTracker tt, IWorkerContext.ILoggingService loggingService) throws FHIRException, IOException { NpmPackage npm = getPcm().loadPackage(src, null); if (npm != null) { diff --git a/org.hl7.fhir.validation/src/main/resources/help.txt b/org.hl7.fhir.validation/src/main/resources/help.txt index 8a0445342..673e2e7a8 100644 --- a/org.hl7.fhir.validation/src/main/resources/help.txt +++ b/org.hl7.fhir.validation/src/main/resources/help.txt @@ -4,98 +4,114 @@ The validation tool compares a resource against the base definitions and any profiles declared in the resource (Resource.meta.profile) or specified on the command line -The FHIR validation tool validates a FHIR resource or bundle. -Schema and schematron checking is performed, then some additional checks are performed. +The FHIR validation tool validates a FHIR resource or bundle. Schema and +schematron checking is performed, then some additional checks are performed. + * XML & Json (FHIR versions {{XML_AND_JSON_FHIR_VERSIONS}}) * Turtle (FHIR versions {{TURTLE_FHIR_VERSIONS}}) -If requested, instances will also be verified against the appropriate schema -W3C XML Schema, JSON schema or ShEx, as appropriate +If requested, instances will also be verified against the appropriate schema W3C +XML Schema, JSON schema or ShEx, as appropriate Usage: java -jar [validator].jar (parameters) The following parameters are supported: -[source]: a file, url, directory or pattern for resources to validate. At - least one source must be declared. If there is more than one source or if - the source is other than a single file or url and the output parameter is +[source]: a file, url, directory or pattern for resources to validate. + At least one source must be declared. If there is more than one source or + if the source is other than a single file or url and the output parameter is used, results will be provided as a Bundle. - Patterns are limited to a directory followed by a filename with an embedded - asterisk. E.g. foo*-examples.xml or someresource.*, etc. --version [ver]: The FHIR version to use. This can only appear once. - valid values {{FHIR_MAJOR_VERSIONS}} or {{FHIR_MINOR_VERSIONS}} - Default value is {{FHIR_CURRENT_VERSION}} --ig [package|file|folder|url]: an IG or profile definition to load. Can be - the URL of an implementation guide or a package ([id]-[ver]) for - a built implementation guide or a local folder that contains a - set of conformance resources. - If you would like to load the latest unreleased version of the implementation guide or package, - please define the version as '#current'. If no version is provided, the latest version - in the package cache will be used, or if no such cached package is available, the - PackageCacheManager will load the latest from the the online package repo. - No default value. This parameter can appear any number of times + Patterns are limited to a directory followed by a filename with an + embedded asterisk. E.g. foo*-examples.xml or someresource.*, etc. +-version [ver]: The FHIR version to use. + This can only appear once. + valid values {{FHIR_MAJOR_VERSIONS}} or {{FHIR_MINOR_VERSIONS}} + Default value is {{FHIR_CURRENT_VERSION}} +-ig [package|file|folder|url]: an IG or profile definition to load. + Can be the URL of an implementation guide or a package ([id]-[ver]) for a + built implementation guide or a local folder that contains a set of + conformance resources. + If you would like to load the latest unreleased version of the + implementation guide or package, please define the version as '#current'. + If no version is provided, the latest version in the package cache will + be used, or if no such cached package is available, the PackageCacheManager + will load the latest from the the online package repo. + If you want the implementation guide to be loaded for a specific version + of FHIR, you can prefix the IG with the appropriate version in square + brackets ([[fhirVer]][id]-[igVer]). + No default value. This parameter can appear any number of times -tx [url]: the [base] url of a FHIR terminology service - Default value is http://tx.fhir.org. This parameter can appear once - To run without terminology value, specific n/a as the URL + Default value is http://tx.fhir.org. This parameter can appear once + To run without terminology value, specific n/a as the URL -txLog [file]: Produce a log of the terminology server operations in [file] - Default value is not to produce a log + Default value is not to produce a log -profile [url]: the canonical URL to validate against (same as if it was - specified in Resource.meta.profile). If no profile is specified, the - resource is validated against the base specification. This parameter - can appear any number of times. - Note: the profile (and it's dependencies) have to be made available + specified in Resource.meta.profile). + If no profile is specified, the resource is validated against the base + specification. This parameter can appear any number of times. + Note: the profile (and it's dependencies) have to be made available through one of the -ig parameters. Note that package dependencies will automatically be resolved -showReferenceMessages - Includes validation messages resulting from validating target resources + Includes validation messages resulting from validating target resources against profiles defined on a reference. This increases the volume of validation messages, but may allow easier debugging. If not specified, then only a high-level message indicating that the referenced item wasn't valid against the listed profile(s) will be provided. --questionnaire mode: what to do with when validating QuestionnaireResponse resources - none (default): just ignore the questionnaire reference - required: check that the QuestionnaireResponse has a questionnaire and validate against it - check: if the QuestionnaireResponse has a questionnaire, validate against it - The questionnaire must be loaded using the -ig parameter -the location of a questionnaire. If provided, then the validator will validate - any QuestionnaireResponse that claims to match the Questionnaire against it - no default value. This parameter can appear any number of times +-questionnaire mode: what to do when validating QuestionnaireResponse resources + * none (default): just ignore the questionnaire reference + * required: check that the QuestionnaireResponse has a questionnaire and + validate against it + * check: if the QuestionnaireResponse has a questionnaire, validate + against it + The questionnaire must be loaded using the -ig parameter + which specifies the location of a questionnaire. If provided, then the + validator will validate any QuestionnaireResponse that claims to match the + Questionnaire against it no default value. This parameter can appear any + number of times -output [file]: a filename for the results (OperationOutcome) - Default: results are sent to the std out. + Default: results are sent to the std out. -debug - Produce additional information about the loading/validation process + Produce additional information about the loading/validation process -recurse - Look in subfolders when -ig refers to a folder + Look in subfolders when -ig refers to a folder -locale - Specifies the locale/language of the validation result messages (eg.: de-DE + Specifies the locale/language of the validation result messages (eg.: + de-DE -sct Specify the edition of SNOMED CT to use. Valid Choices: intl | us | uk | au | nl | ca | se | dk | es - tx.fhir.org only supports a subset. To add to this list or tx.fhir.org - ask on https://chat.fhir.org/#narrow/stream/179202-terminology + tx.fhir.org only supports a subset. To add to this list or tx.fhir.org ask + on https://chat.fhir.org/#narrow/stream/179202-terminology -native: use schema for validation as well * XML: w3c schema+schematron * JSON: json.schema * RDF: SHEX Default: false --language: [lang] - The language to use when validating coding displays - same value as for xml:lang - Not used if the resource specifies language - Default: no specified language --strictExtensions: If present, treat extensions not defined within the specified FHIR version and any - referenced implementation guides or profiles as errors. (Default is to only raise information messages.) --hintAboutNonMustSupport: If present, raise hints if the instance contains data elements that are not - marked as mustSupport=true. Useful to identify elements included that may be ignored by recipients --assumeValidRestReferences: If present, assume that URLs that reference resources follow the RESTful URI pattern - and it is safe to infer the type from the URL --security-checks: If present, check that string content doesn't include any html-like tags that might create - problems downstream (though all external input must always be santized by escaping for either html or sql) +-language: [lang] The language to use when validating coding displays - same + value as for xml:lang + Not used if the resource specifies language + Default: no specified language +-strictExtensions: If present, treat extensions not defined within the specified + FHIR version and any referenced implementation guides or profiles as errors. + (Default is to only raise information messages.) +-hintAboutNonMustSupport: If present, raise hints if the instance contains data + elements that are not marked as mustSupport=true. Useful to identify + elements included that may be ignored by recipients +-assumeValidRestReferences: If present, assume that URLs that reference + resources follow the RESTful URI pattern and it is safe to infer the type + from the URL +-security-checks: If present, check that string content doesn't include any html + -like tags that might create problems downstream (though all external input + must always be santized by escaping for either html or sql) -The validator also supports the param -proxy=[address]:[port] for if you use a proxy +The validator also supports the param -proxy=[address]:[port] for if you use a +proxy Parameters can appear in any order -Alternatively, you can use the validator to execute a transformation as described by a structure map. -To do this, you must provide some additional parameters: +Alternatively, you can use the validator to execute a transformation as +described by a structure map. To do this, you must provide some additional +parameters: -transform [map] @@ -103,34 +119,39 @@ To do this, you must provide some additional parameters: Any other dependency maps have to be loaded through an -ig reference --transform uses the parameters -defn, -txserver, -ig (at least one with the map files), and -output +-transform uses the parameters -defn, -txserver, -ig (at least one with the map +files), and -output Alternatively, you can use the validator to generate narrative for a resource. To do this, you must provide a specific parameter: -narrative --narrative requires the parameters -defn, -txserver, -source, and -output. ig and profile may be used +-narrative requires the parameters -defn, -txserver, -source, and -output. ig +and profile may be used Alternatively, you can use the validator to convert a resource or logical model. To do this, you must provide a specific parameter: -convert --convert requires the parameters -source and -output. ig may be used to provide a logical model +-convert requires the parameters -source and -output. ig may be used to provide +a logical model -Alternatively, you can use the validator to evaluate a FHIRPath expression on a resource or logical model. -To do this, you must provide a specific parameter: +Alternatively, you can use the validator to evaluate a FHIRPath expression on a +resource or logical model. To do this, you must provide a specific parameter: -fhirpath [FHIRPath] * [FHIRPath] the FHIRPath expression to evaluate --fhirpath requires the parameters -source. ig may be used to provide a logical model +-fhirpath requires the parameters -source. ig may be used to provide a logical +model Finally, you can use the validator to generate a snapshot for a profile. To do this, you must provide a specific parameter: -snapshot --snapshot requires the parameters -defn, -txserver, -source, and -output. ig may be used to provide necessary base profiles \ No newline at end of file +-snapshot requires the parameters -defn, -txserver, -source, and -output. ig may +be used to provide necessary base profiles \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java index 5a113bb16..101050f17 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java @@ -18,6 +18,7 @@ import org.hl7.fhir.r5.formats.IParser; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureMap; +import org.hl7.fhir.r5.test.utils.CompareUtilities; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; import org.hl7.fhir.utilities.TextFile; @@ -103,11 +104,11 @@ public class FHIRMappingLanguageTests { assertTrue(e.getMessage(), false); } if (output.endsWith("json")) { - msg = TestingUtilities.checkJsonSrcIsSame(s.toString(), outputJson); + msg = CompareUtilities.checkJsonSrcIsSame(s.toString(), outputJson); } else { TextFile.bytesToFile(s.toByteArray(), fileOutputRes); TextFile.bytesToFile(outputJson.getBytes(), fileOutputResOrig); - msg = TestingUtilities.checkXMLIsSame(new FileInputStream(fileOutputRes), new FileInputStream(fileOutputResOrig)); + msg = CompareUtilities.checkXMLIsSame(new FileInputStream(fileOutputResOrig), new FileInputStream(fileOutputRes)); } if (!Utilities.noString(msg)) { System.out.print(s.toString()); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/IgLoaderTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/IgLoaderTests.java new file mode 100644 index 000000000..f7d4203f3 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/IgLoaderTests.java @@ -0,0 +1,98 @@ +package org.hl7.fhir.validation; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.context.SimpleWorkerContext; +import org.hl7.fhir.r5.model.ImplementationGuide; +import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class IgLoaderTests { + + final static String DUMMY_PATH = Paths.get("src","test","resources", "igLoad", "my-dummy-ig.json").toAbsolutePath().toString(); + final static String DUMMY_FOO_PATH = Paths.get("src","test","resources", "igLoad", "my-dummy-ig.json").toAbsolutePath().toString(); + + @Mock + FilesystemPackageCacheManager filesystemPackageCacheManager; + + @Mock + SimpleWorkerContext simpleWorkerContext; + + @Mock + org.hl7.fhir.utilities.TimeTracker timeTracker; + + private static Stream getTestIgLoadParams() { + return Stream.of( + Arguments.of(DUMMY_PATH, DUMMY_PATH, "4.0.1"), + Arguments.of("[3.0.1]" + DUMMY_PATH, DUMMY_PATH, "3.0.1"), + Arguments.of("[" + DUMMY_PATH, "[" + DUMMY_PATH, "4.0.1"), + Arguments.of(DUMMY_FOO_PATH, DUMMY_FOO_PATH, "4.0.1"), + Arguments.of("[2.0.1]"+DUMMY_FOO_PATH, DUMMY_FOO_PATH, "2.0.1") + ); + } + + @ParameterizedTest + @MethodSource("getTestIgLoadParams") + public void testIgLoad(String packageString, String expectedPackageName, String expectedFhirVersion) throws IOException { + + final byte[] dummyBytes = {}; + final String dummyKey = "dummyKey"; + + final Map dummyMap = new HashMap<>(); + dummyMap.put(dummyKey, dummyBytes); + + + IgLoader igLoader = Mockito.spy(new IgLoader( + filesystemPackageCacheManager, + simpleWorkerContext, + "4.0.1" + )); + + doReturn(dummyMap).when(igLoader).loadIgSource(expectedPackageName, false, true); + doReturn(timeTracker).when(simpleWorkerContext).clock(); + + List igs = Collections.emptyList(); + igLoader.loadIg( igs, + Collections.emptyMap(), + packageString, + false); + + Mockito.verify(igLoader, times(1)).loadResourceByVersion(expectedFhirVersion, dummyBytes, dummyKey); + } + + @Test + public void testFailIfInvalidFHIRVersion() { + Exception exception = assertThrows(FHIRException.class, () -> { + IgLoader igLoader = Mockito.spy(new IgLoader( + filesystemPackageCacheManager, + simpleWorkerContext, + "4.0.1" + )); + + List igs = Collections.emptyList(); + igLoader.loadIg(igs, + Collections.emptyMap(), + "[0.1.2]" + DUMMY_PATH, + false); + }); + } +} diff --git a/org.hl7.fhir.validation/src/test/resources/igLoad/my-dummy-ig.json b/org.hl7.fhir.validation/src/test/resources/igLoad/my-dummy-ig.json new file mode 100644 index 000000000..e69de29bb diff --git a/org.hl7.fhir.validation/src/test/resources/igLoad/my-dummy-ig[foo].json b/org.hl7.fhir.validation/src/test/resources/igLoad/my-dummy-ig[foo].json new file mode 100644 index 000000000..e69de29bb