diff --git a/.vscode/settings.json b/.vscode/settings.json index 5ee78daf3..99a142708 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,8 @@ { "java.configuration.updateBuildConfiguration": "automatic", "cSpell.words": [ + "acceleratorkit", + "ANCM", "Careand", "pagecontent", "plandefinition", diff --git a/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/ANCMiniContentTest.java b/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/ANCMiniContentTest.java new file mode 100644 index 000000000..425736714 --- /dev/null +++ b/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/ANCMiniContentTest.java @@ -0,0 +1,65 @@ +package org.opencds.cqf.tooling.acceleratorkit; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; + +import org.hl7.fhir.r4.model.CodeSystem; +import org.testng.annotations.Test; + +public class ANCMiniContentTest extends BaseContentTest { + + public ANCMiniContentTest() { + super(new Spreadsheet(){{ + path = "acceleratorkit/ANC Test Cases-mini.xlsx"; + dataDictionarySheets = "ANC.A. Registration,ANC.B5 Quick check,ANC.End End"; + scope = "ANCM"; + }}); + } + + @Test + public void validateContentCount() { + assertFalse(profilesPath().toFile().exists()); + assertEquals(cqlPath().toFile().listFiles().length, 4); + assertFalse(examplesPath().toFile().exists()); + assertFalse(extensionsPath().toFile().exists()); + assertFalse(resourcesPath().toFile().exists()); + assertFalse(testsPath().toFile().exists()); + assertEquals(vocabularyPath().toFile().listFiles().length, 1); + } + + @Test + public void validateCQLContent() throws IOException { + assertTrue(cqlPath().resolve("ANCMConcepts.cql").toFile().exists()); + assertTrue(cqlPath().resolve("ANCMContactDataElements.cql").toFile().exists()); + assertTrue(cqlPath().resolve("ANCMDataElements.cql").toFile().exists()); + + var cqlLines = Files.readAllLines(cqlPath().resolve("ANCMConcepts.cql")); + assertEquals(cqlLines.get(6), "codesystem \"RxNorm\": 'http://www.nlm.nih.gov/research/umls/rxnorm'"); + } + + @Test + public void validateCodeSystem() { + var codeSystem = resourceAtPath( + CodeSystem.class, + vocabularyPath().resolve("codesystem/codesystem-activity-codes.json")); + assertEquals(codeSystem.getTitle(), "ANCM Activity Codes"); + } + + @Test + public void exampleIssue628() throws IOException { + // Link the github issue here + // Description of the issue (e.g. "The CQL was missing a comment at line 235") + // var cqlLines = Files.readAllLines(cqlPath().resolve("FhirHelpers.cql")); + // assertEquals(cqlLines.get(20), "// @fluentFunction"); + } + + + @Test + public void validateElm() { + // TODO: Helpers to compile CQL to ELM and validate + } +} \ No newline at end of file diff --git a/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/ANCMiniContentValidationTest.java b/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/ANCMiniContentValidationTest.java deleted file mode 100644 index 0ec7fb948..000000000 --- a/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/ANCMiniContentValidationTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.opencds.cqf.tooling.acceleratorkit; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.testng.annotations.Test; - -public class ANCMiniContentValidationTest extends BaseProcessorTest { - private static final String resourcesPath = "src/test/resources"; - private static final String spreadSheet = "acceleratorkit/ANC Test Cases-mini.xlsx"; - private static final String dataDictionarySheets = "ANC.A. Registration,ANC.B5 Quick check,ANC.End End"; - - @Test - public void validateANCMiniContent() { - File outDir = null; - try { - outDir = Files.createTempDirectory("mini").toFile(); - outDir.deleteOnExit(); - } - catch (Exception e) { - throw new RuntimeException(e); - } - - String outPath = outDir.getAbsolutePath(); - - String spreadSheetPath = Path.of(resourcesPath, spreadSheet).toString(); - String[] args = { "-ProcessAcceleratorKit", "-s=ANCM", "-pts=" + spreadSheetPath, - "-dep=" + dataDictionarySheets, "-op=" + outPath }; - - Processor acceleratorKitProcessor = new Processor(); - // execute to generate the data dictionary files - acceleratorKitProcessor.execute(args); - // structure definitions - compareProfilesStructureDefinitions(outPath); - // vocabulary - compareVocabulary(outPath); - // resources - compareResources(outPath); - // cql - compareCql(outPath); - // examples - compareExamples(outPath); - // extensions - compareExtensions(outPath); - // tests - compareTests(outPath); - } - - private void compareProfilesStructureDefinitions(String root) { - countFiles(Path.of(root, "input", "profiles").toString(), 0); - } - - private void compareCql(String root) { - countFiles(Path.of(root, "input", "cql").toString(), 4); - } - - private void compareExamples(String root) { - countFiles(Path.of(root, "input", "examples").toString(), 0); - } - - private void compareExtensions(String root) { - countFiles(Path.of(root, "input", "extensions").toString(), 0); - } - - private void compareResources(String root) { - countFiles(Path.of(root, "input", "resources").toString(), 0); - } - - private void compareTests(String root) { - countFiles(Path.of(root, "input", "tests").toString(), 0); - } - - private void compareVocabulary(String root) { - countFiles(Path.of(root, "input", "vocabulary").toString(), 1); - } - -} \ No newline at end of file diff --git a/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/BaseContentTest.java b/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/BaseContentTest.java new file mode 100644 index 000000000..064084f1c --- /dev/null +++ b/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/BaseContentTest.java @@ -0,0 +1,194 @@ +package org.opencds.cqf.tooling.acceleratorkit; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Objects; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.testng.annotations.BeforeClass; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.parser.IParser; + +/** + * This class scaffolds the test setup for the AcceleratorKitProcessor. + * Extend this class to create a test for a specific accelerator kit + * spreadsheet. + */ +public abstract class BaseContentTest { + private static final String resourcesPath = "src/test/resources"; + private static final String tempPath = "target/test-output"; + + private final Spreadsheet spreadsheet; + private final FhirContext fhirContext; + + private Path outputPath; + private Processor processor; + + protected BaseContentTest(Spreadsheet spreadsheet) { + this(spreadsheet, FhirVersionEnum.R4); + } + + protected BaseContentTest(Spreadsheet spreadsheet, FhirVersionEnum fhirVersion) { + Objects.requireNonNull(spreadsheet, "spreadsheet is required"); + Objects.requireNonNull(spreadsheet.path, "spreadsheet path is required"); + Objects.requireNonNull(spreadsheet.dataDictionarySheets, "data dictionary sheets are required"); + Objects.requireNonNull(spreadsheet.scope, "scope is required"); + + Objects.requireNonNull(fhirVersion, "fhir version is required"); + + this.fhirContext = FhirContext.forCached(fhirVersion); + this.spreadsheet = spreadsheet; + } + + @BeforeClass + protected void init() throws IOException { + outputPath = Files.createTempDirectory(Path.of(tempPath), "content-test-").toAbsolutePath(); + processor = new Processor(); + processor.execute(args()); + } + + protected String command() { + return "-ProcessAcceleratorKit"; + } + + /** + * Add new fields to this class to support additional command line arguments. + */ + protected static class Spreadsheet { + String path; + String dataDictionarySheets; + String encoding; + String scope; + String dataElementPages; + String testCases; + } + + protected String[] params() { + return new String[] { + "-s", scope(), + "-pts", spreadsheetPath().toAbsolutePath().toString(), + "-dep", dataDictionarySheets(), + "-op", outputPath().toAbsolutePath().toString(), + "-e", encoding(), + "-tc", testCases()}; + }; + + protected String[] args() { + + var params = params(); + if (params.length % 2 != 0) { + throw new RuntimeException("Invalid number of command line arguments. Each argument must have a value"); + } + + var args = new ArrayList(); + args.add(command()); + + // get only the key-value pairs where the value is set, + // create command line arguments (key=value) from them + for (int i = 0; i < params.length; i += 2) { + if (params[i + 1] != null) { + args.add(params[i] + "=" + params[i + 1]); + } + } + + return args.toArray(String[]::new); + } + + // Input params accessors + + protected String testCases() { + return spreadsheet.testCases; + } + + protected String encoding() { + return spreadsheet.encoding; + } + + protected String scope() { + return spreadsheet.scope; + } + + protected Path spreadsheetPath() { + return Path.of(resourcesPath, spreadsheet.path); + } + + protected String dataDictionarySheets() { + return spreadsheet.dataDictionarySheets; + } + + // FHIR context accessors + protected FhirContext fhirContext() { + return fhirContext; + } + + // Directory accessors + + protected Path outputPath() { + return outputPath; + } + + protected Path inputPath() { + return outputPath().resolve("input"); + } + + protected Path profilesPath() { + return inputPath().resolve("profiles"); + } + + protected Path cqlPath() { + return inputPath().resolve("cql"); + } + + protected Path examplesPath() { + return inputPath().resolve("examples"); + } + + protected Path extensionsPath() { + return inputPath().resolve("extensions"); + } + + protected Path resourcesPath() { + return inputPath().resolve("resources"); + } + + protected Path testsPath() { + return inputPath().resolve("tests"); + } + + protected Path vocabularyPath() { + return inputPath().resolve("vocabulary"); + } + + // Resource helpers + + protected T resourceAtPath(Class resourceClass, Path resourcePath) { + Objects.requireNonNull(resourcePath, "resourcePath is required"); + Objects.requireNonNull(resourceClass, "resourceClass is required"); + + var file = resourcePath.toFile(); + if (!file.exists()) { + throw new RuntimeException("Resource file does not exist: " + resourcePath); + } + + IParser parser = null; + if (file.getName().endsWith(".json")) { + parser = fhirContext().newJsonParser(); + } else if (file.getName().endsWith(".xml")) { + parser = fhirContext().newXmlParser(); + } else { + throw new RuntimeException("Unsupported resource file type: " + resourcePath); + } + + try { + return parser.parseResource(resourceClass, new BufferedReader(new FileReader(file))); + } catch (Exception e) { + throw new RuntimeException("Error parsing resource file: " + resourcePath, e); + } + } +} diff --git a/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/BaseProcessorTest.java b/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/BaseProcessorTest.java deleted file mode 100644 index 63ddf319c..000000000 --- a/tooling/src/test/java/org/opencds/cqf/tooling/acceleratorkit/BaseProcessorTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.opencds.cqf.tooling.acceleratorkit; - -import static org.testng.Assert.assertEquals; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - - -public class BaseProcessorTest { - protected void countFiles(String filePath, int expectedCount) { - List files = readFiles(filePath); - assertEquals(files.size(), expectedCount, String.format("expected filePath %s to contain %d files. Found %d files instead.", filePath, expectedCount, files.size())); - } - - protected List readFiles(String path){ - if (!Files.exists(Paths.get(path))) { - return Collections.emptyList(); - } - - try (Stream paths = Files.walk(Paths.get(path))) { - return paths - .filter(Files::isRegularFile) - .map(r -> new File(r.toUri())) - .collect(Collectors.toList()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -}