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