Skip to content

Commit

Permalink
Merge pull request #743 from MeasureAuthoringTool/MAT-7737
Browse files Browse the repository at this point in the history
MAT-7737 Refactor Test Case Validation Endpoint to validate by Model
  • Loading branch information
adongare authored Oct 28, 2024
2 parents 5903688 + 0596dce commit e71ba9f
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 54 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
<dependency>
<groupId>gov.cms.madie</groupId>
<artifactId>madie-java-models</artifactId>
<version>0.6.68-SNAPSHOT</version>
<version>0.6.69-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>gov.cms.madie</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ public UnsupportedTypeException(String factory, String... params) {
+ " as the following are not yet support: %s",
factory, Arrays.toString(params)));
}

public UnsupportedTypeException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import cms.gov.madie.measure.services.VirusScanClient;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import gov.cms.madie.models.common.ModelType;
import gov.cms.madie.models.measure.HapiOperationOutcome;
import gov.cms.madie.models.scanner.ScanValidationDto;
import gov.cms.madie.models.scanner.VirusScanResponseDto;
Expand Down Expand Up @@ -38,10 +39,13 @@ public class ValidationController {
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> validateBundle(
HttpEntity<String> request, @RequestHeader("Authorization") String accessToken) {
HttpEntity<String> request,
@RequestParam String model,
@RequestHeader("Authorization") String accessToken) {
try {
ModelType modelType = ModelType.valueOfName(model);
ResponseEntity<HapiOperationOutcome> output =
fhirServicesClient.validateBundle(request.getBody(), accessToken);
fhirServicesClient.validateBundle(request.getBody(), modelType, accessToken);
return ResponseEntity.ok(mapper.writeValueAsString(output.getBody()));

} catch (JsonProcessingException ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package cms.gov.madie.measure.services;

import cms.gov.madie.measure.config.FhirServicesConfig;
import cms.gov.madie.measure.exceptions.UnsupportedTypeException;
import gov.cms.madie.models.common.BundleType;
import gov.cms.madie.models.common.ModelType;
import gov.cms.madie.models.dto.ExportDTO;
import gov.cms.madie.models.measure.HapiOperationOutcome;
import gov.cms.madie.models.measure.Measure;
Expand Down Expand Up @@ -61,11 +63,18 @@ public byte[] getMeasureBundleExport(Measure measure, String accessToken) {
}

public ResponseEntity<HapiOperationOutcome> validateBundle(
String testCaseJson, String accessToken) {
String testCaseJson, ModelType modelType, String accessToken) {
if (modelType == null) {
throw new UnsupportedTypeException("Please provide model type.");
}

String modelVersion = modelType.getVersionNumber().replace(".", "-");
URI uri =
URI.create(
fhirServicesConfig.getMadieFhirServiceBaseUrl()
+ fhirServicesConfig.getMadieFhirServiceValidateBundleUri());
+ fhirServicesConfig
.getMadieFhirServiceValidateBundleUri()
.replace("$model", modelVersion));
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, accessToken);
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ public PackageDto getMeasurePackage(Measure measure, String accessToken) {

} catch (RestClientException ex) {
log.error(
"An error occurred while creating package for QDM measure: {}. " +
"Please check QDM service logs for more information.",
"An error occurred while creating package for QDM measure: {}. "
+ "Please check QDM service logs for more information.",
measure.getId(),
ex);

Expand Down
16 changes: 11 additions & 5 deletions src/main/java/cms/gov/madie/measure/services/TestCaseService.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,8 @@ public TestCase validateTestCaseAsResource(
? null
: testCase.toBuilder().validResource(JsonUtil.isValidJson(testCase.getJson())).build();
} else {
final HapiOperationOutcome hapiOperationOutcome = validateTestCaseJson(testCase, accessToken);
final HapiOperationOutcome hapiOperationOutcome =
validateTestCaseJson(testCase, modelType, accessToken);
return testCase == null
? null
: testCase.toBuilder()
Expand Down Expand Up @@ -336,8 +337,9 @@ public TestCase updateTestCase(

public TestCase getTestCase(
String measureId, String testCaseId, boolean validate, String accessToken) {
Measure measure = findMeasureById(measureId);
TestCase testCase =
Optional.ofNullable(findMeasureById(measureId).getTestCases())
Optional.ofNullable(measure.getTestCases())
.orElseThrow(() -> new ResourceNotFoundException("Test Case", testCaseId))
.stream()
.filter(tc -> tc.getId().equals(testCaseId))
Expand All @@ -346,7 +348,8 @@ public TestCase getTestCase(
if (testCase == null) {
throw new ResourceNotFoundException("Test Case", testCaseId);
} else if (validate) {
testCase.setHapiOperationOutcome(validateTestCaseJson(testCase, accessToken));
testCase.setHapiOperationOutcome(
validateTestCaseJson(testCase, ModelType.valueOfName(measure.getModel()), accessToken));
}
return testCase;
}
Expand Down Expand Up @@ -760,13 +763,16 @@ public List<String> findTestCaseSeriesByMeasureId(String measureId) {
.collect(Collectors.toList());
}

public HapiOperationOutcome validateTestCaseJson(TestCase testCase, String accessToken) {
public HapiOperationOutcome validateTestCaseJson(
TestCase testCase, ModelType modelType, String accessToken) {
if (testCase == null || StringUtils.isBlank(testCase.getJson())) {
return null;
}

try {
return fhirServicesClient.validateBundle(testCase.getJson(), accessToken).getBody();
return fhirServicesClient
.validateBundle(testCase.getJson(), modelType, accessToken)
.getBody();
} catch (HttpClientErrorException ex) {
log.warn("HAPI FHIR returned response code [{}]", ex.getRawStatusCode(), ex);
try {
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ madie:
bundle-uri: /fhir/measures/bundles
export-uri: /fhir/measures/export
validation:
bundle-uri: /fhir/validations/bundles
bundle-uri: /fhir/validations/qicore/$model/bundles
test-cases: /fhir/test-cases
cql-elm:
service:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import cms.gov.madie.measure.services.VirusScanClient;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import gov.cms.madie.models.common.ModelType;
import gov.cms.madie.models.measure.HapiOperationOutcome;
import gov.cms.madie.models.scanner.ScanValidationDto;
import gov.cms.madie.models.scanner.VirusScanResponseDto;
Expand Down Expand Up @@ -48,7 +49,7 @@ class ValidationControllerTest {
@InjectMocks private ValidationController validationController;

@Captor ArgumentCaptor<String> testCaseJsonCaptor;

@Captor ArgumentCaptor<ModelType> testCaseModelCaptor;
@Captor ArgumentCaptor<String> accessTokenCaptor;

@Test
Expand All @@ -59,19 +60,23 @@ void testValidateBundleProxiesRequest() throws JsonProcessingException {
final String goodOutcomeJson = "{ \"code\": 200, \"successful\": true }";
HttpEntity<String> request = new HttpEntity<>(testCaseJson, headers);

when(fhirServicesClient.validateBundle(anyString(), anyString()))
when(fhirServicesClient.validateBundle(anyString(), any(ModelType.class), anyString()))
.thenReturn(
ResponseEntity.ok(HapiOperationOutcome.builder().code(200).successful(true).build()));

when(mapper.writeValueAsString(any())).thenReturn(goodOutcomeJson);

ResponseEntity<String> output = validationController.validateBundle(request, accessToken);
ResponseEntity<String> output =
validationController.validateBundle(request, ModelType.QI_CORE.getValue(), accessToken);

assertThat(output, is(notNullValue()));
assertThat(output.getBody(), is(notNullValue()));
assertThat(output.getBody(), is(equalTo(goodOutcomeJson)));
verify(fhirServicesClient, times(1))
.validateBundle(testCaseJsonCaptor.capture(), accessTokenCaptor.capture());
.validateBundle(
testCaseJsonCaptor.capture(),
testCaseModelCaptor.capture(),
accessTokenCaptor.capture());
assertThat(testCaseJsonCaptor.getValue(), is(equalTo(testCaseJson)));
assertThat(accessTokenCaptor.getValue(), is(equalTo(accessToken)));
}
Expand All @@ -83,13 +88,14 @@ void testValidateBundleBadRequest() throws JsonProcessingException {
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> request = new HttpEntity<>(testCaseJson, headers);

when(fhirServicesClient.validateBundle(anyString(), anyString()))
when(fhirServicesClient.validateBundle(anyString(), any(ModelType.class), anyString()))
.thenReturn(
ResponseEntity.ok(HapiOperationOutcome.builder().code(200).successful(true).build()));

when(mapper.writeValueAsString(any())).thenThrow(new JsonProcessingException("BadJson") {});

ResponseEntity<String> output = validationController.validateBundle(request, accessToken);
ResponseEntity<String> output =
validationController.validateBundle(request, ModelType.QI_CORE.getValue(), accessToken);

assertThat(output, is(notNullValue()));
assertThat(output.getStatusCode(), is(HttpStatus.BAD_REQUEST));
Expand All @@ -101,7 +107,10 @@ void testValidateBundleBadRequest() throws JsonProcessingException {
"Unable to validate test case JSON due to errors,"
+ " but outcome not able to be interpreted!")));
verify(fhirServicesClient, times(1))
.validateBundle(testCaseJsonCaptor.capture(), accessTokenCaptor.capture());
.validateBundle(
testCaseJsonCaptor.capture(),
testCaseModelCaptor.capture(),
accessTokenCaptor.capture());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import java.net.URI;
import java.util.List;

import cms.gov.madie.measure.exceptions.UnsupportedTypeException;
import gov.cms.madie.models.common.ModelType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -64,13 +66,12 @@ void beforeEach() {
lenient()
.when(fhirServicesConfig.getMadieFhirServiceValidateBundleUri())
.thenReturn("/api/fhir/validations/bundles");

when(fhirServicesConfig.fhirServicesRestTemplate()).thenReturn(restTemplate);
}

@Test
void testFhirServicesClientThrowsException() {
Measure measure = Measure.builder().build();
when(fhirServicesConfig.fhirServicesRestTemplate()).thenReturn(restTemplate);
when(fhirServicesConfig
.fhirServicesRestTemplate()
.exchange(any(URI.class), eq(HttpMethod.PUT), any(HttpEntity.class), any(Class.class)))
Expand All @@ -92,6 +93,7 @@ void testFhirServicesClientThrowsException() {
void testFhirServicesClientReturnsStringData() {
Measure measure = Measure.builder().build();
final String json = "{\"message\": \"GOOD JSON\"}";
when(fhirServicesConfig.fhirServicesRestTemplate()).thenReturn(restTemplate);
when(fhirServicesConfig
.fhirServicesRestTemplate()
.exchange(any(URI.class), eq(HttpMethod.PUT), any(HttpEntity.class), any(Class.class)))
Expand All @@ -111,14 +113,14 @@ void testFhirServicesClientReturnsStringData() {
@Test
void testValidateBundleThrowsException() {
final String testCaseJson = "{ \"resourceType\": \"foo\" }";

when(fhirServicesConfig.fhirServicesRestTemplate()).thenReturn(restTemplate);
when(fhirServicesConfig
.fhirServicesRestTemplate()
.exchange(any(URI.class), eq(HttpMethod.POST), any(HttpEntity.class), any(Class.class)))
.thenThrow(new HttpClientErrorException(HttpStatus.FORBIDDEN));
assertThrows(
HttpClientErrorException.class,
() -> fhirServicesClient.validateBundle(testCaseJson, accessToken));
() -> fhirServicesClient.validateBundle(testCaseJson, ModelType.QI_CORE, accessToken));
verify(fhirServicesConfig.fhirServicesRestTemplate(), times(1))
.exchange(
any(URI.class), eq(HttpMethod.POST), httpEntityCaptor.capture(), any(Class.class));
Expand All @@ -136,12 +138,13 @@ void testValidateBundleReturnsStringData() throws JsonProcessingException {
final HapiOperationOutcome goodOutcome =
HapiOperationOutcome.builder().code(200).successful(true).build();

when(fhirServicesConfig.fhirServicesRestTemplate()).thenReturn(restTemplate);
when(fhirServicesConfig
.fhirServicesRestTemplate()
.exchange(any(URI.class), eq(HttpMethod.POST), any(HttpEntity.class), any(Class.class)))
.thenReturn(ResponseEntity.ok(goodOutcome));
ResponseEntity<HapiOperationOutcome> output =
fhirServicesClient.validateBundle(testCaseJson, accessToken);
fhirServicesClient.validateBundle(testCaseJson, ModelType.QI_CORE, accessToken);
assertThat(output, is(notNullValue()));
assertThat(output.getBody(), is(notNullValue()));
assertThat(output.getBody(), is(equalTo(goodOutcome)));
Expand All @@ -165,6 +168,7 @@ void testGetTestCaseExports() {
.createdBy("testUser")
.cql("library Test1CQLLib version '2.3.001'")
.build();
when(fhirServicesConfig.fhirServicesRestTemplate()).thenReturn(restTemplate);
when(fhirServicesConfig
.fhirServicesRestTemplate()
.exchange(any(URI.class), eq(HttpMethod.PUT), any(HttpEntity.class), any(Class.class)))
Expand All @@ -186,6 +190,7 @@ void testGetTestCaseExportsException() {
.createdBy("testUser")
.cql("library Test1CQLLib version '2.3.001'")
.build();
when(fhirServicesConfig.fhirServicesRestTemplate()).thenReturn(restTemplate);
when(fhirServicesConfig
.fhirServicesRestTemplate()
.exchange(any(URI.class), eq(HttpMethod.PUT), any(HttpEntity.class), any(Class.class)))
Expand All @@ -197,4 +202,13 @@ void testGetTestCaseExportsException() {
measure, accessToken, asList("test-case-id-1", "test=case=id-2"), "COLLECTION");
assertThat(output.getStatusCode(), is(HttpStatus.NOT_FOUND));
}

@Test
void testValidateBundleIfModelIsNullThrowsException() {
Exception ex =
assertThrows(
UnsupportedTypeException.class,
() -> fhirServicesClient.validateBundle("test case json", null, accessToken));
assertThat(ex.getMessage(), is(equalTo("Please provide model type.")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ void getCreateMeasurePackageWhenQdmServiceReturnedErrors() {
when(qdmServiceRestTemplate.exchange(
any(URI.class), eq(HttpMethod.PUT), any(HttpEntity.class), any(Class.class)))
.thenThrow(new RestClientException("something went wrong"));
String errorMessage = "An unexpected error occurred while creating a measure package.something went wrong";
String errorMessage =
"An unexpected error occurred while creating a measure package.something went wrong";
Exception ex =
assertThrows(
InternalServerException.class,
Expand Down
Loading

0 comments on commit e71ba9f

Please sign in to comment.