diff --git a/pom.xml b/pom.xml index 7a26eb5..9b82c0f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,6 @@ 10.1 2.21.1 - org.springframework.boot @@ -60,27 +59,27 @@ lombok true - gov.cms.madie madie-java-models - 0.6.13-SNAPSHOT + 0.6.15-SNAPSHOT + + + gov.cms.madie.packaging + packaging-utility + 0.2.2 - org.springframework.boot spring-boot-starter-test test - org.springframework.security spring-security-test test - - @@ -91,7 +90,6 @@ https://maven.pkg.github.com/measureauthoringtool/madie-java-models - @@ -211,7 +209,6 @@ - org.apache.maven.plugins maven-site-plugin @@ -234,7 +231,6 @@ - org.apache.maven.plugins maven-compiler-plugin diff --git a/src/main/java/gov/cms/madie/Exceptions/TranslationServiceException.java b/src/main/java/gov/cms/madie/Exceptions/TranslationServiceException.java new file mode 100644 index 0000000..37f85fd --- /dev/null +++ b/src/main/java/gov/cms/madie/Exceptions/TranslationServiceException.java @@ -0,0 +1,8 @@ +package gov.cms.madie.Exceptions; + +public class TranslationServiceException extends RuntimeException { + + public TranslationServiceException(String message, Exception cause) { + super(message, cause); + } +} diff --git a/src/main/java/gov/cms/madie/Exceptions/UnsupportedModelException.java b/src/main/java/gov/cms/madie/Exceptions/UnsupportedModelException.java new file mode 100644 index 0000000..882aef6 --- /dev/null +++ b/src/main/java/gov/cms/madie/Exceptions/UnsupportedModelException.java @@ -0,0 +1,7 @@ +package gov.cms.madie.Exceptions; + +public class UnsupportedModelException extends RuntimeException { + public UnsupportedModelException(String message) { + super(message); + } +} diff --git a/src/main/java/gov/cms/madie/madieqdmservice/MadieQdmServiceApplication.java b/src/main/java/gov/cms/madie/MadieQdmServiceApplication.java similarity index 94% rename from src/main/java/gov/cms/madie/madieqdmservice/MadieQdmServiceApplication.java rename to src/main/java/gov/cms/madie/MadieQdmServiceApplication.java index d286ecf..f6b3667 100644 --- a/src/main/java/gov/cms/madie/madieqdmservice/MadieQdmServiceApplication.java +++ b/src/main/java/gov/cms/madie/MadieQdmServiceApplication.java @@ -1,6 +1,6 @@ -package gov.cms.madie.madieqdmservice; +package gov.cms.madie; -import gov.cms.madie.madieqdmservice.config.LogInterceptor; +import gov.cms.madie.config.LogInterceptor; import jakarta.validation.constraints.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; diff --git a/src/main/java/gov/cms/madie/config/CqlElmTranslatorClientConfig.java b/src/main/java/gov/cms/madie/config/CqlElmTranslatorClientConfig.java new file mode 100644 index 0000000..7a529fe --- /dev/null +++ b/src/main/java/gov/cms/madie/config/CqlElmTranslatorClientConfig.java @@ -0,0 +1,23 @@ +package gov.cms.madie.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Getter +@Configuration +public class CqlElmTranslatorClientConfig { + + @Value("${madie.cql-elm.service.base-url}") + private String baseUrl; + + @Value("${madie.cql-elm.service.cql-elm-urn}") + private String cqlElmUrn; + + @Bean + public RestTemplate elmTranslatorRestTemplate() { + return new RestTemplate(); + } +} diff --git a/src/main/java/gov/cms/madie/madieqdmservice/config/LogInterceptor.java b/src/main/java/gov/cms/madie/config/LogInterceptor.java similarity index 94% rename from src/main/java/gov/cms/madie/madieqdmservice/config/LogInterceptor.java rename to src/main/java/gov/cms/madie/config/LogInterceptor.java index 43ae18e..68468e0 100644 --- a/src/main/java/gov/cms/madie/madieqdmservice/config/LogInterceptor.java +++ b/src/main/java/gov/cms/madie/config/LogInterceptor.java @@ -1,4 +1,4 @@ -package gov.cms.madie.madieqdmservice.config; +package gov.cms.madie.config; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/gov/cms/madie/madieqdmservice/config/SecurityConfig.java b/src/main/java/gov/cms/madie/config/SecurityConfig.java similarity index 96% rename from src/main/java/gov/cms/madie/madieqdmservice/config/SecurityConfig.java rename to src/main/java/gov/cms/madie/config/SecurityConfig.java index 7577249..2aa1138 100644 --- a/src/main/java/gov/cms/madie/madieqdmservice/config/SecurityConfig.java +++ b/src/main/java/gov/cms/madie/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package gov.cms.madie.madieqdmservice.config; +package gov.cms.madie.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/gov/cms/madie/madieqdmservice/resources/PackageController.java b/src/main/java/gov/cms/madie/madieqdmservice/resources/PackageController.java deleted file mode 100644 index a2affc8..0000000 --- a/src/main/java/gov/cms/madie/madieqdmservice/resources/PackageController.java +++ /dev/null @@ -1,20 +0,0 @@ -package gov.cms.madie.madieqdmservice.resources; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Slf4j -@RestController -@RequestMapping(path = "/qdm/measures") -@RequiredArgsConstructor -public class PackageController { - - @GetMapping("/package") - public String getMeasurePackage() { - // TODO: implementation coming up soon - return "raw package contents"; - } -} diff --git a/src/main/java/gov/cms/madie/resources/CustomErrorHandlerAdvice.java b/src/main/java/gov/cms/madie/resources/CustomErrorHandlerAdvice.java new file mode 100644 index 0000000..34d9ef5 --- /dev/null +++ b/src/main/java/gov/cms/madie/resources/CustomErrorHandlerAdvice.java @@ -0,0 +1,38 @@ +package gov.cms.madie.resources; + +import gov.cms.madie.Exceptions.TranslationServiceException; +import gov.cms.madie.Exceptions.UnsupportedModelException; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.web.error.ErrorAttributeOptions; +import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.context.request.WebRequest; + +import java.util.Map; + +@ControllerAdvice +@RequiredArgsConstructor +public class CustomErrorHandlerAdvice { + private final ErrorAttributes errorAttributes; + + @ExceptionHandler({TranslationServiceException.class, UnsupportedModelException.class}) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + Map handleCustomException(WebRequest request) { + return getErrorAttributes(request, HttpStatus.BAD_REQUEST); + } + + private Map getErrorAttributes(WebRequest request, HttpStatus httpStatus) { + ErrorAttributeOptions errorOptions = + ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE); + Map errorAttributes = + this.errorAttributes.getErrorAttributes(request, errorOptions); + errorAttributes.put("status", httpStatus.value()); + errorAttributes.put("error", httpStatus.getReasonPhrase()); + return errorAttributes; + } +} diff --git a/src/main/java/gov/cms/madie/resources/PackageController.java b/src/main/java/gov/cms/madie/resources/PackageController.java new file mode 100644 index 0000000..6cbee8c --- /dev/null +++ b/src/main/java/gov/cms/madie/resources/PackageController.java @@ -0,0 +1,39 @@ +package gov.cms.madie.resources; + +import gov.cms.madie.Exceptions.UnsupportedModelException; +import gov.cms.madie.services.PackagingService; +import gov.cms.madie.models.measure.Measure; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping(path = "/qdm/measures") +@RequiredArgsConstructor +public class PackageController { + + private final PackagingService packagingService; + + @PutMapping( + value = "/package", + produces = { + MediaType.APPLICATION_OCTET_STREAM_VALUE, + }, + consumes = {MediaType.APPLICATION_JSON_VALUE}) + public byte[] getMeasurePackage( + @RequestBody @Validated(Measure.ValidationSequence.class) Measure measure, + @RequestHeader("Authorization") String accessToken) { + // generate package if the model type is QDM + if (measure.getModel() != null && measure.getModel().contains("QDM")) { + return packagingService.createMeasurePackage(measure, accessToken); + } + throw new UnsupportedModelException("Unsupported model type: " + measure.getModel()); + } +} diff --git a/src/main/java/gov/cms/madie/services/PackagingService.java b/src/main/java/gov/cms/madie/services/PackagingService.java new file mode 100644 index 0000000..86f7bc5 --- /dev/null +++ b/src/main/java/gov/cms/madie/services/PackagingService.java @@ -0,0 +1,42 @@ +package gov.cms.madie.services; + +import gov.cms.madie.models.dto.TranslatedLibrary; +import gov.cms.madie.models.measure.Measure; +import gov.cms.madie.packaging.utils.ZipUtility; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PackagingService { + private final TranslationServiceClient translationServiceClient; + + public byte[] createMeasurePackage(Measure measure, String accessToken) { + log.info("Creating the measure package for measure [{}]", measure.getId()); + List translatedLibraries = + translationServiceClient.getTranslatedLibraries(measure.getCql(), accessToken); + if (CollectionUtils.isEmpty(translatedLibraries)) { + return new byte[0]; + } + log.info("Adding measure package artifacts to the measure package"); + String resourcesDir = "resources/"; + String cqlDir = "cql/"; + Map entries = new HashMap<>(); + for (TranslatedLibrary translatedLibrary : translatedLibraries) { + String entryName = translatedLibrary.getName() + "-v" + translatedLibrary.getVersion(); + entries.put(resourcesDir + entryName + ".json", translatedLibrary.getElmJson().getBytes()); + entries.put(resourcesDir + entryName + ".xml", translatedLibrary.getElmXml().getBytes()); + entries.put(cqlDir + entryName + ".cql", translatedLibrary.getCql().getBytes()); + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + return new ZipUtility().zipEntries(entries, outputStream); + } +} diff --git a/src/main/java/gov/cms/madie/services/TranslationServiceClient.java b/src/main/java/gov/cms/madie/services/TranslationServiceClient.java new file mode 100644 index 0000000..0503f6f --- /dev/null +++ b/src/main/java/gov/cms/madie/services/TranslationServiceClient.java @@ -0,0 +1,44 @@ +package gov.cms.madie.services; + +import gov.cms.madie.Exceptions.TranslationServiceException; +import gov.cms.madie.config.CqlElmTranslatorClientConfig; +import gov.cms.madie.models.dto.TranslatedLibrary; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.List; + +@Slf4j +@Service +public record TranslationServiceClient( + CqlElmTranslatorClientConfig translatorClientConfig, RestTemplate elmTranslatorRestTemplate) { + + public List getTranslatedLibraries(String cql, String accessToken) { + URI uri = + URI.create(translatorClientConfig.getBaseUrl() + translatorClientConfig.getCqlElmUrn()); + HttpHeaders headers = new HttpHeaders(); + headers.set(HttpHeaders.AUTHORIZATION, accessToken); + headers.set(HttpHeaders.ACCEPT, MediaType.ALL_VALUE); + headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + HttpEntity entity = new HttpEntity<>(cql, headers); + ParameterizedTypeReference> responseType = + new ParameterizedTypeReference<>() {}; + try { + log.info("fetching the elm for measure cql & included libraries"); + return elmTranslatorRestTemplate + .exchange(uri, HttpMethod.PUT, entity, responseType) + .getBody(); + } catch (Exception ex) { + String msg = "An issue occurred while fetching the translated artifacts for measure cql"; + log.error(msg, ex); + throw new TranslationServiceException(msg, ex); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3ba2de6..fc6ab43 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,6 +5,10 @@ server: madie: allowedApi: http://localhost:9000 + cql-elm: + service: + base-url: ${ELM_TRANSLATOR_SERVICE_URL:http://localhost:8084/api} + cql-elm-urn: /cql/elm management: endpoints: diff --git a/src/test/java/gov/cms/madie/madieqdmservice/PackageControllerMvcTest.java b/src/test/java/gov/cms/madie/madieqdmservice/PackageControllerMvcTest.java deleted file mode 100644 index f4c2a18..0000000 --- a/src/test/java/gov/cms/madie/madieqdmservice/PackageControllerMvcTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package gov.cms.madie.madieqdmservice; - -import gov.cms.madie.madieqdmservice.resources.PackageController; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@WebMvcTest({PackageController.class}) -public class PackageControllerMvcTest { - - @Autowired private MockMvc mockMvc; - - private static final String TEST_USER_ID = "john_doe"; - - @Test - public void testGetPackage() throws Exception { - MvcResult result = - mockMvc - .perform( - MockMvcRequestBuilders.get("/qdm/measures/package") - .with(user(TEST_USER_ID)) - .with(csrf()) - .header(HttpHeaders.AUTHORIZATION, "test-okta") - .contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect(status().isOk()) - .andReturn(); - assertThat(result.getResponse().getContentAsString(), is(equalTo("raw package contents"))); - } -} diff --git a/src/test/java/gov/cms/madie/madieqdmservice/PackageControllerTest.java b/src/test/java/gov/cms/madie/madieqdmservice/PackageControllerTest.java deleted file mode 100644 index 1fe6300..0000000 --- a/src/test/java/gov/cms/madie/madieqdmservice/PackageControllerTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package gov.cms.madie.madieqdmservice; - -import gov.cms.madie.madieqdmservice.resources.PackageController; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.junit.jupiter.MockitoExtension; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -@ExtendWith(MockitoExtension.class) -public class PackageControllerTest { - - @InjectMocks private PackageController packageController; - - @Test - public void testGetPackage() { - assertThat(packageController.getMeasurePackage(), is(equalTo("raw package contents"))); - } -} diff --git a/src/test/java/gov/cms/madie/resources/PackageControllerMvcTest.java b/src/test/java/gov/cms/madie/resources/PackageControllerMvcTest.java new file mode 100644 index 0000000..2dd9cf7 --- /dev/null +++ b/src/test/java/gov/cms/madie/resources/PackageControllerMvcTest.java @@ -0,0 +1,73 @@ +package gov.cms.madie.resources; + +import gov.cms.madie.services.PackagingService; +import gov.cms.madie.models.measure.Measure; +import gov.cms.madie.packaging.utils.ResourceFileUtil; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest({PackageController.class}) +class PackageControllerMvcTest implements ResourceFileUtil { + + @MockBean private PackagingService packagingService; + @Autowired private MockMvc mockMvc; + + private static final String TEST_USER_ID = "john_doe"; + private static final String TOKEN = "test-okta"; + + @Test + void testGetMeasurePackage() throws Exception { + String measureJson = getStringFromTestResource("/measures/qdm-test-measure.json"); + Mockito.when(packagingService.createMeasurePackage(new Measure(), TOKEN)) + .thenReturn("measure package".getBytes()); + mockMvc + .perform( + MockMvcRequestBuilders.put("/qdm/measures/package") + .with(user(TEST_USER_ID)) + .with(csrf()) + .header(HttpHeaders.AUTHORIZATION, TOKEN) + .content(measureJson) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andReturn(); + verify(packagingService, times(1)).createMeasurePackage(any(Measure.class), anyString()); + } + + @Test + void testGetMeasurePackageForUnsupportedModel() throws Exception { + String measureJson = getStringFromTestResource("/measures/qicore-test-measure.json"); + MvcResult mockResult = + mockMvc + .perform( + MockMvcRequestBuilders.put("/qdm/measures/package") + .with(user(TEST_USER_ID)) + .with(csrf()) + .header(HttpHeaders.AUTHORIZATION, TOKEN) + .content(measureJson) + .contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isBadRequest()) + .andReturn(); + assertThat( + mockResult.getResolvedException().getMessage(), + is(equalTo("Unsupported model type: QI-Core v4.1.1"))); + } +} diff --git a/src/test/java/gov/cms/madie/resources/PackageControllerTest.java b/src/test/java/gov/cms/madie/resources/PackageControllerTest.java new file mode 100644 index 0000000..ef01b1e --- /dev/null +++ b/src/test/java/gov/cms/madie/resources/PackageControllerTest.java @@ -0,0 +1,71 @@ +package gov.cms.madie.resources; + +import gov.cms.madie.Exceptions.UnsupportedModelException; +import gov.cms.madie.services.PackagingService; +import gov.cms.madie.models.common.ModelType; +import gov.cms.madie.models.measure.Measure; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@ExtendWith(MockitoExtension.class) +class PackageControllerTest { + + @Mock private PackagingService packagingService; + @InjectMocks private PackageController packageController; + + private static final String TOKEN = "test token"; + private Measure measure; + + @BeforeEach + void setup() { + measure = + Measure.builder() + .id("1") + .ecqmTitle("test") + .model(String.valueOf(ModelType.QDM_5_6)) + .build(); + } + + @Test + void testGetMeasurePackage() { + String measurePackage = "measure package"; + Mockito.when(packagingService.createMeasurePackage(measure, TOKEN)) + .thenReturn(measurePackage.getBytes()); + byte[] rawPackage = packageController.getMeasurePackage(measure, TOKEN); + assertThat(new String(rawPackage), is(equalTo(measurePackage))); + } + + @Test + void testGetMeasurePackageIfModelIsNull() { + measure.setModel(null); + String errorMessage = "Unsupported model type: " + measure.getModel(); + Exception ex = + Assertions.assertThrows( + UnsupportedModelException.class, + () -> packageController.getMeasurePackage(measure, TOKEN), + errorMessage); + assertThat(ex.getMessage(), is(equalTo(errorMessage))); + } + + @Test + void testGetMeasurePackageForUnsupportedModel() { + measure.setModel(String.valueOf(ModelType.QI_CORE)); + String errorMessage = "Unsupported model type: " + measure.getModel(); + Exception ex = + Assertions.assertThrows( + UnsupportedModelException.class, + () -> packageController.getMeasurePackage(measure, TOKEN), + errorMessage); + assertThat(ex.getMessage(), is(equalTo(errorMessage))); + } +} diff --git a/src/test/java/gov/cms/madie/services/PackagingServiceTest.java b/src/test/java/gov/cms/madie/services/PackagingServiceTest.java new file mode 100644 index 0000000..9f14a4a --- /dev/null +++ b/src/test/java/gov/cms/madie/services/PackagingServiceTest.java @@ -0,0 +1,90 @@ +package gov.cms.madie.services; + +import gov.cms.madie.Exceptions.TranslationServiceException; +import gov.cms.madie.models.common.ModelType; +import gov.cms.madie.models.dto.TranslatedLibrary; +import gov.cms.madie.models.measure.Measure; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class PackagingServiceTest { + @Mock private TranslationServiceClient translationServiceClient; + @InjectMocks private PackagingService packagingService; + + private static final String TOKEN = "test token"; + private Measure measure; + + @BeforeEach + void setUp() throws Exception { + measure = + Measure.builder() + .id("1") + .ecqmTitle("test") + .cql("fake cql") + .model(String.valueOf(ModelType.QDM_5_6)) + .build(); + } + + @Test + void testCreateMeasurePackage() { + TranslatedLibrary library1 = + TranslatedLibrary.builder() + .name("Lib one") + .version("0.0.000") + .elmJson("elm xml") + .elmXml("elm xml") + .cql("cql") + .build(); + TranslatedLibrary library2 = + TranslatedLibrary.builder() + .name("Lib two") + .version("0.0.001") + .elmJson("elm xml") + .elmXml("elm xml") + .cql("cql") + .build(); + when(translationServiceClient.getTranslatedLibraries(measure.getCql(), TOKEN)) + .thenReturn(List.of(library1, library2)); + byte[] packageContents = packagingService.createMeasurePackage(measure, TOKEN); + assertNotNull(packageContents); + } + + @Test + void testCreateMeasurePackageWhenNoLibFound() { + when(translationServiceClient.getTranslatedLibraries(measure.getCql(), TOKEN)) + .thenReturn(List.of()); + byte[] packageContents = packagingService.createMeasurePackage(measure, TOKEN); + assertThat(packageContents.length, is(equalTo(0))); + } + + @Test + void testCreateMeasurePackageWhenTranslationFailed() { + String msg = "An issue occurred while fetching the translated artifacts for measure cql"; + Mockito.doThrow(new TranslationServiceException(msg, new Exception())) + .when(translationServiceClient) + .getTranslatedLibraries(anyString(), anyString()); + Exception exception = + assertThrows( + TranslationServiceException.class, + () -> packagingService.createMeasurePackage(measure, TOKEN), + msg); + assertThat(exception.getMessage(), containsString(msg)); + } +} diff --git a/src/test/java/gov/cms/madie/services/TranslationServiceClientTest.java b/src/test/java/gov/cms/madie/services/TranslationServiceClientTest.java new file mode 100644 index 0000000..a508033 --- /dev/null +++ b/src/test/java/gov/cms/madie/services/TranslationServiceClientTest.java @@ -0,0 +1,73 @@ +package gov.cms.madie.services; + +import gov.cms.madie.Exceptions.TranslationServiceException; +import gov.cms.madie.config.CqlElmTranslatorClientConfig; +import gov.cms.madie.models.dto.TranslatedLibrary; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.List; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TranslationServiceClientTest { + + @Mock private CqlElmTranslatorClientConfig translatorClientConfig; + @Mock private RestTemplate elmTranslatorRestTemplate; + @InjectMocks private TranslationServiceClient translationServiceClient; + + @Test + void getTranslatedLibraries() { + TranslatedLibrary library1 = + TranslatedLibrary.builder().name("one").cql("cql").elmJson("json").build(); + TranslatedLibrary library2 = + TranslatedLibrary.builder().name("two").cql("cql").elmJson("json").build(); + when(translatorClientConfig.getBaseUrl()).thenReturn("baseurl"); + when(translatorClientConfig.getCqlElmUrn()).thenReturn("/elm/uri"); + when(elmTranslatorRestTemplate.exchange( + any(URI.class), + eq(HttpMethod.PUT), + any(HttpEntity.class), + any(ParameterizedTypeReference.class))) + .thenReturn(ResponseEntity.ok(List.of(library1, library2))); + List translatedLibraries = + translationServiceClient.getTranslatedLibraries("cql", "token"); + assertThat(translatedLibraries.size(), is(equalTo(2))); + assertThat(translatedLibraries.get(0).getName(), is(equalTo(library1.getName()))); + assertThat(translatedLibraries.get(1).getName(), is(equalTo(library2.getName()))); + } + + @Test + void getTranslatedLibrariesWhenTranslationServiceFailed() { + String message = "An issue occurred while fetching the translated artifacts for measure cql"; + when(elmTranslatorRestTemplate.exchange( + any(URI.class), + eq(HttpMethod.PUT), + any(HttpEntity.class), + any(ParameterizedTypeReference.class))) + .thenThrow(new TranslationServiceException(message, new Exception())); + Exception ex = + assertThrows( + TranslationServiceException.class, + () -> translationServiceClient.getTranslatedLibraries("cql", "token"), + message); + assertThat(ex.getMessage(), containsString(message)); + } +} diff --git a/src/test/resources/measures/qdm-test-measure.json b/src/test/resources/measures/qdm-test-measure.json new file mode 100644 index 0000000..106c79f --- /dev/null +++ b/src/test/resources/measures/qdm-test-measure.json @@ -0,0 +1,106 @@ +{ + "id": "6564ac8a5159a90bd713bea5", + "measureHumanReadableId": null, + "measureSetId": "3abdaa71-3683-4afe-829f-278cadc82950", + "version": "0.0.000", + "revisionNumber": null, + "state": null, + "cqlLibraryName": "QDMTestMeasure", + "ecqmTitle": "QDMTestMeasure", + "measureName": "QDMTestMeausre", + "active": true, + "cqlErrors": false, + "errors": [], + "cql": "library QDMTestMeasure version '0.0.000'\n\nusing QDM version '5.6'\n\ninclude TestMATGlobal version '1.0.000' called Global\n\ncodesystem \"SNOMEDCT\": 'urn:oid:2.16.840.1.113883.6.96' \n\nvalueset \"Encounter Inpatient\": 'urn:oid:2.16.840.1.113883.3.666.5.307'\n\ncode \"Dead (finding)\": '419099009' from \"SNOMEDCT\" display 'Dead (finding)'\n\nparameter \"Measurement Period\" Interval\n\ncontext Patient\n\n \ndefine \"Initial Population\":\n \"Qualifying Encounters\"\n\ndefine \"Qualifying Encounters\":\n [\"Encounter, Performed\": \"Encounter Inpatient\"] Encounter\n where Encounter.relevantPeriod ends during \"Measurement Period\"\n ", + "elmJson": "{\n \"library\" : {\n \"annotation\" : [ ],\n \"identifier\" : {\n \"id\" : \"QDMTestMeasure\",\n \"version\" : \"0.0.000\"\n },\n \"schemaIdentifier\" : {\n \"id\" : \"urn:hl7-org:elm\",\n \"version\" : \"r1\"\n },\n \"usings\" : {\n \"def\" : [ {\n \"localIdentifier\" : \"System\",\n \"uri\" : \"urn:hl7-org:elm-types:r1\"\n }, {\n \"localId\" : \"1\",\n \"locator\" : \"3:1-3:23\",\n \"localIdentifier\" : \"QDM\",\n \"uri\" : \"urn:healthit-gov:qdm:v5_6\",\n \"version\" : \"5.6\",\n \"annotation\" : [ {\n \"type\" : \"Annotation\",\n \"s\" : {\n \"r\" : \"1\",\n \"s\" : [ {\n \"value\" : [ \"\", \"using \" ]\n }, {\n \"s\" : [ {\n \"value\" : [ \"QDM\" ]\n } ]\n }, {\n \"value\" : [ \" version '5.6'\" ]\n } ]\n }\n } ]\n } ]\n },\n \"includes\" : {\n \"def\" : [ {\n \"localId\" : \"2\",\n \"locator\" : \"5:1-5:53\",\n \"localIdentifier\" : \"Global\",\n \"path\" : \"TestMATGlobal\",\n \"version\" : \"1.0.000\",\n \"annotation\" : [ {\n \"type\" : \"Annotation\",\n \"s\" : {\n \"r\" : \"2\",\n \"s\" : [ {\n \"value\" : [ \"\", \"include \" ]\n }, {\n \"s\" : [ {\n \"value\" : [ \"TestMATGlobal\" ]\n } ]\n }, {\n \"value\" : [ \" version \", \"'1.0.000'\", \" called \", \"Global\" ]\n } ]\n }\n } ]\n } ]\n },\n \"parameters\" : {\n \"def\" : [ {\n \"localId\" : \"9\",\n \"locator\" : \"13:1-13:49\",\n \"name\" : \"Measurement Period\",\n \"accessLevel\" : \"Public\",\n \"annotation\" : [ {\n \"type\" : \"Annotation\",\n \"s\" : {\n \"r\" : \"9\",\n \"s\" : [ {\n \"value\" : [ \"\", \"parameter \", \"\\\"Measurement Period\\\"\", \" \" ]\n }, {\n \"r\" : \"8\",\n \"s\" : [ {\n \"value\" : [ \"Interval<\" ]\n }, {\n \"r\" : \"7\",\n \"s\" : [ {\n \"value\" : [ \"DateTime\" ]\n } ]\n }, {\n \"value\" : [ \">\" ]\n } ]\n } ]\n }\n } ],\n \"resultTypeSpecifier\" : {\n \"type\" : \"IntervalTypeSpecifier\",\n \"pointType\" : {\n \"name\" : \"{urn:hl7-org:elm-types:r1}DateTime\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n },\n \"parameterTypeSpecifier\" : {\n \"localId\" : \"8\",\n \"locator\" : \"13:32-13:49\",\n \"type\" : \"IntervalTypeSpecifier\",\n \"resultTypeSpecifier\" : {\n \"type\" : \"IntervalTypeSpecifier\",\n \"pointType\" : {\n \"name\" : \"{urn:hl7-org:elm-types:r1}DateTime\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n },\n \"pointType\" : {\n \"localId\" : \"7\",\n \"locator\" : \"13:41-13:48\",\n \"resultTypeName\" : \"{urn:hl7-org:elm-types:r1}DateTime\",\n \"name\" : \"{urn:hl7-org:elm-types:r1}DateTime\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n }\n } ]\n },\n \"codeSystems\" : {\n \"def\" : [ {\n \"localId\" : \"3\",\n \"locator\" : \"7:1-7:55\",\n \"resultTypeName\" : \"{urn:hl7-org:elm-types:r1}CodeSystem\",\n \"name\" : \"SNOMEDCT\",\n \"id\" : \"urn:oid:2.16.840.1.113883.6.96\",\n \"accessLevel\" : \"Public\",\n \"annotation\" : [ {\n \"type\" : \"Annotation\",\n \"s\" : {\n \"r\" : \"3\",\n \"s\" : [ {\n \"value\" : [ \"\", \"codesystem \", \"\\\"SNOMEDCT\\\"\", \": \", \"'urn:oid:2.16.840.1.113883.6.96'\" ]\n } ]\n }\n } ]\n } ]\n },\n \"valueSets\" : {\n \"def\" : [ {\n \"localId\" : \"4\",\n \"locator\" : \"9:1-9:71\",\n \"resultTypeName\" : \"{urn:hl7-org:elm-types:r1}ValueSet\",\n \"name\" : \"Encounter Inpatient\",\n \"id\" : \"urn:oid:2.16.840.1.113883.3.666.5.307\",\n \"accessLevel\" : \"Public\",\n \"annotation\" : [ {\n \"type\" : \"Annotation\",\n \"s\" : {\n \"r\" : \"4\",\n \"s\" : [ {\n \"value\" : [ \"\", \"valueset \", \"\\\"Encounter Inpatient\\\"\", \": \", \"'urn:oid:2.16.840.1.113883.3.666.5.307'\" ]\n } ]\n }\n } ]\n } ]\n },\n \"codes\" : {\n \"def\" : [ {\n \"localId\" : \"6\",\n \"locator\" : \"11:1-11:75\",\n \"resultTypeName\" : \"{urn:hl7-org:elm-types:r1}Code\",\n \"name\" : \"Dead (finding)\",\n \"id\" : \"419099009\",\n \"display\" : \"Dead (finding)\",\n \"accessLevel\" : \"Public\",\n \"annotation\" : [ {\n \"type\" : \"Annotation\",\n \"s\" : {\n \"r\" : \"6\",\n \"s\" : [ {\n \"value\" : [ \"\", \"code \", \"\\\"Dead (finding)\\\"\", \": \", \"'419099009'\", \" from \" ]\n }, {\n \"r\" : \"5\",\n \"s\" : [ {\n \"value\" : [ \"\\\"SNOMEDCT\\\"\" ]\n } ]\n }, {\n \"value\" : [ \" display \", \"'Dead (finding)'\" ]\n } ]\n }\n } ],\n \"codeSystem\" : {\n \"localId\" : \"5\",\n \"locator\" : \"11:41-11:50\",\n \"resultTypeName\" : \"{urn:hl7-org:elm-types:r1}CodeSystem\",\n \"name\" : \"SNOMEDCT\"\n }\n } ]\n },\n \"contexts\" : {\n \"def\" : [ {\n \"locator\" : \"15:1-15:15\",\n \"name\" : \"Patient\"\n } ]\n },\n \"statements\" : {\n \"def\" : [ {\n \"locator\" : \"15:1-15:15\",\n \"name\" : \"Patient\",\n \"context\" : \"Patient\",\n \"expression\" : {\n \"type\" : \"SingletonFrom\",\n \"operand\" : {\n \"locator\" : \"15:1-15:15\",\n \"dataType\" : \"{urn:healthit-gov:qdm:v5_6}Patient\",\n \"templateId\" : \"Patient\",\n \"type\" : \"Retrieve\"\n }\n }\n }, {\n \"localId\" : \"17\",\n \"locator\" : \"21:1-23:67\",\n \"name\" : \"Qualifying Encounters\",\n \"context\" : \"Patient\",\n \"accessLevel\" : \"Public\",\n \"annotation\" : [ {\n \"type\" : \"Annotation\",\n \"s\" : {\n \"r\" : \"17\",\n \"s\" : [ {\n \"value\" : [ \"\", \"define \", \"\\\"Qualifying Encounters\\\"\", \":\\n \" ]\n }, {\n \"r\" : \"16\",\n \"s\" : [ {\n \"s\" : [ {\n \"r\" : \"11\",\n \"s\" : [ {\n \"r\" : \"10\",\n \"s\" : [ {\n \"r\" : \"10\",\n \"s\" : [ {\n \"value\" : [ \"[\", \"\\\"Encounter, Performed\\\"\", \": \" ]\n }, {\n \"s\" : [ {\n \"value\" : [ \"\\\"Encounter Inpatient\\\"\" ]\n } ]\n }, {\n \"value\" : [ \"]\" ]\n } ]\n } ]\n }, {\n \"value\" : [ \" \", \"Encounter\" ]\n } ]\n } ]\n }, {\n \"value\" : [ \"\\n \" ]\n }, {\n \"r\" : \"15\",\n \"s\" : [ {\n \"value\" : [ \"where \" ]\n }, {\n \"r\" : \"15\",\n \"s\" : [ {\n \"r\" : \"13\",\n \"s\" : [ {\n \"r\" : \"12\",\n \"s\" : [ {\n \"value\" : [ \"Encounter\" ]\n } ]\n }, {\n \"value\" : [ \".\" ]\n }, {\n \"r\" : \"13\",\n \"s\" : [ {\n \"value\" : [ \"relevantPeriod\" ]\n } ]\n } ]\n }, {\n \"r\" : \"15\",\n \"value\" : [ \" \", \"ends during\", \" \" ]\n }, {\n \"r\" : \"14\",\n \"s\" : [ {\n \"value\" : [ \"\\\"Measurement Period\\\"\" ]\n } ]\n } ]\n } ]\n } ]\n } ]\n }\n } ],\n \"resultTypeSpecifier\" : {\n \"type\" : \"ListTypeSpecifier\",\n \"elementType\" : {\n \"name\" : \"{urn:healthit-gov:qdm:v5_6}PositiveEncounterPerformed\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n },\n \"expression\" : {\n \"localId\" : \"16\",\n \"locator\" : \"22:3-23:67\",\n \"type\" : \"Query\",\n \"resultTypeSpecifier\" : {\n \"type\" : \"ListTypeSpecifier\",\n \"elementType\" : {\n \"name\" : \"{urn:healthit-gov:qdm:v5_6}PositiveEncounterPerformed\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n },\n \"source\" : [ {\n \"localId\" : \"11\",\n \"locator\" : \"22:3-22:59\",\n \"alias\" : \"Encounter\",\n \"resultTypeSpecifier\" : {\n \"type\" : \"ListTypeSpecifier\",\n \"elementType\" : {\n \"name\" : \"{urn:healthit-gov:qdm:v5_6}PositiveEncounterPerformed\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n },\n \"expression\" : {\n \"localId\" : \"10\",\n \"locator\" : \"22:3-22:49\",\n \"dataType\" : \"{urn:healthit-gov:qdm:v5_6}PositiveEncounterPerformed\",\n \"templateId\" : \"PositiveEncounterPerformed\",\n \"codeProperty\" : \"code\",\n \"codeComparator\" : \"in\",\n \"type\" : \"Retrieve\",\n \"resultTypeSpecifier\" : {\n \"type\" : \"ListTypeSpecifier\",\n \"elementType\" : {\n \"name\" : \"{urn:healthit-gov:qdm:v5_6}PositiveEncounterPerformed\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n },\n \"codes\" : {\n \"locator\" : \"22:28-22:48\",\n \"resultTypeName\" : \"{urn:hl7-org:elm-types:r1}ValueSet\",\n \"name\" : \"Encounter Inpatient\",\n \"preserve\" : true,\n \"type\" : \"ValueSetRef\"\n }\n }\n } ],\n \"relationship\" : [ ],\n \"where\" : {\n \"localId\" : \"15\",\n \"locator\" : \"23:5-23:67\",\n \"resultTypeName\" : \"{urn:hl7-org:elm-types:r1}Boolean\",\n \"type\" : \"In\",\n \"operand\" : [ {\n \"locator\" : \"23:36-23:39\",\n \"type\" : \"End\",\n \"operand\" : {\n \"localId\" : \"13\",\n \"locator\" : \"23:11-23:34\",\n \"path\" : \"relevantPeriod\",\n \"scope\" : \"Encounter\",\n \"type\" : \"Property\",\n \"resultTypeSpecifier\" : {\n \"type\" : \"IntervalTypeSpecifier\",\n \"pointType\" : {\n \"name\" : \"{urn:hl7-org:elm-types:r1}DateTime\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n }\n }\n }, {\n \"localId\" : \"14\",\n \"locator\" : \"23:48-23:67\",\n \"name\" : \"Measurement Period\",\n \"type\" : \"ParameterRef\",\n \"resultTypeSpecifier\" : {\n \"type\" : \"IntervalTypeSpecifier\",\n \"pointType\" : {\n \"name\" : \"{urn:hl7-org:elm-types:r1}DateTime\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n }\n } ]\n }\n }\n }, {\n \"localId\" : \"19\",\n \"locator\" : \"18:1-19:25\",\n \"name\" : \"Initial Population\",\n \"context\" : \"Patient\",\n \"accessLevel\" : \"Public\",\n \"annotation\" : [ {\n \"type\" : \"Annotation\",\n \"s\" : {\n \"r\" : \"19\",\n \"s\" : [ {\n \"value\" : [ \"\", \"define \", \"\\\"Initial Population\\\"\", \":\\n \" ]\n }, {\n \"r\" : \"18\",\n \"s\" : [ {\n \"value\" : [ \"\\\"Qualifying Encounters\\\"\" ]\n } ]\n } ]\n }\n } ],\n \"resultTypeSpecifier\" : {\n \"type\" : \"ListTypeSpecifier\",\n \"elementType\" : {\n \"name\" : \"{urn:healthit-gov:qdm:v5_6}PositiveEncounterPerformed\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n },\n \"expression\" : {\n \"localId\" : \"18\",\n \"locator\" : \"19:3-19:25\",\n \"name\" : \"Qualifying Encounters\",\n \"type\" : \"ExpressionRef\",\n \"resultTypeSpecifier\" : {\n \"type\" : \"ListTypeSpecifier\",\n \"elementType\" : {\n \"name\" : \"{urn:healthit-gov:qdm:v5_6}PositiveEncounterPerformed\",\n \"type\" : \"NamedTypeSpecifier\"\n }\n }\n }\n } ]\n }\n },\n \"externalErrors\" : [ ]\n}", + "elmXml": null, + "testCases": [ + { + "id": "6564acb75159a90bd713bea8", + "name": null, + "title": "Test", + "series": "", + "description": "", + "createdAt": "2023-11-27T14:50:31.284Z", + "createdBy": "john doe", + "lastModifiedAt": "2023-12-19T23:08:44.365Z", + "lastModifiedBy": "John doe", + "validResource": true, + "json": "{\"qdmVersion\":\"5.6\",\"dataElements\":[{\"dataElementCodes\":[],\"_id\":\"658222799d67250000a656eb\",\"participant\":[],\"relatedTo\":[],\"qdmTitle\":\"Encounter, Performed\",\"hqmfOid\":\"2.16.840.1.113883.10.20.28.4.5\",\"qdmCategory\":\"encounter\",\"qdmStatus\":\"performed\",\"qdmVersion\":\"5.6\",\"_type\":\"QDM::EncounterPerformed\",\"description\":\"Encounter, Performed: Encounter Inpatient\",\"codeListId\":\"2.16.840.1.113883.3.666.5.307\",\"id\":\"658222799d67250000a656ea\",\"facilityLocations\":[],\"diagnoses\":[]}],\"_id\":\"658222789d67250000a656cd\"}", + "patientId": "41dd75cc-57fb-456a-9eb9-598798db7817", + "hapiOperationOutcome": null, + "groupPopulations": [] + } + ], + "groups": [ + { + "id": "6564acab5159a90bd713bea7", + "scoring": "Cohort", + "populations": [ + { + "id": "f341c318-9b30-4b7c-b642-d29d5980ea6d", + "name": "initialPopulation", + "definition": "Initial Population", + "associationType": null, + "description": "" + } + ], + "measureObservations": null, + "groupDescription": "", + "improvementNotation": "", + "rateAggregation": "", + "measureGroupTypes": null, + "scoringUnit": "", + "stratifications": [], + "populationBasis": "false" + } + ], + "createdAt": "2023-11-27T14:49:46.742Z", + "createdBy": "John doe", + "lastModifiedAt": "2023-12-18T16:38:12.125Z", + "lastModifiedBy": "John doe", + "measurementPeriodStart": "2020-01-01T00:00:00.000+00:00", + "measurementPeriodEnd": "2020-12-31T23:59:59.999+00:00", + "supplementalData": [], + "supplementalDataDescription": null, + "riskAdjustments": [], + "riskAdjustmentDescription": null, + "model": "QDM v5.6", + "measureMetaData": { + "steward": null, + "developers": null, + "description": null, + "copyright": null, + "disclaimer": null, + "rationale": null, + "guidance": null, + "clinicalRecommendation": null, + "draft": true, + "references": null, + "endorsements": null, + "riskAdjustment": null, + "definition": null, + "experimental": true, + "transmissionFormat": null, + "supplementalDataElements": null + }, + "versionId": "b84bf535-e179-49d1-8871-c08056358275", + "cmsId": null, + "reviewMetaData": { + "approvalDate": null, + "lastReviewDate": null + }, + "measureSet": { + "id": "6564ac8a5159a90bd713bea6", + "measureSetId": "3abdaa71-3683-4afe-829f-278cadc82950", + "owner": "john_doe", + "acls": null + }, + "scoring": "Cohort", + "baseConfigurationTypes": [ + "Efficiency" + ], + "patientBasis": false, + "rateAggregation": null, + "improvementNotation": null +} \ No newline at end of file diff --git a/src/test/resources/measures/qicore-test-measure.json b/src/test/resources/measures/qicore-test-measure.json new file mode 100644 index 0000000..de3ae09 --- /dev/null +++ b/src/test/resources/measures/qicore-test-measure.json @@ -0,0 +1,68 @@ +{ + "id": "6564ac8a5159a90bd713bea5", + "measureHumanReadableId": null, + "measureSetId": "3abdaa71-3683-4afe-829f-278cadc82950", + "version": "0.0.000", + "revisionNumber": null, + "state": null, + "cqlLibraryName": "QICoreTestMeasure", + "ecqmTitle": "QICoreTestMeasure", + "measureName": "QICoreTestMeasure", + "active": true, + "cqlErrors": false, + "errors": [], + "cql": "fake cql", + "elmJson": null, + "elmXml": null, + "testCases": [], + "groups": [ + ], + "createdAt": "2023-11-27T14:49:46.742Z", + "createdBy": "john doe", + "lastModifiedAt": "2023-12-18T16:38:12.125Z", + "lastModifiedBy": "John doe", + "measurementPeriodStart": "2020-01-01T00:00:00.000+00:00", + "measurementPeriodEnd": "2020-12-31T23:59:59.999+00:00", + "supplementalData": [], + "supplementalDataDescription": null, + "riskAdjustments": [], + "riskAdjustmentDescription": null, + "model": "QI-Core v4.1.1", + "measureMetaData": { + "steward": null, + "developers": null, + "description": null, + "copyright": null, + "disclaimer": null, + "rationale": null, + "guidance": null, + "clinicalRecommendation": null, + "draft": true, + "references": null, + "endorsements": null, + "riskAdjustment": null, + "definition": null, + "experimental": true, + "transmissionFormat": null, + "supplementalDataElements": null + }, + "versionId": "b84bf535-e179-49d1-8871-c08056358275", + "cmsId": null, + "reviewMetaData": { + "approvalDate": null, + "lastReviewDate": null + }, + "measureSet": { + "id": "6564ac8a5159a90bd713bea6", + "measureSetId": "3abdaa71-3683-4afe-829f-278cadc82950", + "owner": "john_doe", + "acls": null + }, + "scoring": "Cohort", + "baseConfigurationTypes": [ + "Efficiency" + ], + "patientBasis": false, + "rateAggregation": null, + "improvementNotation": null +} \ No newline at end of file