From 0949d9b9769cc158cf760f893df39da241d17f91 Mon Sep 17 00:00:00 2001 From: David Dieppois <75264909+ddieppois@users.noreply.github.com> Date: Fri, 17 May 2024 11:00:44 -0400 Subject: [PATCH] Cqf-Tooling : NewRefreshIg update (#528) * Added missing tests * _Added CqlRefresh in order to update cql file before conversion. _Made sure that we have the cql data inside the json library and that it matches the udpated cql _Update tests to verify everything works as expected. * Formatting * Test update * Test path update * Update test files * Update test files 2 * Clean up of test file * Clean up of test file 2 * This one is not a file path * Fix output directory * Move files to same directory * TEST: don't swallow exceptions * Add logging of missing files * Revert previous experiments * Fix casing of folder name * Add path to CQL source information * Add more logging * Fix prefix, fix some level 1.3 warnings * Change build to use info log level for debug purposes * Swap up dependency loading code * Revert to normal logging --------- Co-authored-by: Jonathan Percival --- .vscode/settings.json | 2 +- .../cqf/tooling/library/LibraryProcessor.java | 11 +- .../cqf/tooling/npm/NpmPackageManager.java | 10 +- .../cqf/tooling/operation/ig/CqlRefresh.java | 98 ++++++++++ .../tooling/operation/ig/LibraryRefresh.java | 4 + .../operation/ig/NewRefreshIGOperation.java | 2 + .../cqf/tooling/processor/CqlProcessor.java | 19 +- .../operation/RefreshIGOperationTest.java | 179 ++++++++++++++---- .../r4/input/pagecontent/cql/cql-options.json | 19 ++ .../tooling/testfiles}/NewRefreshIG/ig.ini | 0 .../testfiles}/NewRefreshIG/input/cqf-us.xml | 0 .../cql/CumulativeMedicationDuration.cql | 0 .../input/cql/GMTPInitialExpressions.cql | 0 .../input/cql/MBODAInitialExpressions.cql | 0 .../input/cql/UPPARFInitialExpressions.cql | 0 .../NewRefreshIG/input/cql/USCoreCommon.cql | 0 .../NewRefreshIG/input/cql/USCoreElements.cql | 0 .../NewRefreshIG/input/cql/USCoreTests.cql | 0 .../NewRefreshIG/input/cql/cql-options.json | 0 .../library-CumulativeMedicationDuration.json | 0 .../library-GMTPInitialExpressions.json | 0 .../library-MBODAInitialExpressions.json | 0 .../library/library-USCore-ModelInfo.json | 0 .../library/library-USCoreCommon.json | 0 .../library/library-USCoreElements.json | 0 .../library/library-USCoreTests.json | 0 26 files changed, 300 insertions(+), 44 deletions(-) create mode 100644 tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/CqlRefresh.java create mode 100644 tooling/src/test/resources/org/opencds/cqf/tooling/r4/input/pagecontent/cql/cql-options.json rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/ig.ini (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/cqf-us.xml (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/cql/CumulativeMedicationDuration.cql (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/cql/GMTPInitialExpressions.cql (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/cql/MBODAInitialExpressions.cql (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/cql/UPPARFInitialExpressions.cql (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/cql/USCoreCommon.cql (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/cql/USCoreElements.cql (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/cql/USCoreTests.cql (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/cql/cql-options.json (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/resources/library/library-CumulativeMedicationDuration.json (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/resources/library/library-GMTPInitialExpressions.json (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/resources/library/library-MBODAInitialExpressions.json (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/resources/library/library-USCore-ModelInfo.json (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/resources/library/library-USCoreCommon.json (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/resources/library/library-USCoreElements.json (100%) rename tooling/src/test/resources/{ => org/opencds/cqf/tooling/testfiles}/NewRefreshIG/input/resources/library/library-USCoreTests.json (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3f86936a0..634ef798a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,5 +19,5 @@ "testng" ], "java.compile.nullAnalysis.mode": "automatic", - "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable" + "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx4G -Xms100m -Xlog:disable" } \ No newline at end of file diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/library/LibraryProcessor.java b/tooling/src/main/java/org/opencds/cqf/tooling/library/LibraryProcessor.java index f8ceccc79..204bbbf9d 100644 --- a/tooling/src/main/java/org/opencds/cqf/tooling/library/LibraryProcessor.java +++ b/tooling/src/main/java/org/opencds/cqf/tooling/library/LibraryProcessor.java @@ -47,7 +47,7 @@ public class LibraryProcessor extends BaseProcessor { private static final Logger logger = LoggerFactory.getLogger(LibraryProcessor.class); - public static final String ResourcePrefix = "library-"; + public static final String ResourcePrefix = "Library-"; public static String getId(String baseId) { return ResourcePrefix + baseId; @@ -280,14 +280,14 @@ protected List refreshGeneratedContent(List sourceLibraries) { } public List refreshGeneratedContent(String cqlDirectoryPath, String fhirVersion) { - List result = new ArrayList(); + var result = new ArrayList(); File input = new File(cqlDirectoryPath); if (input.exists() && input.isDirectory()) { result.add(input.getAbsolutePath()); } setBinaryPaths(result); - List libraries = new ArrayList(); + var libraries = new ArrayList(); return internalRefreshGeneratedContent(libraries); } @@ -330,6 +330,11 @@ private List internalRefreshGeneratedContent(List sourceLibrar sourceLibraries.add(newLibrary); } } + else + { + logger.warn("No identifier found for CQL file {}", fileInfo.getPath()); + } + } List resources = new ArrayList(); diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/npm/NpmPackageManager.java b/tooling/src/main/java/org/opencds/cqf/tooling/npm/NpmPackageManager.java index 8ef3e2f41..976bc66f7 100644 --- a/tooling/src/main/java/org/opencds/cqf/tooling/npm/NpmPackageManager.java +++ b/tooling/src/main/java/org/opencds/cqf/tooling/npm/NpmPackageManager.java @@ -133,9 +133,10 @@ private void loadCorePackage() { } private void loadIg(ImplementationGuide.ImplementationGuideDependsOnComponent dep, int index) throws IOException { + logger.info("Loading IG Dependency {}#{}", dep.getUri(), dep.getVersion()); String name = dep.getId(); if (!dep.hasId()) { - logger.info("Dependency '{}' has no id, so can't be referred to in markdown in the IG", idForDep(dep)); + logger.warn("Dependency '{}' has no id, so can't be referred to in markdown in the IG", idForDep(dep)); name = "u" + Utilities.makeUuidLC().replace("-", ""); } if (!isValidIGToken(name)) { @@ -155,10 +156,9 @@ private void loadIg(ImplementationGuide.ImplementationGuideDependsOnComponent de throw new IllegalArgumentException( "You must specify a version for the IG " + packageId + " (" + canonical + ")"); - NpmPackage pi = packageId == null ? null : pcm.loadPackageFromCacheOnly(packageId, igver); - if (pi != null) - npmList.add(pi); + NpmPackage pi = pcm.loadPackage(packageId, igver); if (pi == null) { + logger.warn("Dependency " + name + " (" + canonical + ") not found by FilesystemPackageCacheManager"); pi = resolveDependency(canonical, packageId, igver); if (pi == null) { if (Utilities.noString(packageId)) @@ -169,6 +169,8 @@ private void loadIg(ImplementationGuide.ImplementationGuideDependsOnComponent de } } + npmList.add(pi); + logger.debug( "Load " + name + " (" + canonical + ") from " + packageId + "#" + igver); diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/CqlRefresh.java b/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/CqlRefresh.java new file mode 100644 index 000000000..2d00e3c56 --- /dev/null +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/CqlRefresh.java @@ -0,0 +1,98 @@ +package org.opencds.cqf.tooling.operation.ig; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.opencds.cqf.tooling.parameter.RefreshIGParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class CqlRefresh extends Refresh { + + private static final Logger logger = LoggerFactory.getLogger(CqlRefresh.class); + private final Pattern VERSION_PATTERN = Pattern.compile("^(library\\s+(\\S+)\\s+version\\s+)'[0-9]+\\.[0-9]+\\.[0-9]+'"); + private final Pattern INCLUDE_PATTERN = Pattern.compile("^(include\\s+(\\S+)\\s+version\\s+)'([0-9]+\\.[0-9]+\\.[0-9]+)'(\\s+called\\s+(\\S+))?"); + + public CqlRefresh(IGInfo igInfo) { + super(igInfo); + } + + @Override + public List refresh() { + return List.of(); + } + + public void refreshCql(IGInfo igInfo, RefreshIGParameters params) { + Map updatedLibraries = refreshCqlFile(igInfo.getCqlBinaryPath(), params.updatedVersion); + updateCqlReferences(igInfo.getCqlBinaryPath(), updatedLibraries); + } + + private Map refreshCqlFile(String cqlBinaryPath, String updatedVersion) { + Map updatedLibraries = new HashMap<>(); + try (Stream paths = Files.walk(Paths.get(cqlBinaryPath))) { + List files = paths + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".cql")) + .collect(Collectors.toList()); + + for (Path file : files) { + List lines = Files.readAllLines(file); + for (int i = 0; i < lines.size(); i++) { + Matcher matcher = VERSION_PATTERN.matcher(lines.get(i)); + if (matcher.matches()) { + String libraryName = matcher.group(2); + String updatedLine = matcher.replaceFirst("$1'" + updatedVersion + "'"); + lines.set(i, updatedLine); + Files.write(file, lines); + updatedLibraries.put(libraryName, updatedVersion); + break; + } + } + } + } catch (IOException e) { + logger.error("Error updating cql files: {}", e.getMessage()); + } + return updatedLibraries; + } + + private void updateCqlReferences(String cqlBinaryPath, Map updatedLibraries) { + try (Stream paths = Files.walk(Paths.get(cqlBinaryPath))) { + List files = paths + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".cql")) + .collect(Collectors.toList()); + + for (Path file : files) { + List lines = Files.readAllLines(file); + boolean fileUpdated = false; + for (int i = 0; i < lines.size(); i++) { + Matcher matcher = INCLUDE_PATTERN.matcher(lines.get(i)); + while (matcher.find()) { + String libraryName = matcher.group(2); + String newVersion = updatedLibraries.get(libraryName); + if (newVersion != null && !matcher.group(3).equals(newVersion)) { + String calledPart = matcher.group(4) != null ? matcher.group(4) : ""; + String newLine = matcher.replaceFirst("$1'" + newVersion + "'" + calledPart); + lines.set(i, newLine); + fileUpdated = true; + } + } + } + if (fileUpdated) { + Files.write(file, lines); + } + } + } catch (IOException e) { + logger.error("Error updating CQL references: {}", e.getMessage()); + } + } + +} diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/LibraryRefresh.java b/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/LibraryRefresh.java index 13d8a5957..99a563018 100644 --- a/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/LibraryRefresh.java +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/LibraryRefresh.java @@ -66,6 +66,10 @@ public List refresh(RefreshIGParameters params) { logger.info("Refreshing {}", library.getIdElement()); for (CqlProcessor.CqlSourceFileInformation info : cqlProcessor.getAllFileInformation()) { + if (info.getIdentifier() == null) { + logger.warn("No identifier found for CQL file {}", info.getPath()); + } + if (info.getIdentifier().getId().endsWith(name)) { // TODO: should likely verify or resolve/refresh the following elements: // cpg-knowledgeCapability, cpg-knowledgeRepresentationLevel, url, identifier, status, diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/NewRefreshIGOperation.java b/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/NewRefreshIGOperation.java index 23e3c4678..8718c0a34 100644 --- a/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/NewRefreshIGOperation.java +++ b/tooling/src/main/java/org/opencds/cqf/tooling/operation/ig/NewRefreshIGOperation.java @@ -22,6 +22,8 @@ public void execute(String[] args) { try { this.params = new RefreshIGArgumentProcessor().parseAndConvert(args); IGInfo info = new IGInfo(null, params); + CqlRefresh cqlRefresh = new CqlRefresh(info); + cqlRefresh.refreshCql(info, params); LibraryRefresh libraryRefresh = new LibraryRefresh(info); publishLibraries(info, libraryRefresh.refresh(this.params)); PlanDefinitionRefresh planDefinitionRefresh = new PlanDefinitionRefresh(info, libraryRefresh.getCqlProcessor(), libraryRefresh.getLibraryPackages()); diff --git a/tooling/src/main/java/org/opencds/cqf/tooling/processor/CqlProcessor.java b/tooling/src/main/java/org/opencds/cqf/tooling/processor/CqlProcessor.java index 9f3c19ed2..7391086c5 100644 --- a/tooling/src/main/java/org/opencds/cqf/tooling/processor/CqlProcessor.java +++ b/tooling/src/main/java/org/opencds/cqf/tooling/processor/CqlProcessor.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.FilenameFilter; +import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; @@ -34,13 +35,17 @@ import org.opencds.cqf.tooling.npm.NpmLibrarySourceProvider; import org.opencds.cqf.tooling.npm.NpmModelInfoProvider; import org.opencds.cqf.tooling.utilities.ResourceUtils; +import org.slf4j.Logger; public class CqlProcessor { + private static final Logger log = org.slf4j.LoggerFactory.getLogger(CqlProcessor.class); + /** * information about a cql file */ public class CqlSourceFileInformation { + private final String path; private CqlTranslatorOptions options; private VersionedIdentifier identifier; private byte[] cql; @@ -50,6 +55,15 @@ public class CqlSourceFileInformation { private List relatedArtifacts = new ArrayList<>(); private List dataRequirements = new ArrayList<>(); private List parameters = new ArrayList<>(); + + public CqlSourceFileInformation(String path) { + this.path = path; + } + + public String getPath() { + return path; + } + public CqlTranslatorOptions getOptions() { return options; } @@ -361,7 +375,7 @@ public static ValidationMessage exceptionToValidationMessage(File file, CqlCompi private void translateFile(LibraryManager libraryManager, File file, CqlCompilerOptions options) { // logger.logMessage(String.format("Translating CQL source in file %s", file.toString())); - CqlSourceFileInformation result = new CqlSourceFileInformation(); + CqlSourceFileInformation result = new CqlSourceFileInformation(file.getAbsolutePath()); fileMap.put(file.getAbsoluteFile().toString(), result); if (options.getValidateUnits()) { @@ -382,6 +396,8 @@ private void translateFile(LibraryManager libraryManager, File file, CqlCompiler if (!severeErrorList.isEmpty()) { + var messages = severeErrorList.stream().map(x -> x.getMessage()).reduce("", (x, y) -> x + "\n" + y); + log.warn("CQL Processing failed with errors count: {}, messages: {}", severeErrorList.size(), messages); result.getErrors().add(new ValidationMessage(ValidationMessage.Source.Publisher, IssueType.EXCEPTION, file.getName(), String.format("CQL Processing failed with (%d) errors.", translator.getErrors().size()), IssueSeverity.ERROR)); } @@ -390,6 +406,7 @@ private void translateFile(LibraryManager libraryManager, File file, CqlCompiler result.setOptions(new CqlTranslatorOptions().withCqlCompilerOptions(options)); // convert to base64 bytes // NOTE: Publication tooling requires XML content + result.setCql(Files.readAllBytes(file.toPath())); result.setElm(translator.toXml().getBytes()); result.setIdentifier(translator.toELM().getIdentifier()); result.setJsonElm(translator.toJson().getBytes()); diff --git a/tooling/src/test/java/org/opencds/cqf/tooling/operation/RefreshIGOperationTest.java b/tooling/src/test/java/org/opencds/cqf/tooling/operation/RefreshIGOperationTest.java index 6c861325b..d5fa136bc 100644 --- a/tooling/src/test/java/org/opencds/cqf/tooling/operation/RefreshIGOperationTest.java +++ b/tooling/src/test/java/org/opencds/cqf/tooling/operation/RefreshIGOperationTest.java @@ -1,28 +1,18 @@ package org.opencds.cqf.tooling.operation; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintStream; -import java.net.ServerSocket; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import com.fasterxml.jackson.core.StreamReadConstraints; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.Json; +import com.google.gson.*; import org.apache.commons.io.FileUtils; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Group; import org.hl7.fhir.utilities.IniFile; import org.opencds.cqf.tooling.RefreshTest; +import org.opencds.cqf.tooling.operation.ig.NewRefreshIGOperation; import org.opencds.cqf.tooling.parameter.RefreshIGParameters; import org.opencds.cqf.tooling.processor.IGProcessor; import org.opencds.cqf.tooling.processor.argument.RefreshIGArgumentProcessor; @@ -30,22 +20,20 @@ import org.opencds.cqf.tooling.utilities.ResourceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import com.fasterxml.jackson.core.StreamReadConstraints; -import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.common.Json; -import com.google.gson.Gson; +import org.testng.annotations.*; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; +import java.io.*; +import java.net.ServerSocket; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import static org.testng.Assert.*; -import org.hl7.fhir.r4.model.Group; public class RefreshIGOperationTest extends RefreshTest { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); public RefreshIGOperationTest() { @@ -64,6 +52,14 @@ public RefreshIGOperationTest() { private final String INI_LOC = Path.of("target","refreshIG","ig.ini").toString(); + private static final String[] NEW_REFRESH_IG_LIBRARY_FILE_NAMES = { + "GMTPInitialExpressions.json", "GMTPInitialExpressions.json", + "MBODAInitialExpressions.json", "USCoreCommon.json", "USCoreElements.json", "USCoreTests.json" + }; + + private static final String TARGET_OUTPUT_FOLDER_PATH = "target" + separator + "NewRefreshIG"; + private static final String TARGET_OUTPUT_IG_CQL_FOLDER_PATH = TARGET_OUTPUT_FOLDER_PATH + separator + "input" + separator + "cql"; + private static final String TARGET_OUTPUT_IG_LIBRARY_FOLDER_PATH = TARGET_OUTPUT_FOLDER_PATH + separator + "input" + separator + "resources" + separator + "library"; // Store the original standard out before changing it. private final PrintStream originalStdOut = System.out; @@ -81,12 +77,125 @@ public void setUp() throws Exception { IOUtils.resourceDirectories = new ArrayList(); IOUtils.clearDevicePaths(); System.setOut(new PrintStream(this.console)); - File dir = new File("target" + separator + "refreshIG"); + + // Delete directories + deleteDirectory("target" + File.separator + "refreshIG"); + deleteDirectory("target" + File.separator + "NewRefreshIG"); + + deleteTempINI(); + } + + /** + * Attempts to delete a directory if it exists. + * @param path The path to the directory to delete. + */ + private void deleteDirectory(String path) { + File dir = new File(path); if (dir.exists()) { - FileUtils.deleteDirectory(dir); + try { + FileUtils.deleteDirectory(dir); + } catch (IOException e) { + System.err.println("Failed to delete directory: " + path + " - " + e.getMessage()); + } } + } - deleteTempINI(); + @Test + public void testNewRefreshOperation() throws IOException { + copyResourcesToTargetDir(TARGET_OUTPUT_FOLDER_PATH, "testfiles/NewRefreshIG"); + File folder = new File(TARGET_OUTPUT_FOLDER_PATH); + assertTrue(folder.exists(), "Folder should be present"); + File jsonFile = new File(folder, "ig.ini"); + assertTrue(jsonFile.exists(), "ig.ini file should be present"); + + NewRefreshIGOperation newRefreshIGOperation = new NewRefreshIGOperation(); + String[] args = new String[]{ + "-NewRefreshIG", + "-ini=" + TARGET_OUTPUT_FOLDER_PATH + separator + "ig.ini", + "-rd=" + TARGET_OUTPUT_FOLDER_PATH, + "-uv=" + "1.0.1", + "-d", + "-p", + "-t" + }; + newRefreshIGOperation.execute(args); + + // Verify correct update of cql files following refresh + File cumulativeMedFile = new File(TARGET_OUTPUT_IG_CQL_FOLDER_PATH, "CumulativeMedicationDuration.cql"); + assertTrue(cumulativeMedFile.exists(), "CumulativeMedicationDuration.cql should exist"); + verifyFileContent(cumulativeMedFile, "library CumulativeMedicationDuration version '1.0.1'"); + + File mbodaFile = new File(TARGET_OUTPUT_IG_CQL_FOLDER_PATH, "MBODAInitialExpressions.cql"); + assertTrue(mbodaFile.exists(), "MBODAInitialExpressions.cql should exist"); + verifyFileContent(mbodaFile, "include CumulativeMedicationDuration version '1.0.1' called CMD"); + + folder = new File(TARGET_OUTPUT_IG_LIBRARY_FOLDER_PATH); + assertTrue(folder.exists(), "Folder should be created"); + + for (String fileName : NEW_REFRESH_IG_LIBRARY_FILE_NAMES) { + jsonFile = new File(folder, fileName); + assertTrue(jsonFile.exists(), "JSON file " + fileName + " should be created"); + } + + File gmtpFile = new File(TARGET_OUTPUT_IG_LIBRARY_FOLDER_PATH, "GMTPInitialExpressions.json"); + assertTrue(gmtpFile.exists(), "GMTPInitialExpressions.json file should exist"); + try (FileReader reader = new FileReader(gmtpFile)) { + JsonObject jsonObject = JsonParser.parseReader(reader).getAsJsonObject(); + verifyJsonContent(jsonObject, "GMTPInitialExpressions.json"); + + String version = jsonObject.get("version").getAsString(); + assertEquals("1.0.1", version, "Version parameter should be modified correctly"); + + JsonArray relatedArtifacts = jsonObject.getAsJsonArray("relatedArtifact"); + boolean foundFHIRHelpers = verifyRelatedArtifacts(relatedArtifacts); + assertTrue(foundFHIRHelpers, "Library FHIRHelpers not found with correct resource value"); + } catch (IOException e) { + fail("Error reading GMTPInitialExpressions.json file: " + e.getMessage()); + } + } + + private void verifyJsonContent(JsonObject jsonObject, String jsonFileName) { + JsonArray contentArray = jsonObject.getAsJsonArray("content"); + assertNotNull(contentArray, "Content array should not be null in " + jsonFileName); + assertTrue(contentArray.size() > 0, "Content array should not be empty in " + jsonFileName); + + JsonObject contentObject = contentArray.get(0).getAsJsonObject(); + assertEquals(contentObject.get("contentType").getAsString(), "text/cql", "Content type should be 'text/cql' in " + jsonFileName); + assertTrue(contentObject.has("data"), "Data field should exist in " + jsonFileName); + } + + private boolean verifyRelatedArtifacts(JsonArray relatedArtifacts) { + for (JsonElement element : relatedArtifacts) { + JsonObject artifact = element.getAsJsonObject(); + if (artifact.has("display") && artifact.get("display").getAsString().equals("Library FHIRHelpers")) { + String resource = artifact.get("resource").getAsString(); + if (resource.equals("http://fhir.org/guides/cqf/common/Library/FHIRHelpers|4.0.1")) { + return true; + } + } + } + return false; + } + + private void verifyFileContent(File file, String expectedContent) { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + boolean found = false; + while ((line = reader.readLine()) != null) { + if (line.contains(expectedContent)) { + found = true; + break; + } + } + assertTrue(found, "Expected content not found in " + file.getName()); + } catch (IOException e) { + fail("Failed to read " + file.getName() + ": " + e.getMessage()); + } + } + + @AfterSuite + public void cleanup() { + deleteDirectory("null"); } /** diff --git a/tooling/src/test/resources/org/opencds/cqf/tooling/r4/input/pagecontent/cql/cql-options.json b/tooling/src/test/resources/org/opencds/cqf/tooling/r4/input/pagecontent/cql/cql-options.json new file mode 100644 index 000000000..5687fea53 --- /dev/null +++ b/tooling/src/test/resources/org/opencds/cqf/tooling/r4/input/pagecontent/cql/cql-options.json @@ -0,0 +1,19 @@ +{ + "options":[ + "EnableAnnotations", + "EnableLocators", + "DisableListDemotion", + "DisableListPromotion", + "EnableDateRangeOptimization" +], + "formats":[ + "XML","JSON" + ], + "validateUnits":true, + "verifyOnly":false, + "errorLevel":"Info", + "signatureLevel":"None", + "analyzeDataRequirements":false, + "collapseDataRequirements":true, + "compatibilityLevel": "1.3" +} diff --git a/tooling/src/test/resources/NewRefreshIG/ig.ini b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/ig.ini similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/ig.ini rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/ig.ini diff --git a/tooling/src/test/resources/NewRefreshIG/input/cqf-us.xml b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cqf-us.xml similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/cqf-us.xml rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cqf-us.xml diff --git a/tooling/src/test/resources/NewRefreshIG/input/cql/CumulativeMedicationDuration.cql b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/CumulativeMedicationDuration.cql similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/cql/CumulativeMedicationDuration.cql rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/CumulativeMedicationDuration.cql diff --git a/tooling/src/test/resources/NewRefreshIG/input/cql/GMTPInitialExpressions.cql b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/GMTPInitialExpressions.cql similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/cql/GMTPInitialExpressions.cql rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/GMTPInitialExpressions.cql diff --git a/tooling/src/test/resources/NewRefreshIG/input/cql/MBODAInitialExpressions.cql b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/MBODAInitialExpressions.cql similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/cql/MBODAInitialExpressions.cql rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/MBODAInitialExpressions.cql diff --git a/tooling/src/test/resources/NewRefreshIG/input/cql/UPPARFInitialExpressions.cql b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/UPPARFInitialExpressions.cql similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/cql/UPPARFInitialExpressions.cql rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/UPPARFInitialExpressions.cql diff --git a/tooling/src/test/resources/NewRefreshIG/input/cql/USCoreCommon.cql b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/USCoreCommon.cql similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/cql/USCoreCommon.cql rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/USCoreCommon.cql diff --git a/tooling/src/test/resources/NewRefreshIG/input/cql/USCoreElements.cql b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/USCoreElements.cql similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/cql/USCoreElements.cql rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/USCoreElements.cql diff --git a/tooling/src/test/resources/NewRefreshIG/input/cql/USCoreTests.cql b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/USCoreTests.cql similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/cql/USCoreTests.cql rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/USCoreTests.cql diff --git a/tooling/src/test/resources/NewRefreshIG/input/cql/cql-options.json b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/cql-options.json similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/cql/cql-options.json rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/cql/cql-options.json diff --git a/tooling/src/test/resources/NewRefreshIG/input/resources/library/library-CumulativeMedicationDuration.json b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-CumulativeMedicationDuration.json similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/resources/library/library-CumulativeMedicationDuration.json rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-CumulativeMedicationDuration.json diff --git a/tooling/src/test/resources/NewRefreshIG/input/resources/library/library-GMTPInitialExpressions.json b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-GMTPInitialExpressions.json similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/resources/library/library-GMTPInitialExpressions.json rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-GMTPInitialExpressions.json diff --git a/tooling/src/test/resources/NewRefreshIG/input/resources/library/library-MBODAInitialExpressions.json b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-MBODAInitialExpressions.json similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/resources/library/library-MBODAInitialExpressions.json rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-MBODAInitialExpressions.json diff --git a/tooling/src/test/resources/NewRefreshIG/input/resources/library/library-USCore-ModelInfo.json b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-USCore-ModelInfo.json similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/resources/library/library-USCore-ModelInfo.json rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-USCore-ModelInfo.json diff --git a/tooling/src/test/resources/NewRefreshIG/input/resources/library/library-USCoreCommon.json b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-USCoreCommon.json similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/resources/library/library-USCoreCommon.json rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-USCoreCommon.json diff --git a/tooling/src/test/resources/NewRefreshIG/input/resources/library/library-USCoreElements.json b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-USCoreElements.json similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/resources/library/library-USCoreElements.json rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-USCoreElements.json diff --git a/tooling/src/test/resources/NewRefreshIG/input/resources/library/library-USCoreTests.json b/tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-USCoreTests.json similarity index 100% rename from tooling/src/test/resources/NewRefreshIG/input/resources/library/library-USCoreTests.json rename to tooling/src/test/resources/org/opencds/cqf/tooling/testfiles/NewRefreshIG/input/resources/library/library-USCoreTests.json