package ca.uhn.fhir.rest.client; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.InputStream; import java.io.StringReader; import java.nio.charset.Charset; import org.apache.commons.io.input.ReaderInputStream; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicStatusLine; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Matchers; import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.dstu2.resource.Conformance; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.rest.client.exceptions.FhirClientInappropriateForServerException; import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.util.TestUtil; public class ClientServerValidationDstu2Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ClientServerValidationDstu2Test.class); private FhirContext myCtx; private boolean myFirstResponse; private HttpClient myHttpClient; private HttpResponse myHttpResponse; @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); } @Before public void before() { myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); myCtx = FhirContext.forDstu2(); myCtx.getRestfulClientFactory().setHttpClient(myHttpClient); myFirstResponse = true; } @Test public void testClientUsesInterceptors() throws Exception { Conformance conf = new Conformance(); conf.setFhirVersion("0.5.0"); final String confResource = myCtx.newXmlParser().encodeResourceToString(conf); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { @Override public InputStream answer(InvocationOnMock theInvocation) throws Throwable { if (myFirstResponse) { myFirstResponse = false; return new ReaderInputStream(new StringReader(confResource), Charset.forName("UTF-8")); } else { Patient resource = new Patient(); resource.addName().addFamily().setValue("FAM"); return new ReaderInputStream(new StringReader(myCtx.newXmlParser().encodeResourceToString(resource)), Charset.forName("UTF-8")); } } }); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE); IGenericClient client = myCtx.newRestfulGenericClient("http://foo"); client.registerInterceptor(new BasicAuthInterceptor("USER", "PASS")); Patient pt = (Patient) client.read(new UriDt("http://foo/Patient/123")); assertEquals("FAM", pt.getNameFirstRep().getFamilyAsSingleString()); assertEquals(2, capt.getAllValues().size()); Header auth = capt.getAllValues().get(0).getFirstHeader("Authorization"); assertNotNull(auth); assertEquals("Basic VVNFUjpQQVNT", auth.getValue()); auth = capt.getAllValues().get(1).getFirstHeader("Authorization"); assertNotNull(auth); assertEquals("Basic VVNFUjpQQVNT", auth.getValue()); } @Test public void testForceConformanceCheck() throws Exception { Conformance conf = new Conformance(); conf.setFhirVersion("0.5.0"); final String confResource = myCtx.newXmlParser().encodeResourceToString(conf); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { @Override public InputStream answer(InvocationOnMock theInvocation) throws Throwable { if (myFirstResponse) { myFirstResponse = false; return new ReaderInputStream(new StringReader(confResource), Charset.forName("UTF-8")); } else { Patient resource = new Patient(); resource.addName().addFamily().setValue("FAM"); return new ReaderInputStream(new StringReader(myCtx.newXmlParser().encodeResourceToString(resource)), Charset.forName("UTF-8")); } } }); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE); IGenericClient client = myCtx.newRestfulGenericClient("http://foo"); client.registerInterceptor(new BasicAuthInterceptor("USER", "PASS")); client.forceConformanceCheck(); assertEquals(1, capt.getAllValues().size()); Patient pt = (Patient) client.read(new UriDt("http://foo/Patient/123")); assertEquals("FAM", pt.getNameFirstRep().getFamilyAsSingleString()); assertEquals(2, capt.getAllValues().size()); Header auth = capt.getAllValues().get(0).getFirstHeader("Authorization"); assertNotNull(auth); assertEquals("Basic VVNFUjpQQVNT", auth.getValue()); auth = capt.getAllValues().get(1).getFirstHeader("Authorization"); assertNotNull(auth); assertEquals("Basic VVNFUjpQQVNT", auth.getValue()); } @Test public void testServerReturnsAnHttp401() throws Exception { ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 401, "Unauthorized")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT)); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { @Override public InputStream answer(InvocationOnMock theInvocation) throws Throwable { return new ReaderInputStream(new StringReader("Unauthorized"), Charset.forName("UTF-8")); } }); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); IGenericClient client = myCtx.newRestfulGenericClient("http://foo"); try { client.read().resource(Patient.class).withId("123").execute(); fail(); } catch (AuthenticationException e) { // good } } @Test public void testServerReturnsAppropriateVersionForDstu2_040() throws Exception { Conformance conf = new Conformance(); conf.setFhirVersion("0.5.0"); final String confResource = myCtx.newXmlParser().encodeResourceToString(conf); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { @Override public InputStream answer(InvocationOnMock theInvocation) throws Throwable { if (myFirstResponse) { myFirstResponse = false; return new ReaderInputStream(new StringReader(confResource), Charset.forName("UTF-8")); } else { return new ReaderInputStream(new StringReader(myCtx.newXmlParser().encodeResourceToString(new Patient())), Charset.forName("UTF-8")); } } }); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE); IGenericClient client = myCtx.newRestfulGenericClient("http://foo"); // don't load the conformance until the first time the client is actually used assertTrue(myFirstResponse); client.read(new UriDt("http://foo/Patient/123")); assertFalse(myFirstResponse); myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123")); myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123")); // Conformance only loaded once, then 3 reads verify(myHttpClient, times(4)).execute(Matchers.any(HttpUriRequest.class)); } @Test public void testServerReturnsAppropriateVersionForDstu2_050() throws Exception { Conformance conf = new Conformance(); conf.setFhirVersion("0.5.0"); final String confResource = myCtx.newXmlParser().encodeResourceToString(conf); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { @Override public InputStream answer(InvocationOnMock theInvocation) throws Throwable { if (myFirstResponse) { myFirstResponse = false; return new ReaderInputStream(new StringReader(confResource), Charset.forName("UTF-8")); } else { return new ReaderInputStream(new StringReader(myCtx.newXmlParser().encodeResourceToString(new Patient())), Charset.forName("UTF-8")); } } }); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE); IGenericClient client = myCtx.newRestfulGenericClient("http://foo"); // don't load the conformance until the first time the client is actually used assertTrue(myFirstResponse); client.read(new UriDt("http://foo/Patient/123")); assertFalse(myFirstResponse); myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123")); myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123")); // Conformance only loaded once, then 3 reads verify(myHttpClient, times(4)).execute(Matchers.any(HttpUriRequest.class)); } @Test public void testServerReturnsWrongVersionForDstu2() throws Exception { Conformance conf = new Conformance(); conf.setFhirVersion("0.80"); String msg = myCtx.newXmlParser().encodeResourceToString(conf); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE); try { myCtx.newRestfulGenericClient("http://foo").read(new UriDt("http://foo/Patient/123")); fail(); } catch (FhirClientInappropriateForServerException e) { String out = e.toString(); String want = "The server at base URL \"http://foo/metadata\" returned a conformance statement indicating that it supports FHIR version \"0.80\" which corresponds to DSTU1, but this client is configured to use DSTU2 (via the FhirContext)"; ourLog.info(out); ourLog.info(want); assertThat(out, containsString(want)); } } }