This commit is contained in:
Grahame Grieve 2022-04-12 09:12:35 +10:00
commit b9902833c5
72 changed files with 1547 additions and 473 deletions

View File

@ -8,4 +8,5 @@
## Other code changes ## Other code changes
* no changes * Improved output for unit test comparisons

View File

@ -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);
}
}

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<AuditEvent xmlns="http://hl7.org/fhir">
<id value="example"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">Application Start for under service login &quot;Grahame&quot; (id: Grahame's Test HL7Connect)</div>
</text>
<type>
<system value="http://dicom.nema.org/resources/ontology/DCM"/>
<code value="110100"/>
<display value="Application Activity"/>
</type>
<subtype>
<system value="http://dicom.nema.org/resources/ontology/DCM"/>
<code value="110120"/>
<display value="Application Start"/>
</subtype>
<action value="E"/>
<recorded value="2012-10-25T22:04:27+11:00"/>
<outcome value="0"/>
<agent>
<type> <coding> <system value="http://terminology.hl7.org/CodeSystem/extra-security-role-type"/> <code value="humanuser"/> <display value="human user"/> </coding> </type>
<role>
<text value="Service User (Logon)"/>
</role>
<who>
<identifier>
<value value="Grahame"/>
</identifier>
</who>
<requestor value="false"/>
<network>
<address value="127.0.0.1"/>
<type value="2"/>
</network>
</agent>
<agent> <!-- Source active participant, the software making the . AlternativeUserId - Process ID
-->
<type> <coding> <system value="http://dicom.nema.org/resources/ontology/DCM"/> <code value="110153"/> <display value="Source Role ID"/> </coding> </type>
<who>
<identifier>
<system value="urn:oid:2.16.840.1.113883.4.2"/>
<value value="2.16.840.1.113883.4.2"/>
</identifier>
</who>
<altId value="6580"/>
<requestor value="false"/>
<network> <address value="Workstation1.ehr.familyclinic.com"/> <type value="1"/> </network>
</agent>
<source>
<site value="Development"/>
<observer>
<display value="Grahame's Laptop"/>
</observer>
<type>
<system value="http://dicom.nema.org/resources/ontology/DCM"/>
<code value="110122"/>
<display value="Login"/>
</type>
</source>
<entity>
<what> <identifier>
<type>
<coding>
<system value="http://terminology.hl7.org/CodeSystem/v2-0203"/>
<code value="SNO"/>
</coding>
<text value="Dell Serial Number"/>
</type>
<value value="ABCDEF"/>
</identifier>
</what>
<type>
<system value="http://terminology.hl7.org/CodeSystem/audit-entity-type"/>
<code value="4"/>
<display value="Other"/>
</type>
<role>
<system value="http://terminology.hl7.org/CodeSystem/object-role"/>
<code value="4"/>
<display value="Domain Resource"/>
</role>
<lifecycle>
<system value="http://terminology.hl7.org/CodeSystem/dicom-audit-lifecycle"/>
<code value="6"/>
<display value="Access / Use"/>
</lifecycle>
<name value="Grahame's Laptop"/>
<query value="dGhpcyBpcyB2YWxpZCBiYXNlNjQ="/>
</entity>
</AuditEvent>

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<AuditEvent xmlns="http://hl7.org/fhir">
<id value="example"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">Application Start for under service login &quot;Grahame&quot; (id: Grahame's Test HL7Connect)</div>
</text>
<type>
<system value="http://dicom.nema.org/resources/ontology/DCM"/>
<code value="110100"/>
<display value="Application Activity"/>
</type>
<subtype>
<system value="http://dicom.nema.org/resources/ontology/DCM"/>
<code value="110120"/>
<display value="Application Start"/>
</subtype>
<action value="E"/>
<recorded value="2012-10-25T22:04:27+11:00"/>
<outcome value="0"/>
<agent>
<type> <coding> <system value="http://terminology.hl7.org/CodeSystem/extra-security-role-type"/> <code value="humanuser"/> <display value="human user"/> </coding> </type>
<role>
<text value="Service User (Logon)"/>
</role>
<who>
<identifier>
<value value="Grahame"/>
</identifier>
</who>
<requestor value="false"/>
<network>
<address value="127.0.0.1"/>
<type value="2"/>
</network>
</agent>
<agent> <!-- Source active participant, the software making the . AlternativeUserId - Process ID
-->
<type> <coding> <system value="http://dicom.nema.org/resources/ontology/DCM"/> <code value="110153"/> <display value="Source Role ID"/> </coding> </type>
<who>
<identifier>
<system value="urn:oid:2.16.840.1.113883.4.2"/>
<value value="2.16.840.1.113883.4.2"/>
</identifier>
</who>
<altId value="6580"/>
<requestor value="false"/>
<network> <address value="Workstation1.ehr.familyclinic.com"/> <type value="1"/> </network>
</agent>
<source>
<site value="Development"/>
<observer>
<display value="Grahame's Laptop"/>
</observer>
<type>
<system value="http://dicom.nema.org/resources/ontology/DCM"/>
<code value="110122"/>
<display value="Login"/>
</type>
</source>
<entity>
<what> <identifier>
<type>
<coding>
<system value="http://terminology.hl7.org/CodeSystem/v2-0203"/>
<code value="SNO"/>
</coding>
<text value="Dell Serial Number"/>
</type>
<value value="ABCDEF"/>
</identifier>
</what>
<type>
<system value="http://terminology.hl7.org/CodeSystem/audit-entity-type"/>
<code value="4"/>
<display value="Other"/>
</type>
<role>
<system value="http://terminology.hl7.org/CodeSystem/object-role"/>
<code value="4"/>
<display value="Domain Resource"/>
</role>
<lifecycle>
<system value="http://terminology.hl7.org/CodeSystem/dicom-audit-lifecycle"/>
<code value="6"/>
<display value="Access / Use"/>
</lifecycle>
<name value="Grahame's Laptop"/>
<query value="Picard was the best starship captain"/>
</entity>
</AuditEvent>

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<AuditEvent xmlns="http://hl7.org/fhir">
<id value="example"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">Application Start for under service login &quot;Grahame&quot; (id: Grahame's Test HL7Connect)</div>
</text>
<category>
<coding>
<system value="http://dicom.nema.org/resources/ontology/DCM"/>
<code value="110100"/>
<display value="Application Activity"/>
</coding>
</category>
<code>
<coding>
<system value="http://dicom.nema.org/resources/ontology/DCM"/>
<code value="110120"/>
<display value="Application Start"/>
</coding>
</code>
<action value="E"/>
<recorded value="2012-10-25T22:04:27+11:00"/>
<outcome>
<code>
<system value="http://terminology.hl7.org/CodeSystem/audit-event-outcome"/>
<code value="0"/>
<display value="Success"/>
</code>
</outcome>
<agent>
<role>
<text value="Service User (Logon)"/>
</role>
<who>
<identifier>
<value value="Grahame"/>
</identifier>
</who>
<requestor value="false"/>
</agent>
<agent>
<!-- Source active participant, the software making the . AlternativeUserId - Process ID
-->
<extension url="http://hl7.org/fhir/StructureDefinition/auditevent-AlternativeUserID">
<valueIdentifier>
<type>
<text value="process ID"/>
</type>
<value value="6580"/>
</valueIdentifier>
</extension>
<who>
<identifier>
<system value="urn:oid:2.16.840.1.113883.4.2"/>
<value value="2.16.840.1.113883.4.2"/>
</identifier>
</who>
<requestor value="false"/>
<networkString value="Workstation1.ehr.familyclinic.com"/>
</agent>
<source>
<observer>
<display value="Grahame's Laptop"/>
</observer>
<type>
<coding>
<system value="http://dicom.nema.org/resources/ontology/DCM"/>
<code value="110122"/>
<display value="Login"/>
</coding>
</type>
</source>
<entity>
<what>
<identifier>
<type>
<coding>
<system value="http://terminology.hl7.org/CodeSystem/v2-0203"/>
<code value="SNO"/>
</coding>
<text value="Dell Serial Number"/>
</type>
<value value="ABCDEF"/>
</identifier>
</what>
<role>
<coding>
<system value="http://terminology.hl7.org/CodeSystem/object-role"/>
<code value="4"/>
<display value="Domain Resource"/>
</coding>
</role>
<query value="dGhpcyBpcyB2YWxpZCBiYXNlNjQ="/>
</entity>
</AuditEvent>

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.dstu2.model; package org.hl7.fhir.dstu2.model;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; 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 NON_BASE_64 = "Picard was the best starship captain.";
static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; 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 @Test
@DisplayName("Passing a non Base64 encoded String to constructor causes exception.") @DisplayName("Passing a non Base64 encoded String to constructor causes exception.")
public void testNonBase64String() { public void testNonBase64String() {
@ -45,6 +48,15 @@ class Base64BinaryTypeTest {
Assertions.assertNull(b64.getValueAsString()); 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 @Test
@DisplayName("Valid Base64 String creates non-null instance with non-null values.") @DisplayName("Valid Base64 String creates non-null instance with non-null values.")
public void testValid() { public void testValid() {

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.dstu2016may.model; package org.hl7.fhir.dstu2016may.model;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; 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 NON_BASE_64 = "Picard was the best starship captain.";
static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; 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 @Test
@DisplayName("Passing a non Base64 encoded String to constructor causes exception.") @DisplayName("Passing a non Base64 encoded String to constructor causes exception.")
public void testNonBase64String() { public void testNonBase64String() {
@ -45,6 +48,15 @@ class Base64BinaryTypeTest {
Assertions.assertNull(b64.getValueAsString()); 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 @Test
@DisplayName("Valid Base64 String creates non-null instance with non-null values.") @DisplayName("Valid Base64 String creates non-null instance with non-null values.")
public void testValid() { public void testValid() {

View File

@ -130,7 +130,7 @@ public class Base64BinaryType extends PrimitiveType<byte[]> implements IPrimitiv
@Override @Override
public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException { public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException {
myValue = theValue; myValue = theValue;
return this; return (Base64BinaryType) super.setValue(theValue);
} }
@Override @Override

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.dstu3.model; package org.hl7.fhir.dstu3.model;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; 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 NON_BASE_64 = "Picard was the best starship captain.";
static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; 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 @Test
@DisplayName("Passing a non Base64 encoded String to constructor causes exception.") @DisplayName("Passing a non Base64 encoded String to constructor causes exception.")
public void testNonBase64String() { public void testNonBase64String() {
@ -54,6 +57,15 @@ class Base64BinaryTypeTest {
Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); 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 @Test
@DisplayName("Valid Base64 String creates non-null instance with non-null values.") @DisplayName("Valid Base64 String creates non-null instance with non-null values.")
public void testValidSetValueAsString() { public void testValidSetValueAsString() {

View File

@ -130,7 +130,7 @@ public class Base64BinaryType extends PrimitiveType<byte[]> implements IPrimitiv
@Override @Override
public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException { public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException {
myValue = theValue; myValue = theValue;
return this; return (Base64BinaryType) super.setValue(theValue);
} }
@Override @Override

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.r4.model; package org.hl7.fhir.r4.model;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; 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 NON_BASE_64 = "Picard was the best starship captain.";
static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; 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 @Test
@DisplayName("Passing a non Base64 encoded String to constructor causes exception.") @DisplayName("Passing a non Base64 encoded String to constructor causes exception.")
public void testNonBase64String() { public void testNonBase64String() {
@ -54,6 +57,15 @@ class Base64BinaryTypeTest {
Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); 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 @Test
@DisplayName("Valid Base64 String creates non-null instance with non-null values.") @DisplayName("Valid Base64 String creates non-null instance with non-null values.")
public void testValidSetValueAsString() { public void testValidSetValueAsString() {

View File

@ -129,7 +129,7 @@ public class Base64BinaryType extends PrimitiveType<byte[]> implements IPrimitiv
@Override @Override
public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException { public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException {
myValue = theValue; myValue = theValue;
return this; return (Base64BinaryType) super.setValue(theValue);
} }
@Override @Override

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.r4b.model;
import static org.junit.jupiter.api.Assertions.assertThrows; 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.Assertions;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; 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 NON_BASE_64 = "Picard was the best starship captain.";
static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; 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 @Test
@DisplayName("Passing a non Base64 encoded String to constructor causes exception.") @DisplayName("Passing a non Base64 encoded String to constructor causes exception.")
public void testNonBase64String() { public void testNonBase64String() {
@ -55,6 +58,15 @@ class Base64BinaryTypeTest {
Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); 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 @Test
@DisplayName("Valid Base64 String creates non-null instance with non-null values.") @DisplayName("Valid Base64 String creates non-null instance with non-null values.")
public void testValidSetValueAsString() { public void testValidSetValueAsString() {

View File

@ -129,7 +129,7 @@ public class Base64BinaryType extends PrimitiveType<byte[]> implements IPrimitiv
@Override @Override
public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException { public Base64BinaryType setValue(byte[] theValue) throws IllegalArgumentException {
myValue = theValue; myValue = theValue;
return this; return (Base64BinaryType) super.setValue(theValue);
} }
@Override @Override

View File

@ -154,7 +154,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
private boolean generateExpansion(XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException { private boolean generateExpansion(XhtmlNode x, ValueSet vs, boolean header, List<UsedConceptMap> maps) throws FHIRFormatError, DefinitionException, IOException {
boolean hasExtensions = false; boolean hasExtensions = false;
List<String> langs = new ArrayList<String>(); List<String> langs = new ArrayList<String>();
Map<String, String> 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) { if (header) {
XhtmlNode h = x.addTag(getHeader()); XhtmlNode h = x.addTag(getHeader());
@ -219,20 +219,24 @@ public class ValueSetRenderer extends TerminologyRenderer {
tr.td().b().tx("System"); tr.td().b().tx("System");
XhtmlNode tdDisp = tr.td(); XhtmlNode tdDisp = tr.td();
tdDisp.b().tx("Display"); tdDisp.b().tx("Display");
boolean doLangs = false; boolean doDesignations = false;
for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
scanForLangs(c, langs); scanForDesignations(c, langs, designations);
} }
if (doDefinition) { if (doDefinition) {
tr.td().b().tx("Definition"); tr.td().b().tx("Definition");
doLangs = false; doDesignations = false;
} else { } else {
// if we're not doing definitions and we don't have too many languages, we'll do them in line // 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) { doDesignations = langs.size() + designations.size() < MAX_DESIGNATIONS_IN_LINE;
doLangs = true;
if (doDesignations) {
if (vs.hasLanguage()) { if (vs.hasLanguage()) {
tdDisp.tx(" - "+describeLang(vs.getLanguage())); tdDisp.tx(" - "+describeLang(vs.getLanguage()));
} }
for (String url : designations.keySet()) {
tr.td().b().addText(designations.get(url));
}
for (String lang : langs) { for (String lang : langs) {
tr.td().b().addText(describeLang(lang)); tr.td().b().addText(describeLang(lang));
} }
@ -242,22 +246,31 @@ public class ValueSetRenderer extends TerminologyRenderer {
addMapHeaders(tr, maps); addMapHeaders(tr, maps);
for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 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 // now, build observed languages
if (!doLangs && langs.size() > 0) { if (!doDesignations && langs.size() + designations.size() > 0) {
Collections.sort(langs); Collections.sort(langs);
x.para().b().tx("Additional Language Displays"); if (designations.size() == 0) {
t = x.table( "codes"); 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 = t.tr();
tr.td().b().tx("Code"); tr.td().b().tx("Code");
for (String url : designations.keySet()) {
tr.td().b().addText(designations.get(url));
}
for (String lang : langs) { for (String lang : langs) {
tr.td().b().addText(describeLang(lang)); tr.td().b().addText(describeLang(lang));
} }
for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) { 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; return false;
} }
private void addLanguageRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List<String> langs) { private void addDesignationRow(ValueSetExpansionContainsComponent c, XhtmlNode t, List<String> langs, Map<String, String> designations) {
XhtmlNode tr = t.tr(); XhtmlNode tr = t.tr();
tr.td().addText(c.getCode()); tr.td().addText(c.getCode());
addDesignationsToRow(c, designations, tr);
addLangaugesToRow(c, langs, tr); addLangaugesToRow(c, langs, tr);
for (ValueSetExpansionContainsComponent cc : c.getContains()) { for (ValueSetExpansionContainsComponent cc : c.getContains()) {
addLanguageRow(cc, t, langs); addDesignationRow(cc, t, langs, designations);
}
}
public void addDesignationsToRow(ValueSetExpansionContainsComponent c, Map<String, String> 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("\\", "/"); return ref.replace("\\", "/");
} }
private void scanForDesignations(ValueSetExpansionContainsComponent c, List<String> langs, Map<String, String> 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<String> langs) { private void scanForLangs(ValueSetExpansionContainsComponent c, List<String> langs) {
for (Extension ext : c.getExtension()) { for (Extension ext : c.getExtension()) {
if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) { if (ToolingExtensions.EXT_TRANSLATION.equals(ext.getUrl())) {
@ -651,8 +709,8 @@ public class ValueSetRenderer extends TerminologyRenderer {
scanForLangs(cc, langs); scanForLangs(cc, langs);
} }
} }
private void addExpansionRowToTable(XhtmlNode t, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doSystem, boolean doDefinition, List<UsedConceptMap> maps, CodeSystem allCS, List<String> langs, boolean doLangs) { private void addExpansionRowToTable(XhtmlNode t, ValueSetExpansionContainsComponent c, int i, boolean doLevel, boolean doSystem, boolean doDefinition, List<UsedConceptMap> maps, CodeSystem allCS, List<String> langs, Map<String, String> designations, boolean doDesignations) {
XhtmlNode tr = t.tr(); XhtmlNode tr = t.tr();
XhtmlNode td = tr.td(); XhtmlNode td = tr.td();
@ -697,11 +755,12 @@ public class ValueSetRenderer extends TerminologyRenderer {
td.i().tx("("+mapping.comp.getComment()+")"); td.i().tx("("+mapping.comp.getComment()+")");
} }
} }
if (doLangs) { if (doDesignations) {
addDesignationsToRow(c, designations, tr);
addLangaugesToRow(c, langs, tr); addLangaugesToRow(c, langs, tr);
} }
for (ValueSetExpansionContainsComponent cc : c.getContains()) { 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) { private void addCodeToTable(boolean isAbstract, String system, String code, String display, XhtmlNode td) {
CodeSystem e = getContext().getWorker().fetchCodeSystem(system); 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) if (isAbstract)
td.i().setAttribute("title", ABSTRACT_CODE_HINT).addText(code); td.i().setAttribute("title", ABSTRACT_CODE_HINT).addText(code);
else if ("http://snomed.info/sct".equals(system)) { else if ("http://snomed.info/sct".equals(system)) {
@ -824,7 +883,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
} }
for (ConceptSetComponent c : vs.getCompose().getInclude()) { for (ConceptSetComponent c : vs.getCompose().getInclude()) {
for (ConceptReferenceComponent cc : c.getConcept()) { 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)) { if (!Utilities.noString(lang) && !langs.contains(lang)) {
langs.add(lang); langs.add(lang);
} else { } else {
// can we present this as a designation that we know? // can we present this as a designation that we know?
String url = getUrlForDesignation(d); String disp = getDisplayForDesignation(d);
String disp = getDisplayForUrl(url); String url = getUrlForDesignation(d);
if (disp == null) {
disp = getDisplayForUrl(url);
}
if (disp != null && !designations.containsKey(url)) { if (disp != null && !designations.containsKey(url)) {
designations.put(url, disp); designations.put(url, disp);
} }
@ -960,7 +1022,8 @@ public class ValueSetRenderer extends TerminologyRenderer {
case "http://snomed.info/sct#900000000000013009": case "http://snomed.info/sct#900000000000013009":
return "Synonym"; return "Synonym";
default: 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<String> langs, boolean doDesignations, List<UsedConceptMap> maps, Map<String, String> designations, int index) throws FHIRException, IOException { private boolean genInclude(XhtmlNode ul, ConceptSetComponent inc, String type, List<String> langs, boolean doDesignations, List<UsedConceptMap> maps, Map<String, String> designations, int index) throws FHIRException, IOException {
boolean hasExtensions = false; boolean hasExtensions = false;
XhtmlNode li; XhtmlNode li;
@ -1243,18 +1314,12 @@ public class ValueSetRenderer extends TerminologyRenderer {
} }
private void addLanguageRow(ConceptReferenceComponent c, XhtmlNode t, List<String> langs) {
private void addDesignationRow(ConceptReferenceComponent c, XhtmlNode t, List<String> langs, Map<String, String> designations) {
XhtmlNode tr = t.tr(); XhtmlNode tr = t.tr();
tr.td().addText(c.getCode()); tr.td().addText(c.getCode());
for (String lang : langs) { addDesignationsToRow(c, designations, tr);
String d = null; addLangaugesToRow(c, langs, tr);
for (ConceptReferenceDesignationComponent cd : c.getDesignation()) {
String l = cd.getLanguage();
if (lang.equals(l))
d = cd.getValue();
}
tr.td().addText(d == null ? "" : d);
}
} }

View File

@ -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<String> command = new ArrayList<String>();
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<String> command = new ArrayList<String>();
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<String, JsonElement> 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<String, JsonElement> 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("<div") && expectedJsonString.contains("<div")))
if (!actualJsonString.equals(expectedJsonString))
if (!sameBytes(unBase64(actualJsonString), unBase64(expectedJsonString)))
return createNotEqualMessage("string property values differ at " + path, expectedJsonString, actualJsonString);
} else if (actualJsonPrimitive.isNumber() && expectedJsonPrimitive.isNumber()) {
if (!actualJsonPrimitive.getAsString().equals(expectedJsonPrimitive.getAsString()))
return createNotEqualMessage("number property values differ at " + path, expectedJsonPrimitive.getAsString(), actualJsonPrimitive.getAsString());
} else
return createNotEqualMessage("property types differ at " + path, expectedJsonPrimitive.getAsString(), actualJsonPrimitive.getAsString());
} else if (actualJsonElement instanceof JsonObject) {
String s = compareObjects(path, (JsonObject) expectedJsonElement, (JsonObject) actualJsonElement);
if (!Utilities.noString(s))
return s;
} else if (actualJsonElement instanceof JsonArray) {
JsonArray actualArray = (JsonArray) actualJsonElement;
JsonArray expectedArray = (JsonArray) expectedJsonElement;
if (actualArray.size() != expectedArray.size())
return createNotEqualMessage("array properties count differs at " + path, Integer.toString(expectedArray.size()), Integer.toString(actualArray.size()));
for (int i = 0; i < actualArray.size(); i++) {
String s = compareNodes(path + "[" + Integer.toString(i) + "]", expectedArray.get(i), actualArray.get(i));
if (!Utilities.noString(s))
return s;
}
} else if (actualJsonElement instanceof JsonNull) {
} else
return "unhandled property " + actualJsonElement.getClass().getName();
return null;
}
public static String temp() {
if (new File("c:\\temp").exists())
return "c:\\temp";
return System.getProperty("java.io.tmpdir");
}
public static String checkTextIsSame(String expected, String actual) throws JsonSyntaxException, FileNotFoundException, IOException {
return checkTextIsSame(expected, actual, true);
}
public static String checkTextIsSame(String expectedString, String actualString, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException {
String result = compareText(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<String> command = new ArrayList<String>();
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;
}
}

View File

@ -71,7 +71,6 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
public class TestingUtilities extends BaseTestingUtilities { public class TestingUtilities extends BaseTestingUtilities {
private static final boolean SHOW_DIFF = true;
static public Map<String, IWorkerContext> fcontexts; static public Map<String, IWorkerContext> fcontexts;
@ -178,346 +177,5 @@ public class TestingUtilities extends BaseTestingUtilities {
throw new Error("FHIR US directory not configured"); 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<String> command = new ArrayList<String>();
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<String> command = new ArrayList<String>();
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<String> command = new ArrayList<String>();
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<String, JsonElement> 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<String, JsonElement> 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("<div") && s2.contains("<div")))
if (!s1.equals(s2))
if (!sameBytes(unBase64(s1), unBase64(s2)))
return "string property values differ at " + path + ": type " + s1 + "/" + s2;
} else if (p1.isNumber() && p2.isNumber()) {
if (!p1.getAsString().equals(p2.getAsString()))
return "number property values differ at " + path + ": type " + p1.getAsString() + "/" + p2.getAsString();
} else
return "property types differ at " + path + ": type " + p1.getAsString() + "/" + p2.getAsString();
} else if (n1 instanceof JsonObject) {
String s = compareObjects(path, (JsonObject) n1, (JsonObject) n2);
if (!Utilities.noString(s))
return s;
} else if (n1 instanceof JsonArray) {
JsonArray a1 = (JsonArray) n1;
JsonArray a2 = (JsonArray) n2;
if (a1.size() != a2.size())
return "array properties differ at " + path + ": count " + Integer.toString(a1.size()) + "/" + Integer.toString(a2.size());
for (int i = 0; i < a1.size(); i++) {
String s = compareNodes(path + "[" + Integer.toString(i) + "]", a1.get(i), a2.get(i));
if (!Utilities.noString(s))
return s;
}
} else if (n1 instanceof JsonNull) {
} else
return "unhandled property " + n1.getClass().getName();
return null;
}
public static String temp() {
if (new File("c:\\temp").exists())
return "c:\\temp";
return System.getProperty("java.io.tmpdir");
}
public static String checkTextIsSame(String s1, String s2) throws JsonSyntaxException, FileNotFoundException, IOException {
return checkTextIsSame(s1, s2, true);
}
public static String checkTextIsSame(String s1, String s2, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException {
String result = compareText(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<String> command = new ArrayList<String>();
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;
}
}
} }

View File

@ -2,16 +2,20 @@ package org.hl7.fhir.r5.model;
import static org.junit.jupiter.api.Assertions.assertThrows; 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.Assertions;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import java.nio.charset.StandardCharsets;
class Base64BinaryTypeTest { class Base64BinaryTypeTest {
static final String NON_BASE_64 = "Picard was the best starship captain."; static final String NON_BASE_64 = "Picard was the best starship captain.";
static final String VALID_BASE_64 = "dGhpcyBpcyB2YWxpZCBiYXNlNjQ="; 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 @Test
@DisplayName("Passing a non Base64 encoded String to constructor causes exception.") @DisplayName("Passing a non Base64 encoded String to constructor causes exception.")
public void testNonBase64String() { public void testNonBase64String() {
@ -55,6 +59,15 @@ class Base64BinaryTypeTest {
Assertions.assertEquals(VALID_BASE_64, b64.asStringValue()); 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 @Test
@DisplayName("Valid Base64 String creates non-null instance with non-null values.") @DisplayName("Valid Base64 String creates non-null instance with non-null values.")
public void testValidSetValueAsString() { public void testValidSetValueAsString() {

View File

@ -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.Bundle.SearchEntryMode;
import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Resource; 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.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.GraphQLEngine; import org.hl7.fhir.r5.utils.GraphQLEngine;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
@ -88,10 +89,10 @@ public class GraphQLEngineTests implements IGraphQLStorageServices {
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
gql.getOutput().setWriteWrapper(false); gql.getOutput().setWriteWrapper(false);
gql.getOutput().write(str, 0); gql.getOutput().write(str, 0);
IOUtils.copy(TestingUtilities.loadTestResourceStream("r5", "graphql", source), new FileOutputStream(TestingUtilities.tempFile("graphql", source))); IOUtils.copy(CompareUtilities.loadTestResourceStream("r5", "graphql", source), new FileOutputStream(CompareUtilities.tempFile("graphql", source)));
IOUtils.copy(TestingUtilities.loadTestResourceStream("r5", "graphql", output), new FileOutputStream(TestingUtilities.tempFile("graphql", output))); IOUtils.copy(CompareUtilities.loadTestResourceStream("r5", "graphql", output), new FileOutputStream(CompareUtilities.tempFile("graphql", output)));
TextFile.stringToFile(str.toString(), TestingUtilities.tempFile("graphql", output+".out")); TextFile.stringToFile(str.toString(), CompareUtilities.tempFile("graphql", output+".out"));
msg = TestingUtilities.checkJsonIsSame(TestingUtilities.tempFile("graphql", output+".out"), TestingUtilities.tempFile("graphql", output)); msg = CompareUtilities.checkJsonIsSame(CompareUtilities.tempFile("graphql", output), CompareUtilities.tempFile("graphql", output+".out"));
Assertions.assertTrue(Utilities.noString(msg), msg); Assertions.assertTrue(Utilities.noString(msg), msg);
} }
else else

View File

@ -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;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.ITypeParser; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ITypeParser;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 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.test.utils.TestingUtilities;
import org.hl7.fhir.utilities.TerminologyServiceOptions; import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
@ -134,11 +135,11 @@ public class NarrativeGenerationTests {
XhtmlNode x = RendererFactory.factory(source, rc).build(source); XhtmlNode x = RendererFactory.factory(source, rc).build(source);
String expected = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".html")); String expected = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".html"));
String actual = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER; String actual = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER;
String expectedFileName = TestingUtilities.tempFile("narrative", test.getId() + ".expected.html"); String expectedFileName = CompareUtilities.tempFile("narrative", test.getId() + ".expected.html");
String actualFileName = TestingUtilities.tempFile("narrative", test.getId() + ".actual.html"); String actualFileName = CompareUtilities.tempFile("narrative", test.getId() + ".actual.html");
TextFile.stringToFile(expected, expectedFileName); TextFile.stringToFile(expected, expectedFileName);
TextFile.stringToFile(actual, actualFileName); 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); Assertions.assertTrue(msg == null, "Output does not match expected: "+msg);
if (test.isMeta()) { if (test.isMeta()) {
@ -147,9 +148,9 @@ public class NarrativeGenerationTests {
expected = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html")); expected = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html"));
actual = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER; 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); 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); Assertions.assertTrue(msg == null, "Meta output does not match expected: "+msg);
} }
} }

View File

@ -17,6 +17,7 @@ import org.hl7.fhir.r5.renderers.DataRenderer;
import org.hl7.fhir.r5.renderers.RendererFactory; import org.hl7.fhir.r5.renderers.RendererFactory;
import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 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.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.EOperationOutcome; import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;

View File

@ -10,6 +10,7 @@ import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.CapabilityStatement; import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.openapi.OpenApiGenerator; import org.hl7.fhir.r5.openapi.OpenApiGenerator;
import org.hl7.fhir.r5.openapi.Writer; 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.hl7.fhir.r5.test.utils.TestingUtilities;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -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.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Resource; 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.test.utils.TestingUtilities;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
@ -57,7 +58,7 @@ public class ParsingTests {
r = new XmlParser().parse(b); r = new XmlParser().parse(b);
b = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeBytes(r); b = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeBytes(r);
String output = new String(b); String output = new String(b);
String msg = TestingUtilities.checkJsonSrcIsSame(src, output); String msg = CompareUtilities.checkJsonSrcIsSame(src, output);
Assertions.assertTrue(msg == null, msg); Assertions.assertTrue(msg == null, msg);
} }

View File

@ -24,6 +24,7 @@ import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.renderers.RendererFactory; import org.hl7.fhir.r5.renderers.RendererFactory;
import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 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.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.EOperationOutcome; import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -12,6 +12,7 @@ import javax.xml.parsers.ParserConfigurationException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext; 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.test.utils.TestPackageLoader;
import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.JsonParser;
@ -146,11 +147,11 @@ public class VocabTests {
outcome.getValueset().getExpansion().setTimestamp(null); outcome.getValueset().getExpansion().setTimestamp(null);
String expected = new XmlParser().setOutputStyle(OutputStyle.PRETTY).composeString(targetVS); String expected = new XmlParser().setOutputStyle(OutputStyle.PRETTY).composeString(targetVS);
String actual = new XmlParser().setOutputStyle(OutputStyle.PRETTY).composeString(outcome.getValueset()); String actual = new XmlParser().setOutputStyle(OutputStyle.PRETTY).composeString(outcome.getValueset());
String expectedFileName = TestingUtilities.tempFile("vocab", test.getId() + ".expected.html"); String expectedFileName = CompareUtilities.tempFile("vocab", test.getId() + ".expected.html");
String actualFileName = TestingUtilities.tempFile("vocab", test.getId() + ".actual.html"); String actualFileName = CompareUtilities.tempFile("vocab", test.getId() + ".actual.html");
TextFile.stringToFile(expected, expectedFileName); TextFile.stringToFile(expected, expectedFileName);
TextFile.stringToFile(actual, actualFileName); 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); Assertions.assertTrue(msg == null, "Output does not match expected: "+msg);
} else { } else {
Assertions.fail("Expansion Failed: "+outcome.getError()); Assertions.fail("Expansion Failed: "+outcome.getError());

View File

@ -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<Arguments> 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<Arguments> 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));
}
}
}

View File

@ -0,0 +1,12 @@
{
"expectedString" : "expected value",
"expectedBoolean" : true,
"expectedNumber" : 123,
"expectedArray" : [
"unexpectedValue 1",
"expectedValue 2"
],
"property" : {
"subField" : 1
}
}

View File

@ -0,0 +1,3 @@
string property values differ at .expectedArray[0]
Expected :expectedValue 1
Actual :unexpectedValue 1

View File

@ -0,0 +1,11 @@
{
"expectedString" : "expected value",
"expectedBoolean" : true,
"expectedNumber" : 123,
"expectedArray" : [
"expectedValue 1"
],
"property" : {
"subField" : 1
}
}

View File

@ -0,0 +1,3 @@
array properties count differs at .expectedArray
Expected :2
Actual :1

View File

@ -0,0 +1,12 @@
{
"expectedString" : "expected value",
"expectedBoolean" : false,
"expectedNumber" : 123,
"expectedArray" : [
"expectedValue 1",
"expectedValue 2"
],
"property" : {
"subField" : 1
}
}

View File

@ -0,0 +1,3 @@
boolean property values differ at .expectedBoolean
Expected :true
Actual :false

View File

@ -0,0 +1,12 @@
{
"expectedString" : "expected value",
"expectedBoolean" : true,
"expectedNumber" : 789,
"expectedArray" : [
"expectedValue 1",
"expectedValue 2"
],
"property" : {
"subField" : 1
}
}

View File

@ -0,0 +1,3 @@
number property values differ at .expectedNumber
Expected :123
Actual :789

View File

@ -0,0 +1,12 @@
{
"expectedString" : 1,
"expectedBoolean" : true,
"expectedNumber" : 123,
"expectedArray" : [
"expectedValue 1",
"expectedValue 2"
],
"property" : {
"subField" : 1
}
}

View File

@ -0,0 +1,3 @@
property types differ at .expectedString
Expected :expected value
Actual :1

View File

@ -0,0 +1,12 @@
{
"expectedString" : "unexpected value",
"expectedBoolean" : true,
"expectedNumber" : 123,
"expectedArray" : [
"expectedValue 1",
"expectedValue 2"
],
"property" : {
"subField" : 1
}
}

View File

@ -0,0 +1,3 @@
string property values differ at .expectedString
Expected :expected value
Actual :unexpected value

View File

@ -0,0 +1,13 @@
{
"expectedString" : "expected value",
"expectedBoolean" : true,
"expectedNumber" : 123,
"unexpectedProperty" : "totally unexpected",
"expectedArray" : [
"expectedValue 1",
"expectedValue 2"
],
"property" : {
"subField" : 1
}
}

View File

@ -0,0 +1 @@
properties differ at : missing property unexpectedProperty

View File

@ -0,0 +1,11 @@
{
"expectedString" : "expected value",
"expectedNumber" : 123,
"expectedArray" : [
"expectedValue 1",
"expectedValue 2"
],
"property" : {
"subField" : 1
}
}

View File

@ -0,0 +1 @@
properties differ at : missing property expectedBoolean

View File

@ -0,0 +1,12 @@
{
"expectedString" : "expected value",
"expectedBoolean" : true,
"expectedNumber" : 123,
"expectedArray" : [
"expectedValue 1",
"expectedValue 2"
],
"property" : {
}
}

View File

@ -0,0 +1 @@
properties differ at .property: missing property subField

View File

@ -0,0 +1,12 @@
{
"expectedString" : "expected value",
"expectedBoolean" : true,
"expectedNumber" : 123,
"expectedArray" : [
"expectedValue 1",
"expectedValue 2"
],
"property" : {
"subField" : 1
}
}

View File

@ -0,0 +1,7 @@
<root>
<blah myAtt="wrongwrongwrong">
expected
</blah>
<emptyNode></emptyNode>
<foo:nameSpacedNode xmlns:foo="http://www.example.com/FOO" ></foo:nameSpacedNode>
</root>

View File

@ -0,0 +1,3 @@
Attributes differ at /root/blah
Expected :dummyAtt
Actual :wrongwrongwrong

View File

@ -0,0 +1,7 @@
<root>
<blah myAtt="dummyAtt">
expected
</blah>
<emptyNode></emptyNode>
<foo:wrongNameSpacedNode xmlns:foo="http://www.example.com/FOO" ></foo:wrongNameSpacedNode>
</root>

View File

@ -0,0 +1,3 @@
Names differ at /root
Expected :nameSpacedNode
Actual :wrongNameSpacedNode

View File

@ -0,0 +1,7 @@
<root>
<blah myAtt="dummyAtt">
expected
</blah>
<emptyNode></emptyNode>
<foo:nameSpacedNode xmlns:foo="http://www.example.com/BAR" ></foo:nameSpacedNode>
</root>

View File

@ -0,0 +1,3 @@
Namespaces differ at /root
Expected :http://www.example.com/FOO
Actual :http://www.example.com/BAR

View File

@ -0,0 +1,7 @@
<root>
<blah myAtt="dummyAtt">
<foo />
</blah>
<emptyNode></emptyNode>
<foo:nameSpacedNode xmlns:foo="http://www.example.com/FOO" ></foo:nameSpacedNode>
</root>

View File

@ -0,0 +1,3 @@
node type mismatch in children of /root/blah
Expected :1
Actual :1

View File

@ -0,0 +1,7 @@
<root>
<blah myAtt="dummyAtt">
different
</blah>
<emptyNode></emptyNode>
<foo:nameSpacedNode xmlns:foo="http://www.example.com/FOO" ></foo:nameSpacedNode>
</root>

View File

@ -0,0 +1,3 @@
Text differs at /root/blah
Expected :expected
Actual :different

View File

@ -0,0 +1,7 @@
<root>
<blah myAtt="dummyAtt">
expected
</blah>
<emptyNode>not empty</emptyNode>
<foo:nameSpacedNode xmlns:foo="http://www.example.com/FOO" ></foo:nameSpacedNode>
</root>

View File

@ -0,0 +1 @@
node mismatch - more nodes in expected in children of /root/emptyNode

View File

@ -0,0 +1,8 @@
<root>
<blah myAtt="dummyAtt">
expected
</blah>
<emptyNode></emptyNode>
<foo:nameSpacedNode xmlns:foo="http://www.example.com/FOO" ></foo:nameSpacedNode>
<extraNode/>
</root>

View File

@ -0,0 +1 @@
node mismatch - more nodes in expected in children of /root

View File

@ -0,0 +1,7 @@
<root>
<blah>
expected
</blah>
<emptyNode></emptyNode>
<foo:nameSpacedNode xmlns:foo="http://www.example.com/FOO" ></foo:nameSpacedNode>
</root>

View File

@ -0,0 +1 @@
Attributes differ at /root/blah: missing attribute myAtt

View File

@ -0,0 +1,6 @@
<root>
<blah myAtt="dummyAtt">
expected
</blah>
<emptyNode></emptyNode>
</root>

View File

@ -0,0 +1 @@
node mismatch - more nodes in actual in children of /root

View File

@ -0,0 +1,7 @@
<root>
<blah myAtt="dummyAtt">
expected
</blah>
<emptyNode></emptyNode>
<foo:nameSpacedNode xmlns:foo="http://www.example.com/FOO" ></foo:nameSpacedNode>
</root>

View File

@ -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;
}
}
} }

View File

@ -69,11 +69,37 @@ public class IgLoader {
this.isDebug = isDebug; 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<ImplementationGuide> igs, public void loadIg(List<ImplementationGuide> igs,
Map<String, byte[]> binaries, Map<String, byte[]> binaries,
String src, String src,
boolean recursive) throws IOException, FHIRException { 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) { if (npm != null) {
for (String s : npm.dependencies()) { for (String s : npm.dependencies()) {
if (!getContext().getLoadedPackages().contains(s)) { if (!getContext().getLoadedPackages().contains(s)) {
@ -82,17 +108,17 @@ public class IgLoader {
} }
} }
} }
System.out.print(" Load " + src); System.out.print(" Load " + srcPackage);
if (!src.contains("#")) { if (!srcPackage.contains("#")) {
System.out.print("#" + npm.version()); System.out.print("#" + npm.version());
} }
int count = getContext().loadFromPackage(npm, ValidatorUtils.loaderForVersion(npm.fhirVersion())); int count = getContext().loadFromPackage(npm, ValidatorUtils.loaderForVersion(npm.fhirVersion()));
System.out.println(" - " + count + " resources (" + getContext().clock().milestone() + ")"); System.out.println(" - " + count + " resources (" + getContext().clock().milestone() + ")");
} else { } else {
System.out.print(" Load " + src); System.out.print(" Load " + srcPackage);
String canonical = null; String canonical = null;
int count = 0; int count = 0;
Map<String, byte[]> source = loadIgSource(src, recursive, true); Map<String, byte[]> source = loadIgSource(srcPackage, recursive, true);
String version = Constants.VERSION; String version = Constants.VERSION;
if (getVersion() != null) { if (getVersion() != null) {
version = getVersion(); version = getVersion();
@ -100,6 +126,10 @@ public class IgLoader {
if (source.containsKey("version.info")) { if (source.containsKey("version.info")) {
version = readInfoVersion(source.get("version.info")); version = readInfoVersion(source.get("version.info"));
} }
if (explicitFhirVersion != null) {
version = explicitFhirVersion;
}
for (Map.Entry<String, byte[]> t : source.entrySet()) { for (Map.Entry<String, byte[]> t : source.entrySet()) {
String fn = t.getKey(); String fn = t.getKey();
if (!exemptFile(fn)) { 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 { public Content loadContent(String source, String opName, boolean asIg) throws FHIRException, IOException {
Map<String, byte[]> s = loadIgSource(source, false, asIg); Map<String, byte[]> s = loadIgSource(source, false, asIg);
Content res = new Content(); 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:
* <br> - a canonical url for an ig - this will be converted to a package id and loaded into the cache
* <br> - a package id for an ig - this will be loaded into the cache
* <br> - a direct reference to a package ("package.tgz") - this will be extracted by the cache manager, but not put in the cache
* <br> - 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 * @throws IOException
**/ */
public Map<String, byte[]> loadIgSource(String src, public Map<String, byte[]> loadIgSource(String src,
boolean recursive, boolean recursive,
boolean explore) throws FHIRException, IOException { 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)) { if (Common.isNetworkPath(src)) {
String v = null; String v = null;
if (src.contains("|")) { if (src.contains("|")) {
@ -627,7 +674,7 @@ public class IgLoader {
return Utilities.existsInList(fn, EXEMPT_FILES); return Utilities.existsInList(fn, EXEMPT_FILES);
} }
private Resource loadFileWithErrorChecking(String version, Map.Entry<String, byte[]> t, String fn) { protected Resource loadFileWithErrorChecking(String version, Map.Entry<String, byte[]> t, String fn) {
log("* load file: " + fn); log("* load file: " + fn);
Resource r = null; Resource r = null;
try { try {

View File

@ -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 { 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); NpmPackage npm = getPcm().loadPackage(src, null);
if (npm != null) { if (npm != null) {

View File

@ -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 profiles declared in the resource (Resource.meta.profile) or specified on the
command line command line
The FHIR validation tool validates a FHIR resource or bundle. The FHIR validation tool validates a FHIR resource or bundle. Schema and
Schema and schematron checking is performed, then some additional checks are performed. schematron checking is performed, then some additional checks are performed.
* XML & Json (FHIR versions {{XML_AND_JSON_FHIR_VERSIONS}}) * XML & Json (FHIR versions {{XML_AND_JSON_FHIR_VERSIONS}})
* Turtle (FHIR versions {{TURTLE_FHIR_VERSIONS}}) * Turtle (FHIR versions {{TURTLE_FHIR_VERSIONS}})
If requested, instances will also be verified against the appropriate schema If requested, instances will also be verified against the appropriate schema W3C
W3C XML Schema, JSON schema or ShEx, as appropriate XML Schema, JSON schema or ShEx, as appropriate
Usage: java -jar [validator].jar (parameters) Usage: java -jar [validator].jar (parameters)
The following parameters are supported: The following parameters are supported:
[source]: a file, url, directory or pattern for resources to validate. At [source]: a file, url, directory or pattern for resources to validate.
least one source must be declared. If there is more than one source or if At least one source must be declared. If there is more than one source or
the source is other than a single file or url and the output parameter is if the source is other than a single file or url and the output parameter is
used, results will be provided as a Bundle. used, results will be provided as a Bundle.
Patterns are limited to a directory followed by a filename with an embedded Patterns are limited to a directory followed by a filename with an
asterisk. E.g. foo*-examples.xml or someresource.*, etc. embedded asterisk. E.g. foo*-examples.xml or someresource.*, etc.
-version [ver]: The FHIR version to use. This can only appear once. -version [ver]: The FHIR version to use.
valid values {{FHIR_MAJOR_VERSIONS}} or {{FHIR_MINOR_VERSIONS}} This can only appear once.
Default value is {{FHIR_CURRENT_VERSION}} valid values {{FHIR_MAJOR_VERSIONS}} or {{FHIR_MINOR_VERSIONS}}
-ig [package|file|folder|url]: an IG or profile definition to load. Can be Default value is {{FHIR_CURRENT_VERSION}}
the URL of an implementation guide or a package ([id]-[ver]) for -ig [package|file|folder|url]: an IG or profile definition to load.
a built implementation guide or a local folder that contains a Can be the URL of an implementation guide or a package ([id]-[ver]) for a
set of conformance resources. built implementation guide or a local folder that contains a set of
If you would like to load the latest unreleased version of the implementation guide or package, conformance resources.
please define the version as '#current'. If no version is provided, the latest version If you would like to load the latest unreleased version of the
in the package cache will be used, or if no such cached package is available, the implementation guide or package, please define the version as '#current'.
PackageCacheManager will load the latest from the the online package repo. If no version is provided, the latest version in the package cache will
No default value. This parameter can appear any number of times 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 -tx [url]: the [base] url of a FHIR terminology service
Default value is http://tx.fhir.org. This parameter can appear once Default value is http://tx.fhir.org. This parameter can appear once
To run without terminology value, specific n/a as the URL To run without terminology value, specific n/a as the URL
-txLog [file]: Produce a log of the terminology server operations in [file] -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 -profile [url]: the canonical URL to validate against (same as if it was
specified in Resource.meta.profile). If no profile is specified, the specified in Resource.meta.profile).
resource is validated against the base specification. This parameter If no profile is specified, the resource is validated against the base
can appear any number of times. specification. This parameter can appear any number of times.
Note: the profile (and it's dependencies) have to be made available Note: the profile (and it's dependencies) have to be made available
through one of the -ig parameters. Note that package dependencies will through one of the -ig parameters. Note that package dependencies will
automatically be resolved automatically be resolved
-showReferenceMessages -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 against profiles defined on a reference. This increases the volume of
validation messages, but may allow easier debugging. If not specified, validation messages, but may allow easier debugging. If not specified,
then only a high-level message indicating that the referenced item wasn't then only a high-level message indicating that the referenced item wasn't
valid against the listed profile(s) will be provided. valid against the listed profile(s) will be provided.
-questionnaire mode: what to do with when validating QuestionnaireResponse resources -questionnaire mode: what to do when validating QuestionnaireResponse resources
none (default): just ignore the questionnaire reference * none (default): just ignore the questionnaire reference
required: check that the QuestionnaireResponse has a questionnaire and validate against it * required: check that the QuestionnaireResponse has a questionnaire and
check: if the QuestionnaireResponse has a questionnaire, validate against it validate against it
The questionnaire must be loaded using the -ig parameter * check: if the QuestionnaireResponse has a questionnaire, validate
the location of a questionnaire. If provided, then the validator will validate against it
any QuestionnaireResponse that claims to match the Questionnaire against it The questionnaire must be loaded using the -ig parameter
no default value. This parameter can appear any number of times 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) -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 -debug
Produce additional information about the loading/validation process Produce additional information about the loading/validation process
-recurse -recurse
Look in subfolders when -ig refers to a folder Look in subfolders when -ig refers to a folder
-locale -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 -sct
Specify the edition of SNOMED CT to use. Valid Choices: Specify the edition of SNOMED CT to use. Valid Choices:
intl | us | uk | au | nl | ca | se | dk | es 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 tx.fhir.org only supports a subset. To add to this list or tx.fhir.org ask
ask on https://chat.fhir.org/#narrow/stream/179202-terminology on https://chat.fhir.org/#narrow/stream/179202-terminology
-native: use schema for validation as well -native: use schema for validation as well
* XML: w3c schema+schematron * XML: w3c schema+schematron
* JSON: json.schema * JSON: json.schema
* RDF: SHEX * RDF: SHEX
Default: false Default: false
-language: [lang] -language: [lang] The language to use when validating coding displays - same
The language to use when validating coding displays - same value as for xml:lang value as for xml:lang
Not used if the resource specifies language Not used if the resource specifies language
Default: no specified language Default: no specified language
-strictExtensions: If present, treat extensions not defined within the specified FHIR version and any -strictExtensions: If present, treat extensions not defined within the specified
referenced implementation guides or profiles as errors. (Default is to only raise information messages.) FHIR version and any referenced implementation guides or profiles as errors.
-hintAboutNonMustSupport: If present, raise hints if the instance contains data elements that are not (Default is to only raise information messages.)
marked as mustSupport=true. Useful to identify elements included that may be ignored by recipients -hintAboutNonMustSupport: If present, raise hints if the instance contains data
-assumeValidRestReferences: If present, assume that URLs that reference resources follow the RESTful URI pattern elements that are not marked as mustSupport=true. Useful to identify
and it is safe to infer the type from the URL elements included that may be ignored by recipients
-security-checks: If present, check that string content doesn't include any html-like tags that might create -assumeValidRestReferences: If present, assume that URLs that reference
problems downstream (though all external input must always be santized by escaping for either html or sql) 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 Parameters can appear in any order
Alternatively, you can use the validator to execute a transformation as described by a structure map. Alternatively, you can use the validator to execute a transformation as
To do this, you must provide some additional parameters: described by a structure map. To do this, you must provide some additional
parameters:
-transform [map] -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 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. Alternatively, you can use the validator to generate narrative for a resource.
To do this, you must provide a specific parameter: To do this, you must provide a specific parameter:
-narrative -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. Alternatively, you can use the validator to convert a resource or logical model.
To do this, you must provide a specific parameter: To do this, you must provide a specific parameter:
-convert -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. Alternatively, you can use the validator to evaluate a FHIRPath expression on a
To do this, you must provide a specific parameter: resource or logical model. To do this, you must provide a specific parameter:
-fhirpath [FHIRPath] -fhirpath [FHIRPath]
* [FHIRPath] the FHIRPath expression to evaluate * [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. Finally, you can use the validator to generate a snapshot for a profile.
To do this, you must provide a specific parameter: To do this, you must provide a specific parameter:
-snapshot -snapshot
-snapshot requires the parameters -defn, -txserver, -source, and -output. ig may be used to provide necessary base profiles -snapshot requires the parameters -defn, -txserver, -source, and -output. ig may
be used to provide necessary base profiles

View File

@ -18,6 +18,7 @@ import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureMap; 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.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
@ -103,11 +104,11 @@ public class FHIRMappingLanguageTests {
assertTrue(e.getMessage(), false); assertTrue(e.getMessage(), false);
} }
if (output.endsWith("json")) { if (output.endsWith("json")) {
msg = TestingUtilities.checkJsonSrcIsSame(s.toString(), outputJson); msg = CompareUtilities.checkJsonSrcIsSame(s.toString(), outputJson);
} else { } else {
TextFile.bytesToFile(s.toByteArray(), fileOutputRes); TextFile.bytesToFile(s.toByteArray(), fileOutputRes);
TextFile.bytesToFile(outputJson.getBytes(), fileOutputResOrig); 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)) { if (!Utilities.noString(msg)) {
System.out.print(s.toString()); System.out.print(s.toString());

View File

@ -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<Arguments> 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<String, byte[]> 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<ImplementationGuide> 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<ImplementationGuide> igs = Collections.emptyList();
igLoader.loadIg(igs,
Collections.emptyMap(),
"[0.1.2]" + DUMMY_PATH,
false);
});
}
}