Skip to content

Commit 2c4e318

Browse files
Merge branch 'develop' into MAT-8732/adminApiRestartValidations
2 parents ba47268 + ba5e001 commit 2c4e318

5 files changed

Lines changed: 104 additions & 37 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<name>Measure Service</name>
1515
<description>Measure Service for MADiE</description>
1616
<properties>
17-
<cqframework.version>3.22.0</cqframework.version>
17+
<cqframework.version>3.27.0</cqframework.version>
1818
<java.version>17</java.version>
1919
<junit-jupiter.version>5.10.3</junit-jupiter.version>
2020
<mvn.checkstyle.file>madie-checkstyle.xml</mvn.checkstyle.file>

src/main/java/cms/gov/madie/measure/services/ElmTranslatorClient.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public ElmJson getElmJson(final String cql, String measureModel, String accessTo
3535
public ElmJson getElmJson(
3636
final String cql, String measureModel, String elmErrorSeverity, String accessToken) {
3737
try {
38+
// TODO CqlCompilerException is the sole reason for this project to rely on cql-to-elm
39+
// dependency.. we could expose this value from madie-models instead
3840
URI uri =
3941
getElmJsonURI(measureModel, CqlCompilerException.ErrorSeverity.valueOf(elmErrorSeverity));
4042
HttpEntity<String> cqlEntity = getCqlHttpEntity(cql, accessToken, null, null);
@@ -80,9 +82,13 @@ public boolean hasErrors(ElmJson elmJson) {
8082

8183
// overload method invocation so if we don't provide ErrorSeverity we assume that its info
8284
protected URI getElmJsonURI(String measureModel) {
85+
// TODO CqlCompilerException is the sole reason for this project to rely on cql-to-elm
86+
// dependency.. we could expose this value from madie-models instead
8387
return getElmJsonURI(measureModel, CqlCompilerException.ErrorSeverity.Info);
8488
}
8589

90+
// TODO CqlCompilerException is the sole reason for this project to rely on cql-to-elm
91+
// dependency.. we could expose this value from madie-models instead
8692
protected URI getElmJsonURI(
8793
String measureModel, CqlCompilerException.ErrorSeverity errorSeverity) {
8894
var isQdm = StringUtils.equals(measureModel, ModelType.QDM_5_6.getValue());

src/main/java/cms/gov/madie/measure/services/VersionService.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package cms.gov.madie.measure.services;
22

3+
import cms.gov.madie.measure.dto.MadieFeatureFlag;
34
import cms.gov.madie.measure.dto.PackageDto;
45
import cms.gov.madie.measure.exceptions.*;
56
import cms.gov.madie.measure.repositories.CqmMeasureRepository;
67
import cms.gov.madie.measure.repositories.ExportRepository;
78
import cms.gov.madie.measure.repositories.MeasureRepository;
9+
import cms.gov.madie.measure.utils.TestCaseServiceUtil;
810
import gov.cms.madie.models.common.ActionType;
911
import gov.cms.madie.models.common.ModelType;
1012
import gov.cms.madie.models.common.Version;
@@ -46,6 +48,8 @@ public class VersionService {
4648
private final TestCaseSequenceService sequenceService;
4749
private final ElmToJsonService elmToJsonService;
4850
private final MongoGridFsService mongoGridFsService;
51+
private final AppConfigService appConfigService;
52+
private final TestCaseValidationService testCaseValidationService;
4953

5054
public enum VersionValidationResult {
5155
VALID,
@@ -276,6 +280,15 @@ public Measure createDraft(
276280
savedDraft.getId(),
277281
findHighestCaseNumberWhenCaseNumbersExist(savedDraft.getTestCases()));
278282
}
283+
284+
if (!measure.getModel().equalsIgnoreCase(ModelType.QDM_5_6.getValue())
285+
&& !measure.getModel().equals(model)
286+
&& appConfigService.isFlagEnabled(MadieFeatureFlag.STU_6_TEST_CASE_VALIDATION)) {
287+
for (TestCase testCase : savedDraft.getTestCases()) {
288+
testCaseValidationService.validateResourceAsynchronously(
289+
savedDraft, testCase, TestCaseServiceUtil.SAVE, accessToken);
290+
}
291+
}
279292
}
280293

281294
actionLogService.logAction(savedDraft.getId(), Measure.class, ActionType.DRAFTED, username);
@@ -330,23 +343,9 @@ private List<TestCase> cloneTestCases(
330343
} else {
331344
testCase.setJson(convertDateTimeToUTC(testCase.getJson()));
332345
}
333-
// TODO Move this validation to after persistence and run asynchronously
334-
HapiOperationOutcome hapiOperationOutcome = null;
335-
if (testCase.getJson() != null && !testCase.getJson().isEmpty()) {
336-
hapiOperationOutcome =
337-
fhirServicesClient
338-
.validateBundle(
339-
testCase.getJson(),
340-
ModelType.valueOfName(currentMeasure.getModel()),
341-
accessToken)
342-
.getBody();
343-
}
344346

345347
return testCase.toBuilder()
346348
.id(ObjectId.get().toString())
347-
.hapiOperationOutcome(hapiOperationOutcome)
348-
.validResource(
349-
hapiOperationOutcome == null ? false : hapiOperationOutcome.isSuccessful())
350349
.groupPopulations(updatedTestCaseGroupPopulations)
351350
.build();
352351
})

src/main/resources/application-local.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ logging:
88
pattern:
99
console: "%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %mdc %-5level %logger{36}.%M - %msg%n"
1010

11-
hapi-fhir:
12-
url: ${HAPI_FHIR_URL:http://localhost:8080/fhir}
1311

12+
13+
madie:
14+
service-config:
15+
cql-template-qicore411-url: ${CQL_TEMPLATE_QICORE411_URL:http://localhost:8088/QICore411_CQLTemplate.txt}
16+
cql-template-qdm56-url: ${CQL_TEMPLATE_QDM56_URL:http://localhost:8088/QDM56_CQLTemplate.txt}
17+
cql-template-qicore600-url: ${CQL_TEMPLATE_QICORE600_URL:http://localhost:8088/QICore600_CQLTemplate.txt}

src/test/java/cms/gov/madie/measure/services/VersionServiceTest.java

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package cms.gov.madie.measure.services;
22

3+
import cms.gov.madie.measure.dto.MadieFeatureFlag;
34
import cms.gov.madie.measure.dto.PackageDto;
45
import cms.gov.madie.measure.exceptions.BadVersionRequestException;
56
import cms.gov.madie.measure.exceptions.BundleOperationException;
@@ -10,14 +11,10 @@
1011
import cms.gov.madie.measure.repositories.CqmMeasureRepository;
1112
import cms.gov.madie.measure.repositories.ExportRepository;
1213
import cms.gov.madie.measure.repositories.MeasureRepository;
14+
import cms.gov.madie.measure.utils.TestCaseServiceUtil;
1315
import gov.cms.madie.models.common.ModelType;
1416
import gov.cms.madie.models.common.Version;
1517
import gov.cms.madie.models.cqm.CqmMeasure;
16-
import gov.cms.madie.models.measure.ElmJson;
17-
import gov.cms.madie.models.measure.Group;
18-
import gov.cms.madie.models.measure.Measure;
19-
import gov.cms.madie.models.measure.MeasureMetaData;
20-
import gov.cms.madie.models.measure.TestCase;
2118
import gov.cms.madie.packaging.utils.PackagingUtilityFactory;
2219
import gov.cms.madie.packaging.utils.qicore411.PackagingUtilityImpl;
2320
import gov.cms.madie.models.measure.*;
@@ -33,7 +30,6 @@
3330
import org.mockito.Mock;
3431
import org.mockito.MockedStatic;
3532
import org.mockito.junit.jupiter.MockitoExtension;
36-
import org.springframework.http.ResponseEntity;
3733

3834
import java.io.ByteArrayInputStream;
3935
import java.time.Instant;
@@ -50,6 +46,7 @@
5046
import static org.mockito.ArgumentMatchers.anyBoolean;
5147
import static org.mockito.ArgumentMatchers.anyInt;
5248
import static org.mockito.ArgumentMatchers.anyString;
49+
import static org.mockito.ArgumentMatchers.eq;
5350
import static org.mockito.Mockito.*;
5451

5552
@ExtendWith(MockitoExtension.class)
@@ -70,6 +67,8 @@ public class VersionServiceTest {
7067
@Mock ElmTranslatorClient elmTranslatorClient;
7168
@Mock FhirServicesClient fhirServicesClient;
7269

70+
@Mock private TestCaseValidationService testCaseValidationService;
71+
7372
@Captor private ArgumentCaptor<Measure> measureCaptor;
7473
@Captor private ArgumentCaptor<CqmMeasure> cqmMeasureCaptor;
7574
@Captor private ArgumentCaptor<Export> exportArgumentCaptor;
@@ -859,8 +858,6 @@ public void testCreateDraftSuccessfullyForQiCoreJsonInvalid() {
859858
.thenReturn(false);
860859
when(measureRepository.save(any(Measure.class))).thenReturn(versionedCopy);
861860
when(actionLogService.logAction(anyString(), any(), any(), anyString())).thenReturn(true);
862-
when(fhirServicesClient.validateBundle(anyString(), any(ModelType.class), anyString()))
863-
.thenReturn(ResponseEntity.ok(validTestCaseHapiOperationOutcome));
864861

865862
Measure draft =
866863
versionService.createDraft(
@@ -925,8 +922,6 @@ public void testCreateDraftSuccessfullyForQiCore() {
925922
.thenReturn(false);
926923
when(measureRepository.save(any(Measure.class))).thenReturn(versionedCopy);
927924
when(actionLogService.logAction(anyString(), any(), any(), anyString())).thenReturn(true);
928-
when(fhirServicesClient.validateBundle(anyString(), any(ModelType.class), anyString()))
929-
.thenReturn(ResponseEntity.ok(validTestCaseHapiOperationOutcome));
930925

931926
Measure draft =
932927
versionService.createDraft(
@@ -1043,8 +1038,6 @@ public void testCreateDraftWithUpdatedModelSuccessfully() {
10431038
.thenReturn(false);
10441039
when(measureRepository.save(any(Measure.class))).thenReturn(versionedCopy);
10451040
when(actionLogService.logAction(anyString(), any(), any(), anyString())).thenReturn(true);
1046-
when(fhirServicesClient.validateBundle(anyString(), any(ModelType.class), anyString()))
1047-
.thenReturn(ResponseEntity.ok(validTestCaseHapiOperationOutcome));
10481041

10491042
versionService.createDraft(
10501043
versionedMeasure.getId(), "Test", "QI-Core v6.0.0", "test-user", TEST_ACCESS_TOKEN);
@@ -1228,8 +1221,6 @@ public void testCreateDraftCopyCaseNumberFromExistingTestCase() {
12281221
.thenReturn(false);
12291222
when(measureRepository.save(any(Measure.class))).thenReturn(versionedCopy);
12301223
when(actionLogService.logAction(anyString(), any(), any(), anyString())).thenReturn(true);
1231-
when(fhirServicesClient.validateBundle(anyString(), any(ModelType.class), anyString()))
1232-
.thenReturn(ResponseEntity.ok(invalidTestCaseHapiOperationOutcome));
12331224
Measure draft =
12341225
versionService.createDraft(
12351226
versionedMeasure.getId(), "Test", MODEL_QI_CORE, "test-user", TEST_ACCESS_TOKEN);
@@ -1299,8 +1290,6 @@ public void testCreateDraftCopyCaseNumberFromSequenceGenerator() {
12991290
when(measureRepository.save(any(Measure.class))).thenReturn(versionedCopy);
13001291
when(actionLogService.logAction(anyString(), any(), any(), anyString())).thenReturn(true);
13011292
when(sequenceService.generateSequence(anyString())).thenReturn(1);
1302-
when(fhirServicesClient.validateBundle(anyString(), any(ModelType.class), anyString()))
1303-
.thenReturn(ResponseEntity.ok(validTestCaseHapiOperationOutcome));
13041293
Measure draft =
13051294
versionService.createDraft(
13061295
versionedMeasure.getId(), "Test", MODEL_QI_CORE, "test-user", TEST_ACCESS_TOKEN);
@@ -1471,8 +1460,6 @@ public void testCreateQiCore600DraftSuccessfully() {
14711460
when(measureRepository.existsByMeasureSetIdAndActiveAndMeasureMetaDataDraft(
14721461
anyString(), anyBoolean(), anyBoolean()))
14731462
.thenReturn(false);
1474-
when(fhirServicesClient.validateBundle(anyString(), any(ModelType.class), anyString()))
1475-
.thenReturn(ResponseEntity.ok(validTestCaseHapiOperationOutcome));
14761463
when(measureRepository.save(any(Measure.class))).thenReturn(versionedCopy);
14771464
when(actionLogService.logAction(anyString(), any(), any(), anyString())).thenReturn(true);
14781465

@@ -1538,8 +1525,6 @@ public void testCreateQiCore700DraftSuccessfully() {
15381525
when(measureRepository.existsByMeasureSetIdAndActiveAndMeasureMetaDataDraft(
15391526
anyString(), anyBoolean(), anyBoolean()))
15401527
.thenReturn(false);
1541-
when(fhirServicesClient.validateBundle(anyString(), any(ModelType.class), anyString()))
1542-
.thenReturn(ResponseEntity.ok(validTestCaseHapiOperationOutcome));
15431528
when(measureRepository.save(any(Measure.class))).thenReturn(versionedCopy);
15441529
when(actionLogService.logAction(anyString(), any(), any(), anyString())).thenReturn(true);
15451530

@@ -1599,4 +1584,77 @@ public void testCreateDraftWhenMeasureMetaDataIsNull() {
15991584
equalTo(
16001585
"Can not create a draft for the measure \"Test\". Only versioned measure can be drafted.")));
16011586
}
1587+
1588+
@Test
1589+
public void testCreateDraftSuccessfullyWhenModelIsChanged() {
1590+
TestCaseGroupPopulation clonedTestCaseGroupPopulation =
1591+
TestCaseGroupPopulation.builder()
1592+
.groupId("clonedGroupId1")
1593+
.scoring("Cohort")
1594+
.populationBasis("boolean")
1595+
.build();
1596+
Measure versionedMeasure = buildBasicMeasure();
1597+
testCase.setJson(json.replace("}", ""));
1598+
versionedMeasure.setTestCases(List.of(testCase));
1599+
MeasureMetaData metaData = new MeasureMetaData();
1600+
metaData.setDraft(true);
1601+
Measure versionedCopy =
1602+
versionedMeasure.toBuilder()
1603+
.id("2")
1604+
.model(MODEL_QI_CORE)
1605+
.versionId("13-13-13-13")
1606+
.measureName("Test")
1607+
.measureMetaData(metaData)
1608+
.groups(List.of(cvGroup.toBuilder().id(ObjectId.get().toString()).build()))
1609+
.testCases(
1610+
List.of(
1611+
testCase.toBuilder()
1612+
.id(ObjectId.get().toString())
1613+
.groupPopulations(List.of(clonedTestCaseGroupPopulation))
1614+
.hapiOperationOutcome(validTestCaseHapiOperationOutcome)
1615+
.build()))
1616+
.build();
1617+
1618+
when(measureRepository.findById(anyString())).thenReturn(Optional.of(versionedMeasure));
1619+
when(measureRepository.existsByMeasureSetIdAndActiveAndMeasureMetaDataDraft(
1620+
anyString(), anyBoolean(), anyBoolean()))
1621+
.thenReturn(false);
1622+
when(measureRepository.save(any(Measure.class))).thenReturn(versionedCopy);
1623+
when(actionLogService.logAction(anyString(), any(), any(), anyString())).thenReturn(true);
1624+
1625+
when(appConfigService.isFlagEnabled(MadieFeatureFlag.STU_6_TEST_CASE_VALIDATION))
1626+
.thenReturn(true);
1627+
// Mocks a validation request awaiting execution.
1628+
when(testCaseValidationService.validateResourceAsynchronously(
1629+
any(), any(TestCase.class), eq(TestCaseServiceUtil.SAVE), anyString()))
1630+
.thenAnswer(
1631+
invocation ->
1632+
invocation.getArgument(1, TestCase.class).toBuilder()
1633+
.validationStatus(TestCaseValidationStatus.PENDING.toString())
1634+
.build());
1635+
1636+
Measure draft =
1637+
versionService.createDraft(
1638+
versionedMeasure.getId(),
1639+
"Test",
1640+
ModelType.QI_CORE_6_0_0.getValue(),
1641+
"test-user",
1642+
TEST_ACCESS_TOKEN);
1643+
1644+
assertThat(draft.getMeasureName(), is(equalTo("Test")));
1645+
assertThat(draft.getMeasureMetaData().isDraft(), is(equalTo(true)));
1646+
assertThat(draft.getVersion().getMajor(), is(equalTo(2)));
1647+
assertThat(draft.getVersion().getMinor(), is(equalTo(3)));
1648+
assertThat(draft.getVersion().getRevisionNumber(), is(equalTo(1)));
1649+
assertThat(draft.getGroups().size(), is(equalTo(1)));
1650+
assertFalse(draft.getGroups().stream().anyMatch(item -> "xyz-p12r-12ert".equals(item.getId())));
1651+
assertThat(draft.getTestCases().size(), is(equalTo(1)));
1652+
assertFalse(draft.getGroups().stream().anyMatch(item -> "testId1".equals(item.getId())));
1653+
assertThat(
1654+
draft.getTestCases().get(0).getGroupPopulations().get(0).getGroupId(),
1655+
is(equalTo("clonedGroupId1")));
1656+
assertTrue(draft.getTestCases().get(0).getHapiOperationOutcome().isSuccessful());
1657+
assertFalse(draft.getTestCases().get(0).getJson().contains("2024-10-11T01:30:10.123+00:00"));
1658+
assertFalse(draft.getTestCases().get(0).getJson().contains("2024-10-10T01:31:20.456+00:00"));
1659+
}
16021660
}

0 commit comments

Comments
 (0)