From d2f0b73bd3e19d68dfba3840b27745708b66f238 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 29 Aug 2025 13:27:47 -0400 Subject: [PATCH 01/10] Add all utilities classes as well as tests to support NPM. Test tgzs and scenarios still need to be tweaked. --- .../cqf/fhir/utility/adapter/IAdapter.java | 132 +++---- .../fhir/utility/adapter/IAdapterFactory.java | 8 + .../adapter/IKnowledgeArtifactAdapter.java | 5 +- .../fhir/utility/adapter/IMeasureAdapter.java | 6 +- .../utility/adapter/dstu3/AdapterFactory.java | 6 + .../utility/adapter/dstu3/MeasureAdapter.java | 5 + .../utility/adapter/r4/AdapterFactory.java | 6 + .../utility/adapter/r4/MeasureAdapter.java | 8 + .../utility/adapter/r5/AdapterFactory.java | 6 + .../utility/adapter/r5/MeasureAdapter.java | 8 + .../npm/MeasureOrNpmResourceHolder.java | 127 +++++++ .../npm/MeasureOrNpmResourceHolderList.java | 103 +++++ .../npm/NpmConfigDependencySubstitutor.java | 20 + .../fhir/utility/npm/NpmNamespaceManager.java | 16 + .../fhir/utility/npm/NpmPackageLoader.java | 171 +++++++++ .../utility/npm/NpmPackageLoaderInMemory.java | 351 ++++++++++++++++++ .../npm/NpmPackageLoaderWithCache.java | 110 ++++++ .../fhir/utility/npm/NpmResourceHolder.java | 235 ++++++++++++ .../utility/repository/ig/IgRepository.java | 74 +++- .../adapter/dstu3/MeasureAdapterTest.java | 21 ++ .../adapter/r4/MeasureAdapterTest.java | 21 ++ .../adapter/r5/MeasureAdapterTest.java | 21 ++ .../npm/BaseNpmResourceInfoForCqlTest.java | 334 +++++++++++++++++ .../npm/r4/NpmResourceHolderR4Test.java | 147 ++++++++ .../npm/r5/NpmResourceHolderR5Test.java | 88 +++++ .../cqf/fhir/utility/npm/r4/SimpleAlpha.tgz | Bin 0 -> 1563 bytes .../cqf/fhir/utility/npm/r4/SimpleBravo.tgz | Bin 0 -> 1566 bytes .../utility/npm/r4/WithDerivedLibrary.tgz | Bin 0 -> 1327 bytes .../utility/npm/r4/crosspackagesource.tgz | Bin 0 -> 1614 bytes .../utility/npm/r4/crosspackagetarget.tgz | Bin 0 -> 1364 bytes .../r4/with-two-layers-derived-libraries.tgz | Bin 0 -> 1602 bytes .../cqf/fhir/utility/npm/r5/SimpleAlpha.tgz | Bin 0 -> 724 bytes .../cqf/fhir/utility/npm/r5/SimpleBravo.tgz | Bin 0 -> 739 bytes .../utility/npm/r5/WithDerivedLibrary.tgz | Bin 0 -> 1317 bytes .../utility/npm/r5/cross-package-source.tgz | Bin 0 -> 1294 bytes .../utility/npm/r5/cross-package-target.tgz | Bin 0 -> 634 bytes .../r5/with-two-layers-derived-libraries.tgz | Bin 0 -> 1602 bytes 37 files changed, 1946 insertions(+), 83 deletions(-) create mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolder.java create mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderList.java create mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutor.java create mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmNamespaceManager.java create mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoader.java create mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoaderInMemory.java create mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoaderWithCache.java create mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmResourceHolder.java create mode 100644 cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java create mode 100644 cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java create mode 100644 cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleAlpha.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleBravo.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/WithDerivedLibrary.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/crosspackagesource.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/crosspackagetarget.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/with-two-layers-derived-libraries.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/SimpleAlpha.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/SimpleBravo.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/WithDerivedLibrary.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/cross-package-source.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/cross-package-target.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/with-two-layers-derived-libraries.tgz diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IAdapter.java index 34f4c0a36d..20ecec3a08 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IAdapter.java @@ -32,11 +32,11 @@ public interface IAdapter { */ T get(); - public FhirContext fhirContext(); + FhirContext fhirContext(); - public ModelResolver getModelResolver(); + ModelResolver getModelResolver(); - public default void setExtension(List> extensions) { + default void setExtension(List> extensions) { try { getModelResolver().setValue(get(), "extension", null); getModelResolver().setValue(get(), "extension", extensions); @@ -46,7 +46,7 @@ public default void setExtension(List> extensions } } - public default > void addExtension(E extension) { + default > void addExtension(E extension) { try { getModelResolver().setValue(get(), "extension", Collections.singletonList(extension)); } catch (Exception e) { @@ -55,33 +55,33 @@ public default void setExtension(List> extensions } } - public default boolean hasExtension() { + default boolean hasExtension() { return !getExtension().isEmpty(); } - public default boolean hasExtension(String url) { + default boolean hasExtension(String url) { return hasExtension(get(), url); } - public default > List getExtension() { + default > List getExtension() { return getExtension(get()); } - public default > E getExtensionByUrl(String url) { + default > E getExtensionByUrl(String url) { return getExtensionByUrl(get(), url); } - public default > List getExtensionsByUrl(String url) { + default > List getExtensionsByUrl(String url) { return getExtensionsByUrl(get(), url); } @SuppressWarnings("unchecked") - public default > List getExtension(IBase base) { + default > List getExtension(IBase base) { return resolvePathList(base, "extension").stream().map(e -> (E) e).collect(Collectors.toList()); } @SuppressWarnings("unchecked") - public default > List getExtensionsByUrl(IBase base, String url) { + default > List getExtensionsByUrl(IBase base, String url) { return getExtension(base).stream() .filter(e -> e.getUrl().equals(url)) .map(e -> (E) e) @@ -89,29 +89,29 @@ public default boolean hasExtension(String url) { } @SuppressWarnings("unchecked") - public default > E getExtensionByUrl(IBase base, String url) { + default > E getExtensionByUrl(IBase base, String url) { return getExtensionsByUrl(base, url).stream() .map(e -> (E) e) .findFirst() .orElse(null); } - public default Boolean hasExtension(IBase base, String url) { + default Boolean hasExtension(IBase base, String url) { return getExtension(base).stream().anyMatch(e -> e.getUrl().equals(url)); } @SuppressWarnings("unchecked") - public default List resolvePathList(IBase base, String path) { + default List resolvePathList(IBase base, String path) { var pathResult = getModelResolver().resolvePath(base, path); return pathResult instanceof List ? (List) pathResult : new ArrayList<>(); } @SuppressWarnings("unchecked") - public default List resolvePathList(IBase base, String path, Class clazz) { + default List resolvePathList(IBase base, String path, Class clazz) { return resolvePathList(base, path).stream().map(i -> (B) i).collect(Collectors.toList()); } - public default String resolvePathString(IBase base, String path) { + default String resolvePathString(IBase base, String path) { var result = resolvePath(base, path); if (result == null) { return null; @@ -127,96 +127,72 @@ public default String resolvePathString(IBase base, String path) { } } - public default IBase resolvePath(IBase base, String path) { + default IBase resolvePath(IBase base, String path) { return (IBase) getModelResolver().resolvePath(base, path); } @SuppressWarnings("unchecked") - public default B resolvePath(IBase base, String path, Class clazz) { + default B resolvePath(IBase base, String path, Class clazz) { return (B) resolvePath(base, path); } @SuppressWarnings("unchecked") static T newPeriod(FhirVersionEnum version) { - switch (version) { - case DSTU3: - return (T) new org.hl7.fhir.dstu3.model.Period(); - case R4: - return (T) new org.hl7.fhir.r4.model.Period(); - case R5: - return (T) new org.hl7.fhir.r5.model.Period(); - default: - throw new UnprocessableEntityException(UNSUPPORTED_VERSION.formatted(version.toString())); - } + return switch (version) { + case DSTU3 -> (T) new org.hl7.fhir.dstu3.model.Period(); + case R4 -> (T) new org.hl7.fhir.r4.model.Period(); + case R5 -> (T) new org.hl7.fhir.r5.model.Period(); + default -> throw new UnprocessableEntityException(String.format(UNSUPPORTED_VERSION, version.toString())); + }; } @SuppressWarnings("unchecked") static > T newStringType(FhirVersionEnum version, String string) { - switch (version) { - case DSTU3: - return (T) new org.hl7.fhir.dstu3.model.StringType(string); - case R4: - return (T) new org.hl7.fhir.r4.model.StringType(string); - case R5: - return (T) new org.hl7.fhir.r5.model.StringType(string); - default: - throw new UnprocessableEntityException(UNSUPPORTED_VERSION.formatted(version.toString())); - } + return switch (version) { + case DSTU3 -> (T) new org.hl7.fhir.dstu3.model.StringType(string); + case R4 -> (T) new org.hl7.fhir.r4.model.StringType(string); + case R5 -> (T) new org.hl7.fhir.r5.model.StringType(string); + default -> throw new UnprocessableEntityException(String.format(UNSUPPORTED_VERSION, version.toString())); + }; } @SuppressWarnings("unchecked") static > T newUriType(FhirVersionEnum version, String string) { - switch (version) { - case DSTU3: - return (T) new org.hl7.fhir.dstu3.model.UriType(string); - case R4: - return (T) new org.hl7.fhir.r4.model.UriType(string); - case R5: - return (T) new org.hl7.fhir.r5.model.UriType(string); - default: - throw new UnprocessableEntityException(UNSUPPORTED_VERSION.formatted(version.toString())); - } + return switch (version) { + case DSTU3 -> (T) new org.hl7.fhir.dstu3.model.UriType(string); + case R4 -> (T) new org.hl7.fhir.r4.model.UriType(string); + case R5 -> (T) new org.hl7.fhir.r5.model.UriType(string); + default -> throw new UnprocessableEntityException(String.format(UNSUPPORTED_VERSION, version.toString())); + }; } @SuppressWarnings("unchecked") static > T newUrlType(FhirVersionEnum version, String string) { - switch (version) { - case DSTU3: - return (T) new org.hl7.fhir.dstu3.model.UriType(string); - case R4: - return (T) new org.hl7.fhir.r4.model.UrlType(string); - case R5: - return (T) new org.hl7.fhir.r5.model.UrlType(string); - default: - throw new UnprocessableEntityException(UNSUPPORTED_VERSION.formatted(version.toString())); - } + return switch (version) { + case DSTU3 -> (T) new org.hl7.fhir.dstu3.model.UriType(string); + case R4 -> (T) new org.hl7.fhir.r4.model.UrlType(string); + case R5 -> (T) new org.hl7.fhir.r5.model.UrlType(string); + default -> throw new UnprocessableEntityException(String.format(UNSUPPORTED_VERSION, version.toString())); + }; } @SuppressWarnings("unchecked") static > T newDateType(FhirVersionEnum version, Date date) { - switch (version) { - case DSTU3: - return (T) new org.hl7.fhir.dstu3.model.DateType(date); - case R4: - return (T) new org.hl7.fhir.r4.model.DateType(date); - case R5: - return (T) new org.hl7.fhir.r5.model.DateType(date); - default: - throw new UnprocessableEntityException(UNSUPPORTED_VERSION.formatted(version.toString())); - } + return switch (version) { + case DSTU3 -> (T) new org.hl7.fhir.dstu3.model.DateType(date); + case R4 -> (T) new org.hl7.fhir.r4.model.DateType(date); + case R5 -> (T) new org.hl7.fhir.r5.model.DateType(date); + default -> throw new UnprocessableEntityException(String.format(UNSUPPORTED_VERSION, version.toString())); + }; } @SuppressWarnings("unchecked") static > T newDateTimeType(FhirVersionEnum version, Date date) { - switch (version) { - case DSTU3: - return (T) new org.hl7.fhir.dstu3.model.DateTimeType(date); - case R4: - return (T) new org.hl7.fhir.r4.model.DateTimeType(date); - case R5: - return (T) new org.hl7.fhir.r5.model.DateTimeType(date); - default: - throw new UnprocessableEntityException(UNSUPPORTED_VERSION.formatted(version.toString())); - } + return switch (version) { + case DSTU3 -> (T) new org.hl7.fhir.dstu3.model.DateTimeType(date); + case R4 -> (T) new org.hl7.fhir.r4.model.DateTimeType(date); + case R5 -> (T) new org.hl7.fhir.r5.model.DateTimeType(date); + default -> throw new UnprocessableEntityException(String.format(UNSUPPORTED_VERSION, version.toString())); + }; } } diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IAdapterFactory.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IAdapterFactory.java index 5d230e70d4..4ac5852db1 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IAdapterFactory.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IAdapterFactory.java @@ -58,6 +58,14 @@ static IResourceAdapter createAdapterForResource(IBaseResource resource) { */ ILibraryAdapter createLibrary(IBaseResource library); + /** + * Creates an adapter that exposes common Measure operations across multiple versions of FHIR + * + * @param measure a FHIR Measure Resource + * @return an adapter exposing common api calls + */ + IMeasureAdapter createMeasure(IBaseResource measure); + /** * Creates an adapter that exposes common PlanDefinition operations across multiple versions of FHIR * diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IKnowledgeArtifactAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IKnowledgeArtifactAdapter.java index 60e520bece..bc622eb93e 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IKnowledgeArtifactAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IKnowledgeArtifactAdapter.java @@ -33,8 +33,8 @@ import org.opencds.cqf.fhir.utility.Canonicals; import org.opencds.cqf.fhir.utility.Constants; import org.opencds.cqf.fhir.utility.SearchHelper; -import org.opencds.cqf.fhir.utility.VersionComparator; import org.opencds.cqf.fhir.utility.VersionUtilities; +import org.opencds.cqf.fhir.utility.Versions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -338,12 +338,11 @@ static boolean isSupportedMetadataResource(IBaseResource resource) { } static Optional findLatestVersion(IBaseBundle bundle) { - var versionComparator = new VersionComparator(); var sorted = BundleHelper.getEntryResources(bundle).stream() .filter(IKnowledgeArtifactAdapter::isSupportedMetadataResource) .map(r -> (IKnowledgeArtifactAdapter) IAdapterFactory.forFhirVersion(r.getStructureFhirVersionEnum()) .createResource(r)) - .sorted((a, b) -> versionComparator.compare(a.getVersion(), b.getVersion())) + .sorted((a, b) -> Versions.compareVersions(a.getVersion(), b.getVersion())) .toList(); if (!sorted.isEmpty()) { return Optional.of(sorted.get(sorted.size() - 1).get()); diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IMeasureAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IMeasureAdapter.java index 2ce32ad4ac..794e5af087 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IMeasureAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IMeasureAdapter.java @@ -1,6 +1,10 @@ package org.opencds.cqf.fhir.utility.adapter; +import java.util.List; + /** * This interface exposes common functionality across all FHIR Questionnaire versions. */ -public interface IMeasureAdapter extends IKnowledgeArtifactAdapter {} +public interface IMeasureAdapter extends IKnowledgeArtifactAdapter { + List getLibrary(); +} diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/AdapterFactory.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/AdapterFactory.java index 2d6fd0d365..6d70751edf 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/AdapterFactory.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/AdapterFactory.java @@ -26,6 +26,7 @@ import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter; import org.opencds.cqf.fhir.utility.adapter.IKnowledgeArtifactAdapter; import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; +import org.opencds.cqf.fhir.utility.adapter.IMeasureAdapter; import org.opencds.cqf.fhir.utility.adapter.IParametersAdapter; import org.opencds.cqf.fhir.utility.adapter.IParametersParameterComponentAdapter; import org.opencds.cqf.fhir.utility.adapter.IPlanDefinitionAdapter; @@ -82,6 +83,11 @@ public ILibraryAdapter createLibrary(IBaseResource library) { return new LibraryAdapter((IDomainResource) library); } + @Override + public IMeasureAdapter createMeasure(IBaseResource measure) { + return new MeasureAdapter((IDomainResource) measure); + } + @Override public IAttachmentAdapter createAttachment(ICompositeType attachment) { return new AttachmentAdapter(attachment); diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/MeasureAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/MeasureAdapter.java index d20187b024..5fb47d8a44 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/MeasureAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/MeasureAdapter.java @@ -49,6 +49,11 @@ public Measure copy() { private Library effectiveDataRequirements; private LibraryAdapter effectiveDataRequirementsAdapter; + @Override + public List getLibrary() { + return getMeasure().getLibrary().stream().map(Reference::getReference).toList(); + } + private String getEdrReferenceString(Extension edrExtension) { return edrExtension.getUrl().contains("cqfm") ? ((Reference) edrExtension.getValue()).getReference() diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/AdapterFactory.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/AdapterFactory.java index 14310fa762..87bfeff5b8 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/AdapterFactory.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/AdapterFactory.java @@ -26,6 +26,7 @@ import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter; import org.opencds.cqf.fhir.utility.adapter.IKnowledgeArtifactAdapter; import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; +import org.opencds.cqf.fhir.utility.adapter.IMeasureAdapter; import org.opencds.cqf.fhir.utility.adapter.IParametersAdapter; import org.opencds.cqf.fhir.utility.adapter.IParametersParameterComponentAdapter; import org.opencds.cqf.fhir.utility.adapter.IPlanDefinitionAdapter; @@ -82,6 +83,11 @@ public ILibraryAdapter createLibrary(IBaseResource library) { return new LibraryAdapter((IDomainResource) library); } + @Override + public IMeasureAdapter createMeasure(IBaseResource measure) { + return new MeasureAdapter((IDomainResource) measure); + } + @Override public IAttachmentAdapter createAttachment(ICompositeType attachment) { return new AttachmentAdapter(attachment); diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/MeasureAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/MeasureAdapter.java index 2077a50338..87f62083a5 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/MeasureAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/MeasureAdapter.java @@ -10,6 +10,7 @@ import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Library; import org.hl7.fhir.r4.model.Measure; +import org.hl7.fhir.r4.model.PrimitiveType; import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.RelatedArtifact; import org.hl7.fhir.r4.model.UriType; @@ -50,6 +51,13 @@ public Measure copy() { private Library effectiveDataRequirements; private LibraryAdapter effectiveDataRequirementsAdapter; + @Override + public List getLibrary() { + return getMeasure().getLibrary().stream() + .map(PrimitiveType::getValueAsString) + .toList(); + } + private String getEdrReferenceString(Extension edrExtension) { return edrExtension.getUrl().contains("cqfm") ? ((Reference) edrExtension.getValue()).getReference() diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/AdapterFactory.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/AdapterFactory.java index 93ee10b0c7..ffce80c9f5 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/AdapterFactory.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/AdapterFactory.java @@ -26,6 +26,7 @@ import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter; import org.opencds.cqf.fhir.utility.adapter.IKnowledgeArtifactAdapter; import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; +import org.opencds.cqf.fhir.utility.adapter.IMeasureAdapter; import org.opencds.cqf.fhir.utility.adapter.IParametersAdapter; import org.opencds.cqf.fhir.utility.adapter.IParametersParameterComponentAdapter; import org.opencds.cqf.fhir.utility.adapter.IPlanDefinitionAdapter; @@ -82,6 +83,11 @@ public ILibraryAdapter createLibrary(IBaseResource library) { return new LibraryAdapter((IDomainResource) library); } + @Override + public IMeasureAdapter createMeasure(IBaseResource measure) { + return new MeasureAdapter((IDomainResource) measure); + } + @Override public IAttachmentAdapter createAttachment(ICompositeType attachment) { return new AttachmentAdapter(attachment); diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/MeasureAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/MeasureAdapter.java index 615cea4d8e..5aa4479ac3 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/MeasureAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/MeasureAdapter.java @@ -10,6 +10,7 @@ import org.hl7.fhir.r5.model.Extension; import org.hl7.fhir.r5.model.Library; import org.hl7.fhir.r5.model.Measure; +import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.r5.model.Reference; import org.hl7.fhir.r5.model.RelatedArtifact; import org.hl7.fhir.r5.model.UriType; @@ -50,6 +51,13 @@ public Measure copy() { private Library effectiveDataRequirements; private LibraryAdapter effectiveDataRequirementsAdapter; + @Override + public List getLibrary() { + return getMeasure().getLibrary().stream() + .map(PrimitiveType::getValueAsString) + .toList(); + } + private String getEdrReferenceString(Extension edrExtension) { return edrExtension.getUrl().contains("cqfm") ? ((Reference) edrExtension.getValue()).getReference() diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolder.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolder.java new file mode 100644 index 0000000000..a49d5227f0 --- /dev/null +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolder.java @@ -0,0 +1,127 @@ +package org.opencds.cqf.fhir.utility.npm; + +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import jakarta.annotation.Nullable; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.Measure; +import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; + +/** + * This class supports polymorphic handling of a Measure that was either retrieved from the FHIR + * DB or an NPM package. + */ +public final class MeasureOrNpmResourceHolder { + + @Nullable + private final Measure measure; + + private final NpmResourceHolder npmResourceHolder; + + public static MeasureOrNpmResourceHolder measureOnly(Measure measure) { + return new MeasureOrNpmResourceHolder(measure, NpmResourceHolder.EMPTY); + } + + public static MeasureOrNpmResourceHolder npmOnly(NpmResourceHolder npmResourceHolder) { + return new MeasureOrNpmResourceHolder(null, npmResourceHolder); + } + + private MeasureOrNpmResourceHolder(@Nullable Measure measure, NpmResourceHolder npmResourceHolder) { + if (measure == null && (NpmResourceHolder.EMPTY == npmResourceHolder)) { + throw new InternalErrorException("Measure and NpmResourceHolder cannot both be null"); + } + this.measure = measure; + this.npmResourceHolder = npmResourceHolder; + } + + public boolean hasNpmLibrary() { + return Optional.ofNullable(npmResourceHolder) + .flatMap(NpmResourceHolder::getOptMainLibrary) + .isPresent(); + } + + public boolean hasLibrary() { + if (measure == null && (NpmResourceHolder.EMPTY == npmResourceHolder)) { + throw new InvalidRequestException("Measure and NpmResourceHolder cannot both be null"); + } + + if (measure != null) { + return measure.hasLibrary(); + } + + return npmResourceHolder.getOptMainLibrary().isPresent(); + } + + public Optional getMainLibraryUrl() { + if (measure == null && (NpmResourceHolder.EMPTY == npmResourceHolder)) { + throw new InvalidRequestException("Measure and NpmResourceHolder cannot both be null"); + } + + if (measure != null) { + final List libraryUrls = measure.getLibrary(); + + if (libraryUrls.isEmpty()) { + return Optional.empty(); + } + + return Optional.ofNullable(libraryUrls.get(0).asStringValue()); + } + + return npmResourceHolder.getOptMainLibrary().map(ILibraryAdapter::getUrl); + } + + public IIdType getMeasureIdElement() { + return getMeasure().getIdElement(); + } + + public boolean hasMeasureUrl() { + return getMeasure().hasUrl(); + } + + public String getMeasureUrl() { + return getMeasure().getUrl(); + } + + public Measure getMeasure() { + var optMeasureFromNpm = Optional.ofNullable(npmResourceHolder).flatMap(NpmResourceHolder::getMeasure); + + if (optMeasureFromNpm.isPresent() && optMeasureFromNpm.get().get() instanceof Measure measureFromNpm) { + return measureFromNpm; + } + + return measure; + } + + public NpmResourceHolder npmResourceHolder() { + return npmResourceHolder; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + var that = (MeasureOrNpmResourceHolder) obj; + return Objects.equals(this.measure, that.measure) + && Objects.equals(this.npmResourceHolder, that.npmResourceHolder); + } + + @Override + public int hashCode() { + return Objects.hash(measure, npmResourceHolder); + } + + @Override + public String toString() { + return "MeasurePlusNpmResourceHolder[" + "measure=" + + measure + ", " + "npmResourceHolders=" + + npmResourceHolder + ']'; + } +} diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderList.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderList.java new file mode 100644 index 0000000000..b76fb977ee --- /dev/null +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderList.java @@ -0,0 +1,103 @@ +package org.opencds.cqf.fhir.utility.npm; + +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import java.util.List; +import java.util.Objects; +import org.hl7.fhir.r4.model.Measure; + +/** + * Effectively a list of {@link MeasureOrNpmResourceHolder} with some convenience methods + */ +public final class MeasureOrNpmResourceHolderList { + + private final List measuresPlusNpmResourceHolders; + + public static MeasureOrNpmResourceHolderList of(MeasureOrNpmResourceHolder measureOrNpmResourceHolder) { + return new MeasureOrNpmResourceHolderList(List.of(measureOrNpmResourceHolder)); + } + + public static MeasureOrNpmResourceHolderList of(List measureOrNpmResourceHolders) { + return new MeasureOrNpmResourceHolderList(measureOrNpmResourceHolders); + } + + public static MeasureOrNpmResourceHolderList of(Measure measure) { + return new MeasureOrNpmResourceHolderList(List.of(MeasureOrNpmResourceHolder.measureOnly(measure))); + } + + public static MeasureOrNpmResourceHolderList ofMeasures(List measures) { + return new MeasureOrNpmResourceHolderList( + measures.stream().map(MeasureOrNpmResourceHolder::measureOnly).toList()); + } + + private MeasureOrNpmResourceHolderList(List measuresPlusNpmResourceHolders) { + this.measuresPlusNpmResourceHolders = measuresPlusNpmResourceHolders; + } + + public List getMeasuresOrNpmResourceHolders() { + return measuresPlusNpmResourceHolders; + } + + List measures() { + return this.measuresPlusNpmResourceHolders.stream() + .map(MeasureOrNpmResourceHolder::getMeasure) + .toList(); + } + + public List npmResourceHolders() { + return this.measuresPlusNpmResourceHolders.stream() + .map(MeasureOrNpmResourceHolder::npmResourceHolder) + .toList(); + } + + public List getMeasures() { + return measuresPlusNpmResourceHolders.stream() + .map(MeasureOrNpmResourceHolder::getMeasure) + .toList(); + } + + public void checkMeasureLibraries() { + for (MeasureOrNpmResourceHolder measureOrNpmResourceHolder : measuresPlusNpmResourceHolders) { + if (!measureOrNpmResourceHolder.hasLibrary()) { + throw new InvalidRequestException("Measure %s does not have a primary library specified" + .formatted(measureOrNpmResourceHolder.getMeasureUrl())); + } + } + } + + public int size() { + return measuresPlusNpmResourceHolders.size(); + } + + public List measuresPlusNpmResourceHolders() { + return measuresPlusNpmResourceHolders; + } + + public List getMeasureUrls() { + return this.measuresPlusNpmResourceHolders.stream() + .map(MeasureOrNpmResourceHolder::getMeasureUrl) + .toList(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + var that = (MeasureOrNpmResourceHolderList) obj; + return Objects.equals(this.measuresPlusNpmResourceHolders, that.measuresPlusNpmResourceHolders); + } + + @Override + public int hashCode() { + return Objects.hash(measuresPlusNpmResourceHolders); + } + + @Override + public String toString() { + return "MeasurePlusNpmResourceHolderList[" + "measuresPlusNpmResourceHolders=" + measuresPlusNpmResourceHolders + + ']'; + } +} diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutor.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutor.java new file mode 100644 index 0000000000..720094606e --- /dev/null +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutor.java @@ -0,0 +1,20 @@ +package org.opencds.cqf.fhir.utility.npm; + +import java.util.Optional; + +/** + * This class is meant to be used from Spring configuration classes, in the case of any missing + * NpmPackageLoader bean definitions, which Spring will inject as empty Optionals. + *

+ * Helps implement a migration from the old world of FHIR/Repository based resources for Libraries, + * Measures and eventually other clinical intelligence resources (such as PlanDefinitions or + * ValueSets), and the new world where they're derived from NPM packages. + * If Spring config is missing an instance of {@link NpmPackageLoader}, then * return the default + * instance. + */ +public class NpmConfigDependencySubstitutor { + + public static NpmPackageLoader substituteNpmPackageLoaderIfEmpty(Optional optNpmPackageLoader) { + return NpmPackageLoader.getDefaultIfEmpty(optNpmPackageLoader.orElse(null)); + } +} diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmNamespaceManager.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmNamespaceManager.java new file mode 100644 index 0000000000..c983a2d076 --- /dev/null +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmNamespaceManager.java @@ -0,0 +1,16 @@ +package org.opencds.cqf.fhir.utility.npm; + +import java.util.List; +import org.hl7.cql.model.NamespaceInfo; + +/** + * Load all {@link NamespaceInfo}s capturing package ID to URL mappings associated with the NPM + * packages maintained for clinical-reasoning NPM package users to be used to resolve cross-package + * Library/CQL dependencies. See {@link NpmPackageLoader}. + */ +public interface NpmNamespaceManager { + + NpmNamespaceManager DEFAULT = List::of; + + List getAllNamespaceInfos(); +} diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoader.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoader.java new file mode 100644 index 0000000000..ed151d9b1e --- /dev/null +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoader.java @@ -0,0 +1,171 @@ +package org.opencds.cqf.fhir.utility.npm; + +import ca.uhn.fhir.repository.IRepository; +import jakarta.annotation.Nullable; +import java.util.List; +import java.util.Optional; +import org.cqframework.cql.cql2elm.LibraryManager; +import org.cqframework.cql.cql2elm.LibrarySourceProvider; +import org.hl7.cql.model.ModelIdentifier; +import org.hl7.cql.model.NamespaceInfo; +import org.hl7.cql.model.NamespaceManager; +import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * FHIR version agnostic Interface for loading NPM resources including Measures, Libraries and + * NpmPackages as captured within {@link NpmResourceHolder}. + *

+ * This javadoc documents the entire NPM package feature in the clinical-reasoning project. Please + * read below: + *

+ * A downstream app from clinical-reasoning will be able to maintain Measures and Libraries loaded + * from NPM packages. Such Measures and Libraries will, for those clients implementing this + * feature, no longer be maintained in {@link IRepository} storage, unlike all other FHIR resources, + * such as Patients. + *

+ * Downstream apps are responsible for loading and retrieving such packages from implementations + * of the below interface. Additionally, they must map all package IDs to package URLs via a + * List of {@link NamespaceInfo}s. This is done via the + * {@link #initNamespaceMappings(LibraryManager)}, as due to how CQL libraries are loaded, it + * won't work automatically. + *

+ * The {@link NpmResourceHolder} class is used to capture the results of query the NPM + * package with a given measure URL. It's effectively a container for the Measure, its directly + * associated Library, and its NPM package information. In theory, there could be more than one + * NPM package for a given Measure. When CQL runs and calls a custom {@link LibrarySourceProvider}, + * it will first check to see if the directly associated Library matches the provided + * {@link VersionedIdentifier}. If not, it will query all NPM packages within the + * R4NpmResourceInfoForCql to find the Library. And if there is still no match, it will pass the + * VersionedIdentifier, and build a URL from the system and ID before calling + * {@link #loadLibraryByUrl(String)} to load that Library from another package, with the + * VersionedIdentifier already resolved correctly with the help of the NamespaceInfos provided above. + * The implementor is responsible for implementing loadLibraryByUrl to properly return the Library + * from any packages maintained by the application. + *

+ * The above should also work with multiple layers of includes across packages. + *

+ * This workflow is meant to be triggered by a new Measure operation provider: + * $evaluate-measure-by-url, which takes a canonical measure URL instead of a measure ID like + * $evaluate-measure. + *

+ * Example: Package with ID X and URL ... contains Measure ABC + * is associated with Library 123, which contains CQL that includes Library 456 from NPM Package + * with ID Y and URL ..., which contains both the Library and + * its CQL. When resolve the CQL include pointing to Package ID Y, the CQL engine must be able + * to read the namespace info and resolve ID Y to URL .... This + * can only be accomplished via an explicit mapping. + *

+ * Note that there is the real possibility of Measures corresponding to the same canonical URL + * among multiple NPM packages. As such, clients who unintentionally add Measures with the same + * URL in at least two different packages may see the Measure they're not expecting during an + * $evaluate-measure-by-url, and may file production issues accordingly. This may be mitigated + * by new APIs in IHapiPackageCacheManager. + */ +public interface NpmPackageLoader { + Logger logger = LoggerFactory.getLogger(NpmPackageLoader.class); + String LIBRARY_URL_TEMPLATE = "%s/Library/%s"; + + // effectively a no-op implementation + NpmPackageLoader DEFAULT = new NpmPackageLoader() { + + @Override + public NpmNamespaceManager getNamespaceManager() { + return NpmNamespaceManager.DEFAULT; + } + + @Override + public NpmResourceHolder loadNpmResources(IPrimitiveType measureUrl) { + return NpmResourceHolder.EMPTY; + } + + @Override + public Optional loadLibraryByUrl(String libraryUrl) { + return Optional.empty(); + } + }; + + /** + * @param measureUrl The Measure URL provided by the caller, corresponding to a Measure contained + * withing one of the stored NPM packages. + * @return The Measure corresponding to the URL. + */ + NpmResourceHolder loadNpmResources(IPrimitiveType measureUrl); + + /** + * Hackish: Either the downstream app injected this or we default to a NO-OP implementation. + * + * @param npmPackageLoader The NpmPackageLoader, if injected by the downstream app, + * otherwise null. + * @return Either the downstream app's NpmPackageLoaderor a no-op implementation. + */ + static NpmPackageLoader getDefaultIfEmpty(@Nullable NpmPackageLoader npmPackageLoader) { + return Optional.ofNullable(npmPackageLoader).orElse(NpmPackageLoader.DEFAULT); + } + + /** + * Ensure the passed Library gets initialized with the NPM namespace mappings belonging + * to this instance of NpmPackageLoader. + * + * @param libraryManager from the CQL Engine being used for an evaluation + */ + default void initNamespaceMappings(LibraryManager libraryManager) { + final List allNamespaceInfos = getAllNamespaceInfos(); + final NamespaceManager namespaceManager = libraryManager.getNamespaceManager(); + + for (NamespaceInfo namespaceInfo : allNamespaceInfos) { + // if we do this more than one time it won't error out subsequent times + namespaceManager.ensureNamespaceRegistered(namespaceInfo); + } + } + + /** + * @return All NamespaceInfos to map package IDs to package URLs for all NPM Packages maintained + * for clinical-reasoning NPM package to be used to resolve cross-package Library/CQL + * dependencies. + */ + default List getAllNamespaceInfos() { + return getNamespaceManager().getAllNamespaceInfos(); + } + + /** + * It's up to implementors to maintain the NamespaceManager that maintains the NamespaceInfos. + */ + NpmNamespaceManager getNamespaceManager(); + + default Optional findMatchingLibrary(VersionedIdentifier versionedIdentifier) { + return findLibraryFromUnrelatedNpmPackage(versionedIdentifier); + } + + default Optional findMatchingLibrary(ModelIdentifier modelIdentifier) { + return findLibraryFromUnrelatedNpmPackage(modelIdentifier); + } + + default Optional findLibraryFromUnrelatedNpmPackage(VersionedIdentifier versionedIdentifier) { + return loadLibraryByUrl(getUrl(versionedIdentifier)); + } + + default Optional findLibraryFromUnrelatedNpmPackage(ModelIdentifier modelIdentifier) { + return loadLibraryByUrl(getUrl(modelIdentifier)); + } + + private String getUrl(VersionedIdentifier versionedIdentifier) { + // We need this case because the CQL engine will do the right thing and populate the system + // in the cross-package target case + return LIBRARY_URL_TEMPLATE.formatted(versionedIdentifier.getSystem(), versionedIdentifier.getId()); + } + + static String getUrl(ModelIdentifier modelIdentifier) { + return LIBRARY_URL_TEMPLATE.formatted(modelIdentifier.getSystem(), modelIdentifier.getId()); + } + + /** + * @param libraryUrl The Library URL converted from a given + * withing one of the stored NPM packages. + * @return The Measure corresponding to the URL. + */ + Optional loadLibraryByUrl(String libraryUrl); +} diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoaderInMemory.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoaderInMemory.java new file mode 100644 index 0000000000..94fc0e4eaf --- /dev/null +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoaderInMemory.java @@ -0,0 +1,351 @@ +package org.opencds.cqf.fhir.utility.npm; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.regex.Pattern; +import org.hl7.cql.model.NamespaceInfo; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.utilities.npm.NpmPackage; +import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory; +import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; +import org.opencds.cqf.fhir.utility.adapter.IMeasureAdapter; +import org.opencds.cqf.fhir.utility.adapter.IResourceAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simplistic implementation of {@link NpmPackageLoader} that loads NpmPackages from the classpath + * and stores {@link NpmResourceHolder}s in a Map. This class is recommended for testing + * and NOT for production. + *

measureUrlToResourceInfo = new HashMap<>(); + private final Map libraryUrlToPackage = new HashMap<>(); + private final NpmNamespaceManager npmNamespaceManager; + + public static NpmPackageLoaderInMemory fromNpmPackageAbsolutePath(List tgzPaths) { + return fromNpmPackageAbsolutePath(null, tgzPaths); + } + + public static NpmPackageLoaderInMemory fromNpmPackageAbsolutePath( + NpmNamespaceManager npmNamespaceManager, List tgzPaths) { + final List npmPackages = buildNpmPackagesFromAbsolutePath(tgzPaths); + + return new NpmPackageLoaderInMemory(npmPackages, npmNamespaceManager); + } + + public static NpmPackageLoaderInMemory fromNpmPackageClasspath(Class clazz, Path... tgzPaths) { + return fromNpmPackageClasspath(null, clazz, tgzPaths); + } + + public static NpmPackageLoaderInMemory fromNpmPackageClasspath( + @Nullable NpmNamespaceManager npmNamespaceManager, Class clazz, Path... tgzPaths) { + return fromNpmPackageClasspath(npmNamespaceManager, clazz, Arrays.asList(tgzPaths)); + } + + public static NpmPackageLoaderInMemory fromNpmPackageClasspath(Class clazz, List tgzPaths) { + return fromNpmPackageClasspath(null, clazz, tgzPaths); + } + + public static NpmPackageLoaderInMemory fromNpmPackageClasspath( + @Nullable NpmNamespaceManager npmNamespaceManager, Class clazz, List tgzPaths) { + final List npmPackages = buildNpmPackageFromClasspath(clazz, tgzPaths); + + return new NpmPackageLoaderInMemory(npmPackages, npmNamespaceManager); + } + + record UrlAndVersion(String url, @Nullable String version) { + + static UrlAndVersion fromCanonical(String canonical) { + final String[] parts = PATTERN_PIPE.split(canonical); + if (parts.length > 2) { + throw new IllegalArgumentException("Invalid canonical URL: " + canonical); + } + if (parts.length == 1) { + return new UrlAndVersion(parts[0], null); + } + return new UrlAndVersion(parts[0], parts[1]); + } + + static UrlAndVersion fromCanonicalAndVersion(String canonical, @Nullable String version) { + if (version == null) { + return new UrlAndVersion(canonical, null); + } + + return new UrlAndVersion(canonical, version); + } + + @Override + @Nonnull + public String toString() { + return url + "|" + version; + } + } + + @Override + public NpmResourceHolder loadNpmResources(IPrimitiveType measureUrl) { + return measureUrlToResourceInfo.entrySet().stream() + .filter(entry -> doUrlAndVersionMatch(measureUrl, entry)) + .map(Map.Entry::getValue) + .findFirst() + .orElse(NpmResourceHolder.EMPTY); + } + + @Override + public Optional loadLibraryByUrl(String url) { + for (NpmPackage npmPackage : libraryUrlToPackage.values()) { + final FhirContext fhirContext = getFhirContext(npmPackage); + try (InputStream libraryInputStream = npmPackage.loadByCanonical(url)) { + if (libraryInputStream != null) { + final IResourceAdapter resourceAdapter = IAdapterFactory.createAdapterForResource( + fhirContext.newJsonParser().parseResource(libraryInputStream)); + if (resourceAdapter instanceof ILibraryAdapter libraryAdapter) { + return Optional.of(libraryAdapter); + } + } + } catch (IOException exception) { + throw new InternalErrorException(exception); + } + } + return Optional.empty(); + } + + @Override + public NpmNamespaceManager getNamespaceManager() { + return npmNamespaceManager; + } + + @Nonnull + private static List buildNpmPackagesFromAbsolutePath(List tgzPaths) { + return tgzPaths.stream() + .map(NpmPackageLoaderInMemory::getNpmPackageFromAbsolutePaths) + .toList(); + } + + @Nonnull + private static List buildNpmPackageFromClasspath(Class clazz, List tgzPaths) { + return tgzPaths.stream() + .map(path -> getNpmPackageFromClasspath(clazz, path)) + .toList(); + } + + @Nonnull + private static NpmPackage getNpmPackageFromAbsolutePaths(Path tgzPath) { + try (final InputStream npmStream = Files.newInputStream(tgzPath)) { + return NpmPackage.fromPackage(npmStream); + } catch (IOException exception) { + throw new InvalidRequestException(FAILED_TO_LOAD_RESOURCE_TEMPLATE.formatted(tgzPath), exception); + } + } + + @Nonnull + private static NpmPackage getNpmPackageFromClasspath(Class clazz, Path tgzClasspathPath) { + try (final InputStream simpleAlphaStream = clazz.getResourceAsStream(tgzClasspathPath.toString())) { + if (simpleAlphaStream == null) { + throw new InvalidRequestException(FAILED_TO_LOAD_RESOURCE_TEMPLATE.formatted(tgzClasspathPath)); + } + + return NpmPackage.fromPackage(simpleAlphaStream); + } catch (IOException exception) { + throw new InvalidRequestException(FAILED_TO_LOAD_RESOURCE_TEMPLATE.formatted(tgzClasspathPath), exception); + } + } + + private NpmPackageLoaderInMemory(List npmPackages, @Nullable NpmNamespaceManager npmNamespaceManager) { + + if (npmNamespaceManager == null) { + var namespaceInfos = npmPackages.stream() + .map(npmPackage -> new NamespaceInfo(npmPackage.name(), npmPackage.canonical())) + .toList(); + + this.npmNamespaceManager = new NpmNamespaceManagerFromList(namespaceInfos); + } else { + this.npmNamespaceManager = npmNamespaceManager; + } + + npmPackages.forEach(this::setup); + } + + private void setup(NpmPackage npmPackage) { + try { + trySetup(npmPackage); + } catch (Exception e) { + throw new InternalErrorException("Failed to setup NpmPackage: " + npmPackage.name(), e); + } + } + + private void trySetup(NpmPackage npmPackage) throws IOException { + final FhirContext fhirContext = getFhirContext(npmPackage); + + final Optional optPackageFolder = npmPackage.getFolders().entrySet().stream() + .filter(entry -> "package".equals(entry.getKey())) + .map(Map.Entry::getValue) + .findFirst(); + + if (optPackageFolder.isPresent()) { + setupNpmPackageInfo(npmPackage, optPackageFolder.get(), fhirContext); + } + } + + private void setupNpmPackageInfo( + NpmPackage npmPackage, NpmPackage.NpmPackageFolder packageFolder, FhirContext fhirContext) + throws IOException { + + final List resources = findResources(packageFolder, fhirContext); + + final Optional optMeasure = findMeasure(resources); + final List libraries = findLibraries(resources); + + storeResources(npmPackage, optMeasure.orElse(null), libraries); + } + + private List findResources(NpmPackage.NpmPackageFolder packageFolder, FhirContext fhirContext) + throws IOException { + + final Map> types = packageFolder.getTypes(); + final List resources = new ArrayList<>(); + + for (Map.Entry> typeToFiles : types.entrySet()) { + for (String nextFile : typeToFiles.getValue()) { + final String fileContents = new String(packageFolder.fetchFile(nextFile), StandardCharsets.UTF_8); + + if (nextFile.toLowerCase().endsWith(".json")) { + final IResourceAdapter resourceAdapter = IAdapterFactory.createAdapterForResource( + fhirContext.newJsonParser().parseResource(fileContents)); + + resources.add(resourceAdapter); + } + } + } + + return resources; + } + + private Optional findMeasure(List resources) { + return resources.stream() + .filter(IMeasureAdapter.class::isInstance) + .map(IMeasureAdapter.class::cast) + .findFirst(); + } + + private List findLibraries(List resources) { + return resources.stream() + .filter(ILibraryAdapter.class::isInstance) + .map(ILibraryAdapter.class::cast) + .toList(); + } + + private void storeResources( + NpmPackage npmPackage, @Nullable IMeasureAdapter measure, List libraries) { + if (measure != null) { + measureUrlToResourceInfo.put( + UrlAndVersion.fromCanonicalAndVersion(measure.getUrl(), measure.getVersion()), + new NpmResourceHolder(measure, findMatchingLibrary(measure, libraries), List.of(npmPackage))); + } + + for (ILibraryAdapter library : libraries) { + libraryUrlToPackage.put( + UrlAndVersion.fromCanonicalAndVersion(library.getUrl(), library.getVersion()), npmPackage); + } + } + + private ILibraryAdapter findMatchingLibrary(IMeasureAdapter measure, List libraries) { + return libraries.stream() + .filter(library -> measure.getLibrary().stream() + .anyMatch(measureLibraryUrl -> doMeasureUrlAndLibraryMatch(measureLibraryUrl, library))) + .findFirst() + .orElse(null); + } + + private static boolean doUrlAndVersionMatch( + IPrimitiveType measureUrl, Map.Entry entry) { + + if (entry.getKey().equals(UrlAndVersion.fromCanonical(measureUrl.getValueAsString()))) { + return true; + } + + return entry.getKey().url.equals(measureUrl.getValueAsString()); + } + + private static boolean doMeasureUrlAndLibraryMatch(String measureLibraryUrl, ILibraryAdapter library) { + final String[] split = PATTERN_PIPE.split(measureLibraryUrl); + + if (split.length == 1) { + return library.getUrl().equals(measureLibraryUrl); + } + + if (split.length == 2) { + return library.getUrl().equals(split[0]) && library.getVersion().equals(split[1]); + } + + throw new InternalErrorException("bad measureUrl: " + measureLibraryUrl); + } + + private FhirContext getFhirContext(NpmPackage npmPackage) { + return FhirContext.forCached(FhirVersionEnum.forVersionString(npmPackage.fhirVersion())); + } + + /** + * Meant to test various scenarios involving missing of faulty NamespaceInfo data. + */ + public static class NpmNamespaceManagerFromList implements NpmNamespaceManager { + + private final List namespaceInfos; + + public NpmNamespaceManagerFromList(List namespaceInfos) { + this.namespaceInfos = List.copyOf(namespaceInfos); + } + + @Override + public List getAllNamespaceInfos() { + return namespaceInfos; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + NpmNamespaceManagerFromList that = (NpmNamespaceManagerFromList) o; + return Objects.equals(namespaceInfos, that.namespaceInfos); + } + + @Override + public int hashCode() { + return Objects.hashCode(namespaceInfos); + } + + @Override + public String toString() { + return new StringJoiner(", ", NpmNamespaceManagerFromList.class.getSimpleName() + "[", "]") + .add("namespaceInfos=" + namespaceInfos) + .toString(); + } + } +} diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoaderWithCache.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoaderWithCache.java new file mode 100644 index 0000000000..bbf7b63ea7 --- /dev/null +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmPackageLoaderWithCache.java @@ -0,0 +1,110 @@ +package org.opencds.cqf.fhir.utility.npm; + +import java.util.List; +import java.util.Optional; +import org.hl7.cql.model.ModelIdentifier; +import org.hl7.cql.model.NamespaceInfo; +import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; + +/** + * Support operations against a set of NPM resources that will either operate on the already + * retrieved resources, or delegate to another {@link NpmPackageLoader} if the retrieved data + * does not return results for a given query. + */ +public class NpmPackageLoaderWithCache implements NpmPackageLoader { + + private final List npmResourceHolders; + private final NpmPackageLoader npmPackageLoader; + + public static NpmPackageLoaderWithCache of(NpmResourceHolder npmResourceHolder, NpmPackageLoader npmPackageLoader) { + return new NpmPackageLoaderWithCache(List.of(npmResourceHolder), npmPackageLoader); + } + + public static NpmPackageLoaderWithCache of( + List npmResourceHolders, NpmPackageLoader npmPackageLoader) { + return new NpmPackageLoaderWithCache(npmResourceHolders, npmPackageLoader); + } + + private NpmPackageLoaderWithCache(List npmResourceHolders, NpmPackageLoader npmPackageLoader) { + this.npmResourceHolders = npmResourceHolders; + this.npmPackageLoader = npmPackageLoader; + } + + @Override + public NpmNamespaceManager getNamespaceManager() { + return npmPackageLoader.getNamespaceManager(); + } + + @Override + public NpmResourceHolder loadNpmResources(IPrimitiveType measureUrl) { + return npmResourceHolders.stream() + .filter(npmResourceHolder -> isMeasureUrlMatch(npmResourceHolder, measureUrl)) + .findFirst() + .orElseGet(() -> npmPackageLoader.loadNpmResources(measureUrl)); + } + + @Override + public Optional findMatchingLibrary(VersionedIdentifier versionedIdentifier) { + var optLibrary = npmResourceHolders.stream() + .map(npmResourceHolder -> npmResourceHolder.findMatchingLibrary(versionedIdentifier)) + .flatMap(Optional::stream) + .findFirst(); + + if (optLibrary.isPresent()) { + return optLibrary; + } + + return findLibraryFromUnrelatedNpmPackage(versionedIdentifier); + } + + @Override + public Optional findMatchingLibrary(ModelIdentifier modelIdentifier) { + var optLibrary = npmResourceHolders.stream() + .map(npmResourceHolder -> npmResourceHolder.findMatchingLibrary(modelIdentifier)) + .flatMap(Optional::stream) + .findFirst(); + + if (optLibrary.isPresent()) { + return optLibrary; + } + + return findLibraryFromUnrelatedNpmPackage(modelIdentifier); + } + + @Override + public List getAllNamespaceInfos() { + return npmPackageLoader.getAllNamespaceInfos(); + } + + @Override + public Optional loadLibraryByUrl(String libraryUrl) { + + var optLibrary = npmResourceHolders.stream() + .filter(npmResourceHolder -> isLibraryUrlMatch(npmResourceHolder, libraryUrl)) + .map(NpmResourceHolder::getOptMainLibrary) + .flatMap(Optional::stream) + .findFirst(); + + if (optLibrary.isPresent()) { + return optLibrary; + } + + return npmPackageLoader.loadLibraryByUrl(libraryUrl); + } + + private static boolean isMeasureUrlMatch(NpmResourceHolder npmResourceHolder, IPrimitiveType measureUrl) { + return npmResourceHolder + .getMeasure() + .map(measure -> measure.getUrl().equals(measureUrl.getValue())) + .orElse(false); + } + + private static boolean isLibraryUrlMatch(NpmResourceHolder npmResourceHolder, String libraryUrl) { + return npmResourceHolder + .getMeasure() + .map(library -> library.getUrl().equals(libraryUrl)) + .orElse(false); + } +} diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmResourceHolder.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmResourceHolder.java new file mode 100644 index 0000000000..351354f982 --- /dev/null +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmResourceHolder.java @@ -0,0 +1,235 @@ +package org.opencds.cqf.fhir.utility.npm; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import com.google.common.annotations.VisibleForTesting; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; +import org.hl7.cql.model.ModelIdentifier; +import org.hl7.cql.model.NamespaceInfo; +import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.utilities.npm.NpmPackage; +import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory; +import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; +import org.opencds.cqf.fhir.utility.adapter.IMeasureAdapter; + +/** + * FHIR version agnostic container for Measures and Libraries, and a List of associated {@link NpmPackage}s. + * Encapsulate the NpmPackages by only exposing the {@link NamespaceInfo}s and derived + *Libraries not directly associated with a given Measure. + */ +public class NpmResourceHolder { + + public static final NpmResourceHolder EMPTY = new NpmResourceHolder(null, null, List.of()); + + private static final String TEXT_CQL = "text/cql"; + + @Nullable + private final IMeasureAdapter measure; + + @Nullable + private final ILibraryAdapter mainLibrary; + + // In theory, it's possible to have more than one associated NpmPackage + private final List npmPackages; + + @Nullable + private final IAdapterFactory adapterFactory; + + public NpmResourceHolder( + @Nullable IMeasureAdapter measure, @Nullable ILibraryAdapter mainLibrary, List npmPackages) { + this.measure = measure; + this.mainLibrary = mainLibrary; + this.npmPackages = npmPackages; + + adapterFactory = Optional.ofNullable(measure) + .map(measureNonNull -> IAdapterFactory.forFhirVersion( + measureNonNull.fhirContext().getVersion().getVersion())) + .orElse(null); + } + + public Optional getMeasure() { + return Optional.ofNullable(measure); + } + + public Optional getOptMainLibrary() { + return Optional.ofNullable(mainLibrary); + } + + @VisibleForTesting + List getNpmPackages() { + return npmPackages; + } + + public Optional findMatchingLibrary(VersionedIdentifier versionedIdentifier) { + + final Optional optMainLibrary = getOptMainLibrary(); + + if (doesLibraryMatch(versionedIdentifier)) { + return optMainLibrary; + } + + return loadNpmLibrary(versionedIdentifier); + } + + public Optional findMatchingLibrary(ModelIdentifier modelIdentifier) { + + final Optional optMainLibrary = getOptMainLibrary(); + + if (doesLibraryMatch(modelIdentifier)) { + return optMainLibrary; + } + + return loadNpmLibrary(modelIdentifier); + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) { + return false; + } + final NpmResourceHolder that = (NpmResourceHolder) object; + return Objects.equals(measure, that.measure) + && Objects.equals(mainLibrary, that.mainLibrary) + && Objects.equals(npmPackages, that.npmPackages); + } + + @Override + public int hashCode() { + return Objects.hash(measure, mainLibrary, npmPackages); + } + + @Override + public String toString() { + return new StringJoiner(", ", NpmResourceHolder.class.getSimpleName() + "[", "]") + .add("measure=" + measure) + .add("mainLibrary=" + mainLibrary) + .add("npmPackages=" + npmPackages) + .toString(); + } + + private Optional loadNpmLibrary(VersionedIdentifier versionedIdentifier) { + return npmPackages.stream() + .map(npmPackage -> loadLibraryInputStreamContext(npmPackage, versionedIdentifier)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(this::convertContextToLibrary) + .flatMap(Optional::stream) + .findFirst(); + } + + public List getNamespaceInfos() { + return npmPackages.stream().map(this::getNamespaceInfo).toList(); + } + + @Nonnull + private NamespaceInfo getNamespaceInfo(NpmPackage npmPackage) { + return new NamespaceInfo(npmPackage.name(), npmPackage.canonical()); + } + + // Note that this code hasn't actually been tested and is not needed at the present time. + // If this should change, the code will need to be tested and possibly modified. + private Optional loadNpmLibrary(ModelIdentifier modelIdentifier) { + return npmPackages.stream() + .map(npmPackage -> loadLibraryInputStreamContext(npmPackage, modelIdentifier)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(this::convertContextToLibrary) + .flatMap(Optional::stream) + .findFirst(); + } + + private boolean doesLibraryMatch(VersionedIdentifier versionedIdentifier) { + return doesLibraryMatch(versionedIdentifier.getId()); + } + + private boolean doesLibraryMatch(ModelIdentifier modelIdentifier) { + return doesLibraryMatch(modelIdentifier.getId()); + } + + private boolean doesLibraryMatch(String id) { + if (mainLibrary == null || adapterFactory == null) { + return false; + } + + if (mainLibrary.getId().getIdPart().equals(id)) { + return mainLibrary.getContent().stream() + .map(adapterFactory::createAttachment) + .anyMatch(attachment -> TEXT_CQL.equals(attachment.getContentType())); + } + + return false; + } + + record LibraryInputStreamContext(FhirVersionEnum fhirVersionEnum, InputStream libraryInputStream) {} + + Optional loadLibraryInputStreamContext( + NpmPackage npmPackage, VersionedIdentifier libraryIdentifier) { + return loadLibraryAsInputStream(npmPackage, libraryIdentifier) + .map(inputStream -> new LibraryInputStreamContext( + FhirVersionEnum.forVersionString(npmPackage.fhirVersion()), inputStream)); + } + + Optional loadLibraryInputStreamContext( + NpmPackage npmPackage, ModelIdentifier modelIdentifier) { + return loadLibraryAsInputStream(npmPackage, modelIdentifier) + .map(inputStream -> new LibraryInputStreamContext( + FhirVersionEnum.forVersionString(npmPackage.fhirVersion()), inputStream)); + } + + private Optional convertContextToLibrary(LibraryInputStreamContext context) { + try { + final IBaseResource resource = FhirContext.forCached(context.fhirVersionEnum) + .newJsonParser() + .parseResource(context.libraryInputStream); + + if (adapterFactory != null) { + return Optional.of(adapterFactory.createLibrary(resource)); + } + + return Optional.empty(); + } catch (Exception exception) { + throw new InternalErrorException("Failed to load library as input stream", exception); + } + } + + private static Optional loadLibraryAsInputStream( + NpmPackage npmPackage, VersionedIdentifier libraryIdentifier) { + + try { + return Optional.ofNullable(npmPackage.loadByCanonicalVersion( + buildUrl(npmPackage, libraryIdentifier), libraryIdentifier.getVersion())); + } catch (IOException exception) { + throw new InternalErrorException("Failed to load NPM package: " + libraryIdentifier.getId(), exception); + } + } + + private static Optional loadLibraryAsInputStream( + NpmPackage npmPackage, ModelIdentifier modelIdentifier) { + + try { + return Optional.ofNullable(npmPackage.loadByCanonicalVersion( + buildUrl(npmPackage, modelIdentifier), modelIdentifier.getVersion())); + } catch (IOException exception) { + throw new InternalErrorException("Failed to load NPM package: " + modelIdentifier.getId(), exception); + } + } + + @Nonnull + private static String buildUrl(NpmPackage npmPackage, VersionedIdentifier libraryIdentifier) { + return "%s/Library/%s".formatted(npmPackage.canonical(), libraryIdentifier.getId()); + } + + @Nonnull + private static String buildUrl(NpmPackage npmPackage, ModelIdentifier modelIdentifier) { + return "%s/Library/%s-ModelInfo".formatted(npmPackage.canonical(), modelIdentifier.getId()); + } +} diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/ig/IgRepository.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/ig/IgRepository.java index 0beb44f755..f3759ebb42 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/ig/IgRepository.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/ig/IgRepository.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableTable; import com.google.common.collect.Multimap; import com.google.common.collect.Table; +import jakarta.annotation.Nonnull; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -41,6 +42,8 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.fhir.utility.Ids; import org.opencds.cqf.fhir.utility.matcher.ResourceMatcher; +import org.opencds.cqf.fhir.utility.npm.NpmPackageLoader; +import org.opencds.cqf.fhir.utility.npm.NpmPackageLoaderInMemory; import org.opencds.cqf.fhir.utility.repository.Repositories; import org.opencds.cqf.fhir.utility.repository.ig.EncodingBehavior.PreserveEncoding; import org.opencds.cqf.fhir.utility.repository.ig.IgConventions.CategoryLayout; @@ -124,6 +127,10 @@ public class IgRepository implements IRepository { private final Path root; private final IgConventions conventions; private final ResourceMatcher resourceMatcher; + private final NpmPackageLoader npmPackageLoader; + private final Path npmJsonPath; + private final List npmTgzPaths; + private IRepositoryOperationProvider operationProvider; private final Cache> resourceCache = @@ -237,6 +244,66 @@ public IgRepository( this.conventions = requireNonNull(conventions, "conventions cannot be null"); this.resourceMatcher = Repositories.getResourceMatcher(this.fhirContext); this.operationProvider = operationProvider; + this.npmJsonPath = buildNpmJsonPath().orElse(null); + this.npmTgzPaths = buildTgzPaths(); + this.npmPackageLoader = buildNpmPackageLoader(npmTgzPaths); + } + + public NpmPackageLoader getNpmPackageLoader() { + return npmPackageLoader; + } + + public Path getJson() { + return this.npmJsonPath; + } + + @Nonnull + public List getNpmTgzPaths() { + return npmTgzPaths; + } + + public Path getRootPath() { + return this.root; + } + + private NpmPackageLoader buildNpmPackageLoader(List npmTgzPaths) { + return NpmPackageLoaderInMemory.fromNpmPackageAbsolutePath(npmTgzPaths); + } + + private Optional buildNpmJsonPath() { + final Path npmDir = resolveNpmPath(); + + // More often than not, the npm directory will not exist in an IgRepository + if (!Files.exists(npmDir) || !Files.isDirectory(npmDir)) { + return Optional.empty(); + } + + try (Stream npmSubPaths = Files.list(npmDir)) { + return npmSubPaths + .filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".json")) + .findFirst(); + } catch (IOException exception) { + throw new IllegalStateException("Could not resolve NPM JSON file", exception); + } + } + + private List buildTgzPaths() { + final Path npmDir = resolveNpmPath(); + + // More often than not, the npm directory will not exist in an IgRepository + if (!Files.exists(npmDir) || !Files.isDirectory(npmDir)) { + return List.of(); + } + + try (Stream npmSubPaths = Files.list(npmDir)) { + return npmSubPaths + .filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".tgz")) + .toList(); + } catch (IOException exception) { + throw new IllegalStateException("Could not resolve NPM namespace tgz files", exception); + } } public void setOperationProvider(IRepositoryOperationProvider operationProvider) { @@ -251,6 +318,11 @@ public void clearCache(Iterable paths) { this.resourceCache.invalidate(paths); } + @Nonnull + private Path resolveNpmPath() { + return root.resolve("input/npm"); + } + private boolean isExternalPath(Path path) { return path.getParent() != null && path.getParent().toString().toLowerCase().endsWith(EXTERNAL_DIRECTORY); @@ -361,7 +433,7 @@ protected Stream directoriesForCategory( Class resourceType, IgRepositoryCompartment igRepositoryCompartment) { var category = ResourceCategory.forType(resourceType.getSimpleName()); var categoryPaths = TYPE_DIRECTORIES.rowMap().get(this.conventions.categoryLayout()).get(category).stream() - .map(path -> this.root.resolve(path)); + .map(this.root::resolve); if (category == ResourceCategory.DATA && this.conventions.compartmentLayout() == CompartmentLayout.DIRECTORY_PER_COMPARTMENT) { var compartmentPath = pathForCompartment(resourceType, this.fhirContext, igRepositoryCompartment); diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/dstu3/MeasureAdapterTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/dstu3/MeasureAdapterTest.java index c4ccc164b3..79f19bf13a 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/dstu3/MeasureAdapterTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/dstu3/MeasureAdapterTest.java @@ -15,6 +15,7 @@ import java.time.LocalDate; import java.util.Date; import java.util.List; +import java.util.stream.Stream; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus; @@ -26,6 +27,9 @@ import org.hl7.fhir.dstu3.model.RelatedArtifact; import org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opencds.cqf.fhir.utility.Constants; import org.opencds.cqf.fhir.utility.adapter.TestVisitor; @@ -271,4 +275,21 @@ void adapter_get_all_dependencies_with_non_depends_on_related_artifacts() { assertTrue(dependencies.contains(dep.getReference())); }); } + + private static Stream getLibraryParams() { + return Stream.of( + Arguments.of(List.of(), List.of()), + Arguments.of( + List.of(new Reference("library1"), new Reference("library2")), + List.of("library1", "library2"))); + } + + @ParameterizedTest + @MethodSource("getLibraryParams") + void getLibrary(List measureLibraries, List expectedAdapterLibraries) { + var measure = new Measure().setLibrary(measureLibraries); + var measureAdapter = adapterFactory.createMeasure(measure); + + assertEquals(expectedAdapterLibraries, measureAdapter.getLibrary()); + } } diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r4/MeasureAdapterTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r4/MeasureAdapterTest.java index eaea70a5e4..ee05483cd9 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r4/MeasureAdapterTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r4/MeasureAdapterTest.java @@ -15,6 +15,7 @@ import java.time.LocalDate; import java.util.Date; import java.util.List; +import java.util.stream.Stream; import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; @@ -28,6 +29,9 @@ import org.hl7.fhir.r4.model.RelatedArtifact; import org.hl7.fhir.r4.model.RelatedArtifact.RelatedArtifactType; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opencds.cqf.fhir.utility.Constants; import org.opencds.cqf.fhir.utility.adapter.TestVisitor; @@ -277,4 +281,21 @@ void adapter_get_all_dependencies_with_non_depends_on_related_artifacts() { assertTrue(dependencies.indexOf(dep.getReference()) >= 0); }); } + + private static Stream getLibraryParams() { + return Stream.of( + Arguments.of(List.of(), List.of()), + Arguments.of( + List.of(new CanonicalType("library1"), new CanonicalType("library2")), + List.of("library1", "library2"))); + } + + @ParameterizedTest + @MethodSource("getLibraryParams") + void getLibrary(List measureLibraries, List expectedAdapterLibraries) { + var measure = new Measure().setLibrary(measureLibraries); + var measureAdapter = adapterFactory.createMeasure(measure); + + assertEquals(expectedAdapterLibraries, measureAdapter.getLibrary()); + } } diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r5/MeasureAdapterTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r5/MeasureAdapterTest.java index 42baf21fbe..def27cc2cf 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r5/MeasureAdapterTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r5/MeasureAdapterTest.java @@ -15,6 +15,7 @@ import java.time.LocalDate; import java.util.Date; import java.util.List; +import java.util.stream.Stream; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; @@ -28,6 +29,9 @@ import org.hl7.fhir.r5.model.RelatedArtifact; import org.hl7.fhir.r5.model.RelatedArtifact.RelatedArtifactType; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.opencds.cqf.fhir.utility.Constants; import org.opencds.cqf.fhir.utility.adapter.TestVisitor; @@ -249,4 +253,21 @@ void adapter_get_all_dependencies_with_non_depends_on_related_artifacts() { assertTrue(dependencies.contains(dep.getReference())); }); } + + private static Stream getLibraryParams() { + return Stream.of( + Arguments.of(List.of(), List.of()), + Arguments.of( + List.of(new CanonicalType("library1"), new CanonicalType("library2")), + List.of("library1", "library2"))); + } + + @ParameterizedTest + @MethodSource("getLibraryParams") + void getLibrary(List measureLibraries, List expectedAdapterLibraries) { + var measure = new Measure().setLibrary(measureLibraries); + var measureAdapter = adapterFactory.createMeasure(measure); + + assertEquals(expectedAdapterLibraries, measureAdapter.getLibrary()); + } } diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java new file mode 100644 index 0000000000..b523c568d1 --- /dev/null +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java @@ -0,0 +1,334 @@ +package org.opencds.cqf.fhir.utility.npm; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import ca.uhn.fhir.context.FhirVersionEnum; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.fhir.instance.model.api.ICompositeType; +import org.hl7.fhir.r4.model.CanonicalType; +import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory; +import org.opencds.cqf.fhir.utility.adapter.IAttachmentAdapter; +import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; +import org.opencds.cqf.fhir.utility.adapter.IMeasureAdapter; + +public abstract class BaseNpmResourceInfoForCqlTest { + + protected static final String DOT_TGZ = ".tgz"; + + protected static final String SIMPLE_ALPHA_LOWER = "simplealpha"; + protected static final String SIMPLE_ALPHA_MIXED = "SimpleAlpha"; + protected static final String SIMPLE_BRAVO_LOWER = "simplebravo"; + protected static final String SIMPLE_BRAVO_MIXED = "SimpleBravo"; + protected static final String WITH_DERIVED_LIBRARY = "WithDerivedLibrary"; + protected static final String DERIVED_LIBRARY_ID = "DerivedLibrary"; + protected static final String DERIVED_LIBRARY = DERIVED_LIBRARY_ID; + protected static final String DERIVED_LAYER_1_A = "DerivedLayer1a"; + protected static final String DERIVED_LAYER_1_B = "DerivedLayer1b"; + protected static final String DERIVED_LAYER_2_A = "DerivedLayer2a"; + protected static final String DERIVED_LAYER_2_B = "DerivedLayer2b"; + protected static final String CROSS_PACKAGE_SOURCE = "crosspackagesource"; + protected static final String CROSS_PACKAGE_SOURCE_ID = "CrossPackageSource"; + protected static final String CROSS_PACKAGE_TARGET = "crosspackagetarget"; + protected static final String CROSS_PACKAGE_TARGET_ID = "CrossPackageTarget"; + + protected static final String WITH_TWO_LAYERS_DERIVED_LIBRARIES = "with-two-layers-derived-libraries"; + protected static final String WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER = "WithTwoLayersDerivedLibraries"; + + protected static final String SIMPLE_ALPHA_TGZ = SIMPLE_ALPHA_LOWER + DOT_TGZ; + protected static final String SIMPLE_BRAVO_TGZ = SIMPLE_BRAVO_LOWER + DOT_TGZ; + protected static final Path WITH_DERIVED_LIBRARY_TGZ = Paths.get(WITH_DERIVED_LIBRARY + DOT_TGZ); + protected static final Path WITH_TWO_LAYERS_DERIVED_LIBRARIES_TGZ = + Paths.get(WITH_TWO_LAYERS_DERIVED_LIBRARIES + DOT_TGZ); + protected static final Path CROSS_PACKAGE_SOURCE_TGZ = Paths.get(CROSS_PACKAGE_SOURCE + DOT_TGZ); + protected static final Path CROSS_PACKAGE_TARGET_TGZ = Paths.get(CROSS_PACKAGE_TARGET + DOT_TGZ); + + protected static final String SLASH_MEASURE_SLASH = "/Measure/"; + protected static final String SLASH_LIBRARY_SLASH = "/Library/"; + + private static final String PIPE = "|"; + private static final String VERSION_0_1 = "0.1"; + private static final String VERSION_0_2 = "0.2"; + private static final String VERSION_0_3 = "0.3"; + private static final String VERSION_0_4 = "0.4"; + private static final String VERSION_0_5 = "0.5"; + private static final String VERSION_0_6 = "0.6"; + + protected static final String SIMPLE_ALPHA_NAMESPACE_URL = "http://simplealpha.npm.opencds.org"; + protected static final String SIMPLE_BRAVO_NAMESPACE_URL = "http://simplebravo.npm.opencds.org"; + protected static final String DERIVED_URL = "http://with-derived-library.npm.opencds.org"; + protected static final String DERIVED_TWO_LAYERS_URL = "http://with-two-layers-derived-libraries.npm.opencds.org"; + protected static final String CROSS_PACKAGE_SOURCE_URL = "http://crosspackagesource.npm.opencds.org"; + protected static final String CROSS_PACKAGE_TARGET_URL = "http://crosspackagetarget.npm.opencds.org"; + + protected static final String MEASURE_URL_ALPHA = + SIMPLE_ALPHA_NAMESPACE_URL + SLASH_MEASURE_SLASH + SIMPLE_ALPHA_MIXED; + protected static final String MEASURE_URL_BRAVO = + SIMPLE_BRAVO_NAMESPACE_URL + SLASH_MEASURE_SLASH + SIMPLE_BRAVO_MIXED; + protected static final String MEASURE_URL_WITH_DERIVED_LIBRARY = + DERIVED_URL + SLASH_MEASURE_SLASH + WITH_DERIVED_LIBRARY; + protected static final String MEASURE_URL_WITH_DERIVED_LIBRARY_WITH_VERSION = + MEASURE_URL_WITH_DERIVED_LIBRARY + PIPE + VERSION_0_2; + protected static final String MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES = + DERIVED_TWO_LAYERS_URL + SLASH_MEASURE_SLASH + WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER; + protected static final String MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION = + MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES + PIPE + VERSION_0_1; + + protected static final String MEASURE_URL_CROSS_PACKAGE_SOURCE = + CROSS_PACKAGE_SOURCE_URL + SLASH_MEASURE_SLASH + CROSS_PACKAGE_SOURCE_ID; + protected static final String MEASURE_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION = + MEASURE_URL_CROSS_PACKAGE_SOURCE + PIPE + VERSION_0_2; + + protected static final String LIBRARY_URL_ALPHA = + SIMPLE_ALPHA_NAMESPACE_URL + SLASH_LIBRARY_SLASH + SIMPLE_ALPHA_MIXED; + protected static final String LIBRARY_URL_BRAVO = + SIMPLE_BRAVO_NAMESPACE_URL + SLASH_LIBRARY_SLASH + SIMPLE_BRAVO_MIXED; + protected static final String LIBRARY_URL_WITH_DERIVED_LIBRARY = + DERIVED_URL + SLASH_LIBRARY_SLASH + WITH_DERIVED_LIBRARY; + protected static final String LIBRARY_URL_WITH_DERIVED_LIBRARY_AND_VERSION = + LIBRARY_URL_WITH_DERIVED_LIBRARY + PIPE + VERSION_0_1; + protected static final String LIBRARY_URL_DERIVED_LIBRARY = DERIVED_URL + SLASH_LIBRARY_SLASH + DERIVED_LIBRARY; + protected static final String LIBRARY_URL_CROSS_PACKAGE_SOURCE = + CROSS_PACKAGE_SOURCE_URL + SLASH_LIBRARY_SLASH + CROSS_PACKAGE_SOURCE_ID; + protected static final String LIBRARY_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION = + LIBRARY_URL_CROSS_PACKAGE_SOURCE + PIPE + VERSION_0_2; + protected static final String LIBRARY_URL_CROSS_PACKAGE_TARGET = + CROSS_PACKAGE_TARGET_URL + SLASH_LIBRARY_SLASH + CROSS_PACKAGE_TARGET_ID; + + protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES = + DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER; + + protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1A = + DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_1_A; + protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1B = + DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_1_B; + protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_2A = + DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_2_A; + protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_2B = + DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_2_B; + + protected abstract FhirVersionEnum getExpectedFhirVersion(); + + protected void simpleCommon(Path tgzPath, String measureUrl, String expectedLibraryUrl, String expectedCql) { + final NpmPackageLoaderInMemory loader = setup(tgzPath); + + final NpmResourceHolder npmResourceHolder = + loader.loadNpmResources(new org.hl7.fhir.r5.model.CanonicalType(measureUrl)); + + verifyMeasure(measureUrl, expectedLibraryUrl, npmResourceHolder); + verifyLibrary( + expectedLibraryUrl, + expectedCql, + npmResourceHolder.getOptMainLibrary().orElse(null)); + } + + protected void multiplePackages(String expectedCqlAlpha, String expectedCqlBravo) { + final NpmPackageLoaderInMemory loader = setup( + Stream.of(SIMPLE_ALPHA_TGZ, SIMPLE_BRAVO_TGZ).map(Paths::get).toArray(Path[]::new)); + + final NpmResourceHolder resourceInfoAlpha = + loader.loadNpmResources(new org.hl7.fhir.r5.model.CanonicalType(MEASURE_URL_ALPHA)); + final NpmResourceHolder resourceInfoBravo = + loader.loadNpmResources(new org.hl7.fhir.r5.model.CanonicalType(MEASURE_URL_BRAVO)); + + verifyMeasure(MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA, resourceInfoAlpha); + verifyLibrary( + LIBRARY_URL_ALPHA, + expectedCqlAlpha, + resourceInfoAlpha.getOptMainLibrary().orElse(null)); + + verifyMeasure(MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO, resourceInfoBravo); + verifyLibrary( + LIBRARY_URL_BRAVO, + expectedCqlBravo, + resourceInfoBravo.getOptMainLibrary().orElse(null)); + } + + protected void derivedLibrary(String expectedCql, String expectedCqlDerived) { + + final NpmPackageLoaderInMemory loader = setup(WITH_DERIVED_LIBRARY_TGZ); + + final NpmResourceHolder resourceInfoWithNoVersion = + loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_DERIVED_LIBRARY)); + verifyMeasure(MEASURE_URL_WITH_DERIVED_LIBRARY, LIBRARY_URL_WITH_DERIVED_LIBRARY, resourceInfoWithNoVersion); + final NpmResourceHolder resourceInfoWithVersion = + loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_DERIVED_LIBRARY_WITH_VERSION)); + verifyMeasure(MEASURE_URL_WITH_DERIVED_LIBRARY, LIBRARY_URL_WITH_DERIVED_LIBRARY, resourceInfoWithVersion); + verifyLibrary( + LIBRARY_URL_WITH_DERIVED_LIBRARY, + expectedCql, + resourceInfoWithVersion.getOptMainLibrary().orElse(null)); + + final ILibraryAdapter derivedLibraryFromNoVersion = resourceInfoWithVersion + .findMatchingLibrary(new VersionedIdentifier().withId(DERIVED_LIBRARY_ID)) + .orElse(null); + + verifyLibrary(LIBRARY_URL_DERIVED_LIBRARY, expectedCqlDerived, derivedLibraryFromNoVersion); + + final ILibraryAdapter derivedLibraryFromVersion = resourceInfoWithVersion + .findMatchingLibrary( + new VersionedIdentifier().withId(DERIVED_LIBRARY_ID).withVersion("0.4")) + .orElse(null); + + verifyLibrary(LIBRARY_URL_DERIVED_LIBRARY, expectedCqlDerived, derivedLibraryFromVersion); + + final ILibraryAdapter derivedLibraryFromBadVersion = resourceInfoWithVersion + .findMatchingLibrary( + new VersionedIdentifier().withId(DERIVED_LIBRARY_ID).withVersion("bad")) + .orElse(null); + + assertNull(derivedLibraryFromBadVersion); + } + + protected void derivedLibraryTwoLayers( + String expectedCql, + String expectedCqlDerived1a, + String expectedCqlDerived1b, + String expectedCqlDerived2a, + String expectedCqlDerived2b) { + + final NpmPackageLoaderInMemory loader = setup(WITH_TWO_LAYERS_DERIVED_LIBRARIES_TGZ); + + final NpmResourceHolder resourceInfoNoVersion = + loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES)); + verifyMeasure( + MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + resourceInfoNoVersion); + verifyLibrary( + LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + expectedCql, + resourceInfoNoVersion.getOptMainLibrary().orElse(null)); + + final NpmResourceHolder resourceInfoWithVersion = + loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION)); + verifyMeasure( + MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + resourceInfoWithVersion); + verifyLibrary( + LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + expectedCql, + resourceInfoWithVersion.getOptMainLibrary().orElse(null)); + + final ILibraryAdapter derivedLibrary1a = resourceInfoWithVersion + .findMatchingLibrary( + new VersionedIdentifier().withId(DERIVED_LAYER_1_A).withVersion("0.1")) + .orElse(null); + + verifyLibrary(LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1A, expectedCqlDerived1a, derivedLibrary1a); + + final ILibraryAdapter derivedLibrary1b = resourceInfoWithVersion + .findMatchingLibrary( + new VersionedIdentifier().withId(DERIVED_LAYER_1_B).withVersion("0.1")) + .orElse(null); + + verifyLibrary(LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1B, expectedCqlDerived1b, derivedLibrary1b); + + final ILibraryAdapter derivedLibrary2a = resourceInfoWithVersion + .findMatchingLibrary( + new VersionedIdentifier().withId(DERIVED_LAYER_2_A).withVersion("0.1")) + .orElse(null); + + verifyLibrary(LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_2A, expectedCqlDerived2a, derivedLibrary2a); + + final ILibraryAdapter derivedLibrary2b = resourceInfoWithVersion + .findMatchingLibrary( + new VersionedIdentifier().withId(DERIVED_LAYER_2_B).withVersion("0.1")) + .orElse(null); + + verifyLibrary(LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_2B, expectedCqlDerived2b, derivedLibrary2b); + } + + protected void crossPackage(String expectedCqlSource, String expectedCqlTarget) { + + final NpmPackageLoaderInMemory loader = setup(CROSS_PACKAGE_SOURCE_TGZ, CROSS_PACKAGE_TARGET_TGZ); + + final NpmResourceHolder resourceInfoWithNoVersion = + loader.loadNpmResources(new CanonicalType(MEASURE_URL_CROSS_PACKAGE_SOURCE)); + verifyMeasure( + MEASURE_URL_CROSS_PACKAGE_SOURCE, + LIBRARY_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION, + resourceInfoWithNoVersion); + final NpmResourceHolder resourceInfoWithVersion = + loader.loadNpmResources(new CanonicalType(MEASURE_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION)); + verifyMeasure( + MEASURE_URL_CROSS_PACKAGE_SOURCE, + LIBRARY_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION, + resourceInfoWithVersion); + + verifyLibrary( + LIBRARY_URL_CROSS_PACKAGE_SOURCE, + expectedCqlSource, + resourceInfoWithVersion.getOptMainLibrary().orElse(null)); + + final Optional matchingLibraryWithSourceResult = + resourceInfoWithVersion.findMatchingLibrary(new VersionedIdentifier().withId(CROSS_PACKAGE_TARGET_ID)); + + // We expect NOT to find the target Library here since it's not in the source package at all + assertTrue(matchingLibraryWithSourceResult.isEmpty()); + + // On the other hand, we can load the Library directly from the loader by URL: + final Optional optLibraryTarget = loader.loadLibraryByUrl(LIBRARY_URL_CROSS_PACKAGE_TARGET); + + assertTrue(optLibraryTarget.isPresent()); + verifyLibrary(LIBRARY_URL_CROSS_PACKAGE_TARGET, expectedCqlTarget, optLibraryTarget.get()); + } + + private void verifyLibrary(String expectedLibraryUrl, String expectedCql, @Nullable ILibraryAdapter library) { + assertNotNull(library); + + assertEquals( + getExpectedFhirVersion(), library.fhirContext().getVersion().getVersion()); + + assertEquals(expectedLibraryUrl, library.getUrl()); + + final List attachments = library.getContent(); + + assertEquals(1, attachments.size()); + + final ICompositeType attachment = attachments.get(0); + + final IAdapterFactory adapterFactory = IAdapterFactory.forFhirVersion( + library.fhirContext().getVersion().getVersion()); + + final IAttachmentAdapter adaptedAttachment = adapterFactory.createAttachment(attachment); + + assertEquals("text/cql", adaptedAttachment.getContentType()); + final byte[] attachmentData = adaptedAttachment.getData(); + final String cql = new String(attachmentData, StandardCharsets.UTF_8); + + assertEquals(expectedCql, cql); + } + + private void verifyMeasure(String measureUrl, String expectedLibraryUrl, NpmResourceHolder npmResourceHolder) { + + final Optional optMeasure = npmResourceHolder.getMeasure(); + assertTrue(optMeasure.isPresent()); + + final IMeasureAdapter measure = optMeasure.get(); + assertEquals( + getExpectedFhirVersion(), measure.fhirContext().getVersion().getVersion()); + assertEquals(measureUrl, measure.getUrl()); + + final List libraryUrls = measure.getLibrary(); + assertEquals(1, libraryUrls.size()); + final String libraryUrl = libraryUrls.get(0); + assertEquals(expectedLibraryUrl, libraryUrl); + } + + @Nonnull + private NpmPackageLoaderInMemory setup(Path... tgzPaths) { + return NpmPackageLoaderInMemory.fromNpmPackageClasspath(getClass(), tgzPaths); + } +} diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java new file mode 100644 index 0000000000..cfe2616bd4 --- /dev/null +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java @@ -0,0 +1,147 @@ +package org.opencds.cqf.fhir.utility.npm.r4; + +import ca.uhn.fhir.context.FhirVersionEnum; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.opencds.cqf.fhir.utility.npm.BaseNpmResourceInfoForCqlTest; + +@SuppressWarnings("squid:S2699") +class NpmResourceHolderR4Test extends BaseNpmResourceInfoForCqlTest { + + protected FhirVersionEnum fhirVersion = FhirVersionEnum.R4; + + private static final String EXPECTED_CQL_ALPHA = + """ + library opencds.simplealpha.SimpleAlpha + + using FHIR version '4.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + + parameter "Measurement Period" Interval + default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + exists ("Encounter Finished") + + define "Encounter Finished": + [Encounter] E + where E.status = 'finished' + """; + + private static final String EXPECTED_CQL_BRAVO = + """ + library opencds.simplealpha.SimpleBravo + + using FHIR version '4.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + + parameter "Measurement Period" Interval + default Interval[@2024-01-01T00:00:00.0-06:00, @2025-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + exists ("Encounter Planned") + + define "Encounter Planned": + [Encounter] E + where E.status = 'planned' + """; + + private static final String EXPECTED_CQL_WITH_DERIVED = + "library WithDerivedLibrary version '0.3' using FHIR version '4.0.1' include DerivedLibrary version '0.4' parameter \"Measurement Period\" Interval default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": DerivedLibrary.\"Has Initial Population\" define \"Denominator\": \"Initial Population\" define \"Numerator\": \"Initial Population\" "; + private static final String EXPECTED_CQL_DERIVED = + "library DerivedLibrary version '0.4' using FHIR version '4.0.1' context Patient define \"Has Initial Population\": true "; + + private static final String EXPECTED_CQL_DERIVED_TWO_LAYERS = + "library WithTwoLayersDerivedLibraries version '0.1' using FHIR version '4.0.1' include DerivedLayer1a version '0.1' include DerivedLayer1b version '0.1' parameter \"Measurement Period\" Interval default Interval[@2022-01-01T00:00:00.0-06:00, @2023-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": DerivedLayer1a.\"Has Initial Population\" define \"Denominator\": DerivedLayer1b.\"Has Denominator\" define \"Numerator\": DerivedLayer1b.\"Has Numerator\" "; + + private static final String EXPECTED_CQL_DERIVED_1_A = + "library DerivedLayer1a version '0.1' using FHIR version '4.0.1' include DerivedLayer2a version '0.1' include DerivedLayer2b version '0.1' define \"Has Initial Population\": DerivedLayer2a.\"Has Initial Population\" "; + + private static final String EXPECTED_CQL_DERIVED_1_B = + "library DerivedLayer1b version '0.1' using FHIR version '4.0.1' include DerivedLayer2a version '0.1' include DerivedLayer2b version '0.1' define \"Has Denominator\": DerivedLayer2a.\"Has Denominator\" define \"Has Numerator\": DerivedLayer2b.\"Has Numerator\" "; + + private static final String EXPECTED_CQL_DERIVED_2_A = + "library DerivedLayer2a version '0.1' using FHIR version '4.0.1' define \"Has Initial Population\": true define \"Has Denominator\": true "; + + private static final String EXPECTED_CQL_DERIVED_2_B = + "library DerivedLayer2b version '0.1' using FHIR version '4.0.1' define \"Has Numerator\": true "; + + private static final String EXPECTED_CQL_CROSS_SOURCE = + """ + library opencds.crosspackagesource.CrossPackageSource version '0.2' + + using FHIR version '4.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + include opencds.crosspackagetarget.CrossPackageTarget version '0.3' called CrossPackageTarget + + parameter "Measurement Period" Interval + default Interval[@2020-01-01T00:00:00.0-06:00, @2021-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + exists (CrossPackageTarget."Encounter Finished") + """; + + private static final String EXPECTED_CQL_CROSS_TARGET = + """ + library opencds.crosspackagetarget.CrossPackageTarget version '0.3' + + using FHIR version '4.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + + context Patient + + define "Encounter Finished": + [Encounter] E + where E.status = 'finished' + """; + + @Override + protected FhirVersionEnum getExpectedFhirVersion() { + return FhirVersionEnum.R4; + } + + @Test + void simpleAlpha() { + simpleCommon(Path.of(SIMPLE_ALPHA_TGZ), MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA, EXPECTED_CQL_ALPHA); + } + + @Test + void simpleBravo() { + simpleCommon(Path.of(SIMPLE_BRAVO_TGZ), MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO, EXPECTED_CQL_BRAVO); + } + + @Test + void multiplePackages() { + multiplePackages(EXPECTED_CQL_ALPHA, EXPECTED_CQL_BRAVO); + } + + @Test + void derivedLibrary() { + derivedLibrary(EXPECTED_CQL_WITH_DERIVED, EXPECTED_CQL_DERIVED); + } + + @Test + void derivedLibraryTwoLayers() { + derivedLibraryTwoLayers( + EXPECTED_CQL_DERIVED_TWO_LAYERS, + EXPECTED_CQL_DERIVED_1_A, + EXPECTED_CQL_DERIVED_1_B, + EXPECTED_CQL_DERIVED_2_A, + EXPECTED_CQL_DERIVED_2_B); + } + + @Test + void crossPackage() { + crossPackage(EXPECTED_CQL_CROSS_SOURCE, EXPECTED_CQL_CROSS_TARGET); + } +} diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java new file mode 100644 index 0000000000..b63cabc2e8 --- /dev/null +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java @@ -0,0 +1,88 @@ +package org.opencds.cqf.fhir.utility.npm.r5; + +import ca.uhn.fhir.context.FhirVersionEnum; +import java.nio.file.Path; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opencds.cqf.fhir.utility.npm.BaseNpmResourceInfoForCqlTest; + +@SuppressWarnings("squid:S2699") +class NpmResourceHolderR5Test extends BaseNpmResourceInfoForCqlTest { + + protected FhirVersionEnum fhirVersion = FhirVersionEnum.R5; + + private static final String EXPECTED_CQL_ALPHA = + "library SimpleAlpha using FHIR version '5.0.1' parameter \"Measurement Period\" Interval default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": true "; + private static final String EXPECTED_CQL_BRAVO = + "library SimpleBravo using FHIR version '5.0.1' parameter \"Measurement Period\" Interval default Interval[@2024-01-01T00:00:00.0-06:00, @2025-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": true "; + private static final String EXPECTED_CQL_WITH_DERIVED = + "library WithDerivedLibrary version '0.1' using FHIR version '5.0.1' include DerivedLibrary version '0.1' parameter \"Measurement Period\" Interval default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": DerivedLibrary.\"Has Initial Population\" define \"Denominator\": \"Initial Population\" define \"Numerator\": \"Initial Population\" "; + private static final String EXPECTED_CQL_DERIVED = + "library DerivedLibrary version '0.1' using FHIR version '5.0.1' context Patient define \"Has Initial Population\": true "; + + private static final String EXPECTED_CQL_DERIVED_TWO_LAYERS = + "library WithTwoLayersDerivedLibraries version '0.1' using FHIR version '5.0.1' include DerivedLayer1a version '0.1' include DerivedLayer1b version '0.1' parameter \"Measurement Period\" Interval default Interval[@2022-01-01T00:00:00.0-06:00, @2023-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": DerivedLayer1a.\"Has Initial Population\" define \"Denominator\": DerivedLayer1b.\"Has Denominator\" define \"Numerator\": DerivedLayer1b.\"Has Numerator\" "; + + private static final String EXPECTED_CQL_DERIVED_1_A = + "library DerivedLayer1a version '0.1' using FHIR version '5.0.1' include DerivedLayer2a version '0.1' include DerivedLayer2b version '0.1' define \"Has Initial Population\": DerivedLayer2a.\"Has Initial Population\" "; + + private static final String EXPECTED_CQL_DERIVED_1_B = + "library DerivedLayer1b version '0.1' using FHIR version '5.0.1' include DerivedLayer2a version '0.1' include DerivedLayer2b version '0.1' define \"Has Denominator\": DerivedLayer2a.\"Has Denominator\" define \"Has Numerator\": DerivedLayer2b.\"Has Numerator\" "; + + private static final String EXPECTED_CQL_DERIVED_2_A = + "library DerivedLayer2a version '0.1' using FHIR version '5.0.1' define \"Has Initial Population\": true define \"Has Denominator\": true "; + + private static final String EXPECTED_CQL_DERIVED_2_B = + "library DerivedLayer2b version '0.1' using FHIR version '5.0.1' define \"Has Numerator\": true "; + + private static final String EXPECTED_CQL_CROSS_SOURCE = + "library CrossPackageSource version '0.3' using FHIR version '5.0.1' include opencds.crosspackagetarget.CrossPackageTarget version '0.5' called CrossPackageTarget parameter \"Measurement Period\" Interval default Interval[@2020-01-01T00:00:00.0-06:00, @2021-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": CrossPackageTarget.\"Has Initial Population\" define \"Denominator\": CrossPackageTarget.\"Has Denominator\" define \"Numerator\": CrossPackageTarget.\"Has Numerator\" "; + + private static final String EXPECTED_CQL_CROSS_TARGET = + "library CrossPackageTarget version '0.5' using FHIR version '5.0.1' define \"Has Initial Population\": true define \"Has Denominator\": true define \"Has Numerator\": true "; + + @Override + protected FhirVersionEnum getExpectedFhirVersion() { + return FhirVersionEnum.R5; + } + + private static Stream simplePackagesParams() { + return Stream.of( + Arguments.of(SIMPLE_ALPHA_TGZ, MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA, EXPECTED_CQL_ALPHA), + Arguments.of(SIMPLE_BRAVO_TGZ, MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO, EXPECTED_CQL_BRAVO)); + } + + @ParameterizedTest + @MethodSource("simplePackagesParams") + void simple(Path tgzPath, String measureUrl, String expectedLibraryUrl, String expectedCql) { + simpleCommon(tgzPath, measureUrl, expectedLibraryUrl, expectedCql); + } + + @Test + void multiplePackages() { + multiplePackages(EXPECTED_CQL_ALPHA, EXPECTED_CQL_BRAVO); + } + + @Test + void derivedLibrary() { + derivedLibrary(EXPECTED_CQL_WITH_DERIVED, EXPECTED_CQL_DERIVED); + } + + @Test + void derivedLibraryTwoLayers() { + derivedLibraryTwoLayers( + EXPECTED_CQL_DERIVED_TWO_LAYERS, + EXPECTED_CQL_DERIVED_1_A, + EXPECTED_CQL_DERIVED_1_B, + EXPECTED_CQL_DERIVED_2_A, + EXPECTED_CQL_DERIVED_2_B); + } + + @Test + void crossPackage() { + crossPackage(EXPECTED_CQL_CROSS_SOURCE, EXPECTED_CQL_CROSS_TARGET); + } +} diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleAlpha.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleAlpha.tgz new file mode 100644 index 0000000000000000000000000000000000000000..22074561c50e2e66174317c2a262d8f4b885d102 GIT binary patch literal 1563 zcmV+$2ITo4iwFo|*s*8=|8r?=aBO8^Y;b5{E_7#l0PR^>bJ|D{=9yoi;>DZVAVxys zaBa3?umNG?#9#|h$`6AyNU}%A>M-Dx|ND+E8!)iWu0yi%d<7-V^c>xD_17b|#FjpO zhVu@B8uD`A1t7t;CjjO0(|eT90iNS>%sjUTgnj@$(tYl2g0C`GU|Q${R~0KlO{jBa z7H~DLF646*zdl^3-a;LfzGolNrml)&@~k9JzzeH;er5lcK^f#2ai9Aihqp*Wx)-X( zVpBO%Y&!k*=AWhKKRy4S&VRAW*V!5e%3_V4|8MF1KQkvD^cKZ|X}K63Vb_4{jqlj6 zwf`%Euv(#$CQBO{ zA+r&Le$CD4bF}CUz%2gD7qi~6ll+^)?CkQ0|8#RSJ8L$Qa^~)Grx(uYn6HOgTNX$^7voXi zjdUb7+$uxGomf{ye;h)RPju9;+Vz<>oEusj-%U)Z66q;58yL0mz1HdJr_F9_Vx8Y< zv0akhx1ze|jxxM3qrM`W$T(wZN4ZbBqf4z|N#50@ldDsyN&4z&4oPE@4%Kqs&^q$H zbPV(^AL)wkA(;;{mmi+iXRq^IpYs0={9mk7{{N={RQm)O*#0 zEHaoHrdJAmLc*t3qPbrh1Y|h{RI&O$zlx{%Z^w(MY9mFAOrt!nX zafm(kGiH`WLaO5>5zgyGJuCL4mK3v2c7!>qV>n*$viL^#oY37h#82b%%!bW&EG^=A zpdnrw;|*#rtG>ClUE}yNeg0E)nT`BETCs65UOWb?!J_HA7)INwASDejpPw4oS^r_H z^m?P@VcV2=CK(tL?w%G5Z9ODjYdP{Qni`3R&8dmJ9k!Ak?vkCE9ZWAZ^SODeIoJQ& zu!-`2%KumRKPRxf2r8mT`Ttwu|I56eoTjh7|99|zP$`RR`(Lb<1Tfs{%!8BJcM z&r53}pAUV)gqT)G>N6>`XAT4wu-rjDZ`vvjH55xb2)jhSUWid4#zXagMSM8(nJ@rHD=?BxM1%|faSQ`=Ne-z$iF4J^6oxo>6n^>06K_!j(eDy~!}!O7 zBB+q(dnS_dTcq#u`S==rPP!z{pLCtiYY|gN8=1jj?Ap=X^aG#yt#D$iP8cb{vvCSNp|56rLIi_jnsA*z(>aAIH z4HGrRdIGCDUOXU64Ls0qp%fGp6ciK`6ciK`6ciK`6ciK`6ciK`6ciK`6ciK`6cj%j N{sv<1?B)Pa002=~8xH^g literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleBravo.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleBravo.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ec8cf55f26a18aa8a054caa1a44743e23c118077 GIT binary patch literal 1566 zcmV+(2I2W1iwFpN+p%Z@|8r?=aBO8_a$$CFE_7#l0PR^>Q{zSu_OpM5$}ePV7g>j8 zFqKU$HrU7<0UKmnTRe=Uu`C!J(cyzF{(D*%_zs(}Bw6MwC|T1zJw0cCJz~pj`O77m zo?v8PzwkrArP%c3II;5lJ1*xrffowQwD1Oqf)M%8eBpgU*k|m}vhinrPpVe-tCAqH zHBk_x+J3P>@zcXY`aLpn`A4=9Z|XfsN}tvA$q7q)eqsNMoX8az=`Hs^iSDt1O+V5{ zvvqo)HXQzZ^Uu=rpPv6K=RaTD6D4~7zoYa2%+h@1&vv_(?Gaq}(b#400@r!1{a>wA zHl6<5V)pe5_g@od)hu%jCR>-uKCCd+G#z-)w3 z5Zf_)Gz|0NFm*reR8pe5hUJ)0_VuYO#2`C5yY;c&Qlf#-c{p<}Jgr|3)Lt{tTcgn#$=+|;vq4`SH%yP$ z1rn*VdaHX(y7Gxi^tg^tG20JU(RrgXZBN`&)RXSC6Xjm*rh46$0``=dKC26Azx{xE zwMcI0+}Yh>bl%m?fzS-JmU1s2t8!Oq%W7{riS?kp$)MjJX+4Eg`?4wD^JLJ!;B{LG z)tTEDwDJ8L*e(k!^v5rp6YeC>z!V?rvsrD87-v(?OYA| z9WV9~`f#uIR8O;Sz<&}UC&>hk5y3mm zPZQ>x17CT4c36`e#w4!~^Z5CBZKmt;w)jZnfgg{(p{95c&9D z;s27zukim$mGb{LAR7)ZHS-8 zXQdD8`!TnOlYvHLZj9HsJ@5K@Yum>0W&Zr5>@pwuzwE`@$$0S?EGCOq;1LvWtCE}= zU@o5xY_0#eQF^;p^0;YAdAjga#YRzZ9yOsg@ur;)>zooCR!#&uU`N8zk zZmxE3vE=rDn>tbcPx=2m|F2YeRuuOHsY1X1z9;@a&->vtef9mnh5vI^QCQmlQvA7? z^8YtLMx~jYCa3fn5pRUpxiwKNMgcG(rgfw ziO~@u*qB!-7Cq!c{s000DX*|0hBKGI09>QQ$PS2r3;%kALVOK})Nc@64Ln2<37>>t zzX-6Fb~zsYt~u@?`D3|Vt_sgjOe~c)NIw*d$u;^EnuOpFO&5zs%+&E(W^j;%c09F$ zFkpTu9Xq-k#ZEAnz(*Wxls-IHUlqo+zdjY;GRKLv$q4(HIcC%E8O $4m8h-stqD z@t?;3)%!nAlz5TG|92Gsv*)Yb`F&6PuSm<^|0PinY5ae~`@j4iZ;=LQ8}IY@yi`Yy z>sS!nUv{ha{nvm;@%R0w=1>%lTtE4&4)c|l#&=ap>RzyTDVUIt#XCL&gLMNMuyC+= zYZhO_#HLuk!s@P{49J{;NBTXKf`WpAf`WpAf`WpAf`WpAf`WpAf`WpAf`WpAf`WpA Q;=hJJ0rg;YhX7Cj056+4mjD0& literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/WithDerivedLibrary.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/WithDerivedLibrary.tgz new file mode 100644 index 0000000000000000000000000000000000000000..fcb76377cfe5314e3018cf0012fea234cbf34310 GIT binary patch literal 1327 zcmV+~11MOJZQsX!jE4Tkj||O-q766s=gcG|QK5B?QAu z5+g{0#B&7A@Epf&5yE>WRZEtnW(=u#+1#-mH_ohCzDW<%E-s$%Q_W-5{zoLL*8k8k zlPq?q5lUxmCq`F}eSoI18Rw15Bn(~^1Mm=e{?YYkB~h%_Uz8Y;CTN!BBw>r7-)N3c z%Kv=*Z+8d~64~TFLH@e<$-CueCI?eFa^z?ia2Ys#y3Ul93?FmeLRP8wnC)*E&2@ko^=MEbF_{UYC$qfNdG z*97siiji1hi~<*8-0o~|lU8V#zkU?Ix5*?=Qpd-Wt>ULn?4uy`LN{;BJgEq$hPE>( z6!-k<_|<-Q3oqcdKsAcPD2!7KUpZi-B=Sr=Me^is42G7(&zOnU;%ECtm6?QnM{L+f9lVTRg=p%_h~wX5eN}6S0rfxlZ@2 zzr6;soghUQ$cd{^q;nm?si^n#Bf*w}KLFJ^D z(vp}@gfYl`Cw>gXqt)R1LAV}cxvCBDwcNR@|MmS})w$2O|KZAK*7+a4|Cz7*|0Cpf z!~G7s=fRcw96czmapJA++JDC>xVjfsD$NqyX<8{*)%{i&q;O^mUVuApt2@1vdbmcQ zK4_KoO3gM?d^E{v*F%=C<<3AEo8x}i?+$ZwYs?Ke92~0r zO)r;Sdwdj4ear37XT5Wzu*SG|V~$#xu3GfLrJNlOELZP(*@V4jjPAMn8-8i**XnBd zWW;zj=<90l$QWri_PFOwN5jlkl~aflP1#eVcf1Vi*uIvWbow*+``iS(6C}I3D!WE^ z=ugJO1Z;ZKpzm3MF`H_se0VA6hXZ<1e1@ls!**DHRDN-ACbP$v^5Q^MN5@=mj=EleXe9m2c?qz^$G#M@2^KNTlkFFtCJZD|Rd4g^S&k3-Y zpRLw{M}w|*gXj2&EnwUC4ByL)fs6p#Y|1haPot-tj*cq~o`}6>e-;c=$N|P$xqcaz zwra08^*!1gX^E~WSJwAog6B77d8EtVzLiXJS$waT;!lyf{#Whmi~R<9rvB4>`Ti3{ zPT)C~f%m^CGU8YL{|LcFa8+rcF0X1duGgCic~KAVDlZgXrtGrJAPkUYzVJa;8o&G< z*pAJMv^ER@vzJ?S(SW}Z{U1)s)rnqUGNA*%0f<3@U21#Gd^tI zi1`M*#|mgxhD$iJpM7ZGP#*HXx8{CWo;&{%U-Lgv;5hvMA4cHluk-&A@)G{{=G^Z$ zSRMb64Q{siaL%`Zd#rJ}#lhui033w@o|YN8(j9cXf&Se9K6Qk-fZMw{&43eKWA64m l7n;qiTzQmN93zFcf}!^Z@{2F|k}vslGrz*a zOSdzPv5gH$X1mj1!YzSKAb~(953;P-0{ODOa7j1+edSxqz3H}1JB_}9*w#6g&XJC! z?@sIf+R`@-2C7L5c&b|X!8fJ6Vef91=vFG z#j;Y8kB+30$d?qMbR^5U1BTxoE`SqYpu&%ABbwA@MMPvVasBta4m1(ANP1>q$A@U);rukW}>D(;ZJ&E=p&tC`bAL4axFxg4z? z+lb3IZ5&`gxJ$W6&9NW&+#j1B@%b|wI$>lNSHm{;O{C}FK4S%E`#;Jj zbew|Fk80IDa35sFLE>2AZd?K#ow62M_VKAK!l#v|q{@XKAZy7`y31PFc8Fsvc$1W4 z%Tz~4y;!l3a;n3)&3Bvi(U#+Wq3>gOm}=)&^d6s{?&7=0)Qy-k*ujd`s}^S}*+@nd zqd=+rnjv`|1R#=Yb%LQ)TcBQL3hVuFD0Lq$tjM2!d z9X~*^Vh*Li97+9at=L}-r5i)H`U@zLsfJJOwkj`LGv{2t?YNo+jqb#3-QbEex@|px z!ExABp>Q!i4ck3KSDh6A1G_1c2kEnG|zA8#@r z`&|=SRIaYrN!x5Mx4G{B&G5EG`{(LhwJY@fS}?lp5{OUn^`LJ;+Bc(tNHqJ(xS=-E zIwwtBX=ygqu}7v>ou3VF(O+>x26k=-G`=X?&_DYIf-KGSd4wfl{&54 zg_248vgxYE`}d#YK8g9e+#}nS+4&#i|38)gmyZ@J;?uKBRtkEM{u$yw`C zA8#>x+4SwT?d!+q+4D^qXV&vy^kVB^Ji8B86G-g41Vj^U+ky4o_aApk@3u-FcMXY$ zX~$r&e^>~xjgU^`oiBk_ZWG5e5Etor86^IxSs3grhBU;cfU}|^K0<`;wJwW z8UKF~{$Jk0|K%fDVaNZkivOqAZzua-&;JEQSfBq&QIr|~e?k7A5%}Fd1K3aGA+Q}A zL-2KU{(q+d)#zwn%w7j!K-pw`qD}`fD~%{=KQY!{%?9FM9jsD$9nsn;C4dLh zTTxZKC-aoo#=W#u!6Q4aWME)mU|?WiU|?WiU|?WiU|?WiU|?WiU|?WiU|?WiVEBpn M54Bb&0RT_{0Q1K|p#T5? literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/crosspackagetarget.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/crosspackagetarget.tgz new file mode 100644 index 0000000000000000000000000000000000000000..080274ad5fb9917c5644f89faf6c4a6a7b3392a1 GIT binary patch literal 1364 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 A#{d8T literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/SimpleAlpha.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/SimpleAlpha.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3c28e4a70249726ddf8836843908616fb15a04df GIT binary patch literal 724 zcmV;_0xSI=iwFQ4@||b^1MOB@Z<{a_?sI+x;(1LVT%z_3(v{LBo7yChF!jM02b>UK zV4AS1|9$4FR7=`U>Sa~)NyyRhcYJIgpD!l@a0y)0IeJBK9M_X1rWoaN>Y>#00a&3uuD2*oa2VvXT)SIa)7WQES&xwDw9`Qwkh`^E zxT;@ZXU9rDwqp=ybfuq#p)l@PVo>rPEKxb!wC-E)eb@gZ>iVy)?mgF5ETh zusvO$iJ%SXz+Zml-OwCBKt6A4K8CCF42{$=F!{Bu8LEAuhO?34I@9lwy@Kxb(z~2s zwQEf;e*^O*)%%biER=LOg06m{deom-(`z4M1&>X`gMyJ+Chyz9l{?XljQT8ftX|py zzpKZR*DBsY0VvR-D(u~z?Y9vm7i*rtt;X=uVBW_)M#?^Xw7M5RMTo1-d_s2q6 z5Bh4!!;ka6(euo@C#j*x&u08k`}D@M5-fgqM+1rAC; zRN4xJiogLVwxN8)NpHES8fijN%(mjE3t|%|PO+Qq;xXE6_J?_@b8N>Cf~e|$H8$r- z7`*lx>^@{se7gT4P&gC zTQORN{HHv#B5~q{L7oInv5cj$&!6xvrO`>NMb;>F|% ztV1bv!#H0xZ%Y}Qxn4Xex0A5UcNSw&sR^2pXWwrMl1(BK*d)Ln$w`}%wid4lE&nCRJ26PJMwB?@SOhJlK9*ICBOjzg7!iG?}ADp zo}FvrUh@mhu|!OgFpV)8XA$8!#;G2ZI$vAIj?xF-A=S|*-J_bgwPPmVV-^u-Th*7M zemuo_$$KzGRdB;pzVEyK7g6iKy1ISnAF`Lehwp#6{^f)I-vgT+GG~IV9*0_hPufGF z<`--6FFix9qwMZ!n*X2k2vcvhH6I)Xlmv9^3!AMw+)vkMqGU~5_=jIrZ)rB7D9>Ac z-^0sZMn>8g8SvKXPdeK_b=RlYj6do7)EtYB-q9AO zVY;@l2KuR=+N{Q2_7VfA*|4jF^Rc?lKa;L{`)yu+*!pzXy_SbvHPKHC9(;xuv32H_ zAOUmg0==iXcHb}dsP`{@bLpq%NcBdB6tTE#b+ikstwF2b1I3x=p<|y7Ame-~?e(s4 Vu-F3*IN*Q}hF`sCmyQ4w0025IakT&d literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/WithDerivedLibrary.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/WithDerivedLibrary.tgz new file mode 100644 index 0000000000000000000000000000000000000000..207d268f9aae1ec2b028e290a3d8c1ca355cab7e GIT binary patch literal 1317 zcmV+=1={)_iwFQs2%-8RGtlF4_Gx1NgzzPt{_{c<{>08v}C(k z#Axx~@98Wk4(fQvWo}(h6-2tv*5Bzq{l(BQmxg0Ezr3MP6eaR}J%0+tlVZ5ySxTMe zM3ELKR$$pWMRP1ef2s3tnN%fdoEQ=*Nq9S3P-+UHql6LnB6_e(YJ8=n2NhbudZQQ%`Gs=-vFY2*?I z#3D(;gJyFD#-vqPlWw{D{LpIzq3xSi+z2A4+NjOPBbAmA>HA*oMZQZ%yL{)a>-8UH zjMxk!twPDaWXmqlA4%2ogfZ-5OlOcT(ty*hgYtCr$Hjw}C9% zPtXOjqcRj>Ux#oisy+Qkur;p9{uZ{Bq_Az8mPkhGC?waBI8yG)D#>%as;;i?&3DY_XC`DjdTrtk{;@BK?6d z`l)9}kAZl+nS474w_~g~y#c<~dw2c6y8p{Q_YwC$T>JFq{ufy;zyF2L`~O|!cE|k= zyXV1``W!vTukre8-L?OYQ?S)2R4PehJZP8+SXJX@;3sfqa$bN3Zp%BpkZQU@pgL%! z^+wGy5_~j?dCx_rr)Kt0o*L6ZFzAglW2(D)_d1;U{e|f-9m^YMCgWbsk>npK+;u;b z9BXThNh$S+;qlV(|T{@{HkBGr)IMW?OLF( zDg9%8qTX23zB`|cQ%jN0AWk@E&XCr1Q>hN4*PA;X@p&}Q0=xM#bhLSU5+Bgpm z=Qf)TLVdzhnj$ati4ORT9Bt{QfNMCLtejD=y|O0PkSkuZCg40lw~N;VSS-&swcyaO zr{3T-K4J^l4qV-HQ++5Qz&4#TG{jS>IjfyUq)ZmoA1(!w-C98ZUgID~wY-KUto3ILpHeE z=G{5p4(_qVxiPx+Kj`E%tTx>Z$802}}Sx)Ybw literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/cross-package-source.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/cross-package-source.tgz new file mode 100644 index 0000000000000000000000000000000000000000..edbc0155a87a2979b20d259105ed48ecc1a65b13 GIT binary patch literal 1294 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 Date: Fri, 29 Aug 2025 14:13:05 -0400 Subject: [PATCH 02/10] All R4 tests working. --- .../npm/BaseNpmResourceInfoForCqlTest.java | 74 ++++++---- .../npm/r4/NpmResourceHolderR4Test.java | 130 +++++++++++++++++- .../utility/npm/r4/WithDerivedLibrary.tgz | Bin 1327 -> 0 bytes .../r4/with-two-layers-derived-libraries.tgz | Bin 1602 -> 0 bytes .../utility/npm/r4/withderivedlibrary.tgz | Bin 0 -> 1821 bytes .../npm/r4/withtwolayersderivedlibraries.tgz | Bin 0 -> 2599 bytes 6 files changed, 169 insertions(+), 35 deletions(-) delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/WithDerivedLibrary.tgz delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/with-two-layers-derived-libraries.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/withderivedlibrary.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/withtwolayersderivedlibraries.tgz diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java index b523c568d1..0af2872ea2 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java @@ -30,9 +30,18 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected static final String SIMPLE_ALPHA_MIXED = "SimpleAlpha"; protected static final String SIMPLE_BRAVO_LOWER = "simplebravo"; protected static final String SIMPLE_BRAVO_MIXED = "SimpleBravo"; - protected static final String WITH_DERIVED_LIBRARY = "WithDerivedLibrary"; + protected static final String WITH_DERIVED_LIBRARY_LOWER = "withderivedlibrary"; + protected static final String WITH_DERIVED_LIBRARY_MIXED = "WithDerivedLibrary"; protected static final String DERIVED_LIBRARY_ID = "DerivedLibrary"; protected static final String DERIVED_LIBRARY = DERIVED_LIBRARY_ID; + + protected static final String NAMESPACE_PREFIX = "opencds."; + + protected static final String WITH_TWO_LAYERS_DERIVED_LIBRARIES = "withtwolayersderivedlibraries"; + protected static final String WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER = "WithTwoLayersDerivedLibraries"; + + protected static final String WITH_TWO_LAYERS_NAMESPACE = NAMESPACE_PREFIX + WITH_TWO_LAYERS_DERIVED_LIBRARIES; + protected static final String DERIVED_LAYER_1_A = "DerivedLayer1a"; protected static final String DERIVED_LAYER_1_B = "DerivedLayer1b"; protected static final String DERIVED_LAYER_2_A = "DerivedLayer2a"; @@ -42,12 +51,9 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected static final String CROSS_PACKAGE_TARGET = "crosspackagetarget"; protected static final String CROSS_PACKAGE_TARGET_ID = "CrossPackageTarget"; - protected static final String WITH_TWO_LAYERS_DERIVED_LIBRARIES = "with-two-layers-derived-libraries"; - protected static final String WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER = "WithTwoLayersDerivedLibraries"; - protected static final String SIMPLE_ALPHA_TGZ = SIMPLE_ALPHA_LOWER + DOT_TGZ; protected static final String SIMPLE_BRAVO_TGZ = SIMPLE_BRAVO_LOWER + DOT_TGZ; - protected static final Path WITH_DERIVED_LIBRARY_TGZ = Paths.get(WITH_DERIVED_LIBRARY + DOT_TGZ); + protected static final Path WITH_DERIVED_LIBRARY_TGZ = Paths.get(WITH_DERIVED_LIBRARY_LOWER + DOT_TGZ); protected static final Path WITH_TWO_LAYERS_DERIVED_LIBRARIES_TGZ = Paths.get(WITH_TWO_LAYERS_DERIVED_LIBRARIES + DOT_TGZ); protected static final Path CROSS_PACKAGE_SOURCE_TGZ = Paths.get(CROSS_PACKAGE_SOURCE + DOT_TGZ); @@ -66,8 +72,8 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected static final String SIMPLE_ALPHA_NAMESPACE_URL = "http://simplealpha.npm.opencds.org"; protected static final String SIMPLE_BRAVO_NAMESPACE_URL = "http://simplebravo.npm.opencds.org"; - protected static final String DERIVED_URL = "http://with-derived-library.npm.opencds.org"; - protected static final String DERIVED_TWO_LAYERS_URL = "http://with-two-layers-derived-libraries.npm.opencds.org"; + protected static final String DERIVED_URL = "http://withderivedlibrary.npm.opencds.org"; + protected static final String DERIVED_TWO_LAYERS_URL = "http://withtwolayersderivedlibraries.npm.opencds.org"; protected static final String CROSS_PACKAGE_SOURCE_URL = "http://crosspackagesource.npm.opencds.org"; protected static final String CROSS_PACKAGE_TARGET_URL = "http://crosspackagetarget.npm.opencds.org"; @@ -76,13 +82,13 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected static final String MEASURE_URL_BRAVO = SIMPLE_BRAVO_NAMESPACE_URL + SLASH_MEASURE_SLASH + SIMPLE_BRAVO_MIXED; protected static final String MEASURE_URL_WITH_DERIVED_LIBRARY = - DERIVED_URL + SLASH_MEASURE_SLASH + WITH_DERIVED_LIBRARY; + DERIVED_URL + SLASH_MEASURE_SLASH + WITH_DERIVED_LIBRARY_MIXED; protected static final String MEASURE_URL_WITH_DERIVED_LIBRARY_WITH_VERSION = - MEASURE_URL_WITH_DERIVED_LIBRARY + PIPE + VERSION_0_2; + MEASURE_URL_WITH_DERIVED_LIBRARY + PIPE + VERSION_0_4; protected static final String MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES = DERIVED_TWO_LAYERS_URL + SLASH_MEASURE_SLASH + WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER; protected static final String MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION = - MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES + PIPE + VERSION_0_1; + MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES + PIPE + VERSION_0_5; protected static final String MEASURE_URL_CROSS_PACKAGE_SOURCE = CROSS_PACKAGE_SOURCE_URL + SLASH_MEASURE_SLASH + CROSS_PACKAGE_SOURCE_ID; @@ -93,11 +99,13 @@ public abstract class BaseNpmResourceInfoForCqlTest { SIMPLE_ALPHA_NAMESPACE_URL + SLASH_LIBRARY_SLASH + SIMPLE_ALPHA_MIXED; protected static final String LIBRARY_URL_BRAVO = SIMPLE_BRAVO_NAMESPACE_URL + SLASH_LIBRARY_SLASH + SIMPLE_BRAVO_MIXED; - protected static final String LIBRARY_URL_WITH_DERIVED_LIBRARY = - DERIVED_URL + SLASH_LIBRARY_SLASH + WITH_DERIVED_LIBRARY; - protected static final String LIBRARY_URL_WITH_DERIVED_LIBRARY_AND_VERSION = - LIBRARY_URL_WITH_DERIVED_LIBRARY + PIPE + VERSION_0_1; + + protected static final String LIBRARY_URL_WITH_DERIVED_LIBRARY_NO_VERSION = + DERIVED_URL + SLASH_LIBRARY_SLASH + WITH_DERIVED_LIBRARY_MIXED; + protected static final String LIBRARY_URL_WITH_DERIVED_LIBRARY_WITH_VERSION = + DERIVED_URL + SLASH_LIBRARY_SLASH + WITH_DERIVED_LIBRARY_MIXED + PIPE + VERSION_0_4; protected static final String LIBRARY_URL_DERIVED_LIBRARY = DERIVED_URL + SLASH_LIBRARY_SLASH + DERIVED_LIBRARY; + protected static final String LIBRARY_URL_CROSS_PACKAGE_SOURCE = CROSS_PACKAGE_SOURCE_URL + SLASH_LIBRARY_SLASH + CROSS_PACKAGE_SOURCE_ID; protected static final String LIBRARY_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION = @@ -105,8 +113,10 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected static final String LIBRARY_URL_CROSS_PACKAGE_TARGET = CROSS_PACKAGE_TARGET_URL + SLASH_LIBRARY_SLASH + CROSS_PACKAGE_TARGET_ID; - protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES = + protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_NO_VERSION = DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER; + protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION = + LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_NO_VERSION + PIPE + VERSION_0_5; protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1A = DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_1_A; @@ -160,12 +170,18 @@ protected void derivedLibrary(String expectedCql, String expectedCqlDerived) { final NpmResourceHolder resourceInfoWithNoVersion = loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_DERIVED_LIBRARY)); - verifyMeasure(MEASURE_URL_WITH_DERIVED_LIBRARY, LIBRARY_URL_WITH_DERIVED_LIBRARY, resourceInfoWithNoVersion); + verifyMeasure( + MEASURE_URL_WITH_DERIVED_LIBRARY, + LIBRARY_URL_WITH_DERIVED_LIBRARY_WITH_VERSION, + resourceInfoWithNoVersion); final NpmResourceHolder resourceInfoWithVersion = loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_DERIVED_LIBRARY_WITH_VERSION)); - verifyMeasure(MEASURE_URL_WITH_DERIVED_LIBRARY, LIBRARY_URL_WITH_DERIVED_LIBRARY, resourceInfoWithVersion); + verifyMeasure( + MEASURE_URL_WITH_DERIVED_LIBRARY, + LIBRARY_URL_WITH_DERIVED_LIBRARY_WITH_VERSION, + resourceInfoWithVersion); verifyLibrary( - LIBRARY_URL_WITH_DERIVED_LIBRARY, + LIBRARY_URL_WITH_DERIVED_LIBRARY_NO_VERSION, expectedCql, resourceInfoWithVersion.getOptMainLibrary().orElse(null)); @@ -203,10 +219,10 @@ protected void derivedLibraryTwoLayers( loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES)); verifyMeasure( MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, - LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION, resourceInfoNoVersion); verifyLibrary( - LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_NO_VERSION, expectedCql, resourceInfoNoVersion.getOptMainLibrary().orElse(null)); @@ -214,37 +230,39 @@ protected void derivedLibraryTwoLayers( loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION)); verifyMeasure( MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, - LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION, resourceInfoWithVersion); verifyLibrary( - LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, + LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_NO_VERSION, expectedCql, resourceInfoWithVersion.getOptMainLibrary().orElse(null)); final ILibraryAdapter derivedLibrary1a = resourceInfoWithVersion - .findMatchingLibrary( - new VersionedIdentifier().withId(DERIVED_LAYER_1_A).withVersion("0.1")) + .findMatchingLibrary(new VersionedIdentifier() + .withId(DERIVED_LAYER_1_A) + .withVersion(VERSION_0_5) + .withSystem(DERIVED_TWO_LAYERS_URL)) .orElse(null); verifyLibrary(LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1A, expectedCqlDerived1a, derivedLibrary1a); final ILibraryAdapter derivedLibrary1b = resourceInfoWithVersion .findMatchingLibrary( - new VersionedIdentifier().withId(DERIVED_LAYER_1_B).withVersion("0.1")) + new VersionedIdentifier().withId(DERIVED_LAYER_1_B).withVersion(VERSION_0_5)) .orElse(null); verifyLibrary(LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1B, expectedCqlDerived1b, derivedLibrary1b); final ILibraryAdapter derivedLibrary2a = resourceInfoWithVersion .findMatchingLibrary( - new VersionedIdentifier().withId(DERIVED_LAYER_2_A).withVersion("0.1")) + new VersionedIdentifier().withId(DERIVED_LAYER_2_A).withVersion(VERSION_0_5)) .orElse(null); verifyLibrary(LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_2A, expectedCqlDerived2a, derivedLibrary2a); final ILibraryAdapter derivedLibrary2b = resourceInfoWithVersion .findMatchingLibrary( - new VersionedIdentifier().withId(DERIVED_LAYER_2_B).withVersion("0.1")) + new VersionedIdentifier().withId(DERIVED_LAYER_2_B).withVersion(VERSION_0_5)) .orElse(null); verifyLibrary(LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_2B, expectedCqlDerived2b, derivedLibrary2b); @@ -314,7 +332,7 @@ private void verifyLibrary(String expectedLibraryUrl, String expectedCql, @Nulla private void verifyMeasure(String measureUrl, String expectedLibraryUrl, NpmResourceHolder npmResourceHolder) { final Optional optMeasure = npmResourceHolder.getMeasure(); - assertTrue(optMeasure.isPresent()); + assertTrue(optMeasure.isPresent(), "Could not find measure with url: %s".formatted(measureUrl)); final IMeasureAdapter measure = optMeasure.get(); assertEquals( diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java index cfe2616bd4..4cdfe391f1 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java @@ -53,24 +53,140 @@ class NpmResourceHolderR4Test extends BaseNpmResourceInfoForCqlTest { """; private static final String EXPECTED_CQL_WITH_DERIVED = - "library WithDerivedLibrary version '0.3' using FHIR version '4.0.1' include DerivedLibrary version '0.4' parameter \"Measurement Period\" Interval default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": DerivedLibrary.\"Has Initial Population\" define \"Denominator\": \"Initial Population\" define \"Numerator\": \"Initial Population\" "; + """ + library opencds.withderivedlibrary WithDerivedLibrary version '0.4' + + using FHIR version '4.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + include DerivedLibrary version '0.4' + + parameter "Measurement Period" Interval + default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + exists (DerivedLibrary."Encounter Finished") + """; + private static final String EXPECTED_CQL_DERIVED = - "library DerivedLibrary version '0.4' using FHIR version '4.0.1' context Patient define \"Has Initial Population\": true "; + """ + library opencds.withderivedlibrary.DerivedLibrary version '0.4' + + using FHIR version '4.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + + context Patient + + define "Encounter Finished": + [Encounter] E + where E.status = 'finished' + """; private static final String EXPECTED_CQL_DERIVED_TWO_LAYERS = - "library WithTwoLayersDerivedLibraries version '0.1' using FHIR version '4.0.1' include DerivedLayer1a version '0.1' include DerivedLayer1b version '0.1' parameter \"Measurement Period\" Interval default Interval[@2022-01-01T00:00:00.0-06:00, @2023-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": DerivedLayer1a.\"Has Initial Population\" define \"Denominator\": DerivedLayer1b.\"Has Denominator\" define \"Numerator\": DerivedLayer1b.\"Has Numerator\" "; + """ + library opencds.withtwolayersderivedlibraries.WithTwoLayersDerivedLibraries version '0.5' + + using FHIR version '4.0.1' + + include DerivedLayer1a version '0.5' + include DerivedLayer1b version '0.5' + + parameter "Measurement Period" Interval + default Interval[@2022-01-01T00:00:00.0-06:00, @2023-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + DerivedLayer1a."Initial Population" + + define "Denominator": + DerivedLayer1b."Denominator" + + define "Numerator": + DerivedLayer1b."Numerator" + """; private static final String EXPECTED_CQL_DERIVED_1_A = - "library DerivedLayer1a version '0.1' using FHIR version '4.0.1' include DerivedLayer2a version '0.1' include DerivedLayer2b version '0.1' define \"Has Initial Population\": DerivedLayer2a.\"Has Initial Population\" "; + """ + library opencds.withtwolayersderivedlibraries.DerivedLayer1a version '0.5' + + using FHIR version '4.0.1' + + include DerivedLayer2a version '0.5' + include DerivedLayer2b version '0.5' + + context Patient + + define "Initial Population": + DerivedLayer2a."Initial Population" + """; private static final String EXPECTED_CQL_DERIVED_1_B = - "library DerivedLayer1b version '0.1' using FHIR version '4.0.1' include DerivedLayer2a version '0.1' include DerivedLayer2b version '0.1' define \"Has Denominator\": DerivedLayer2a.\"Has Denominator\" define \"Has Numerator\": DerivedLayer2b.\"Has Numerator\" "; + """ + library opencds.withtwolayersderivedlibraries.DerivedLayer1b version '0.5' + + using FHIR version '4.0.1' + + include DerivedLayer2a version '0.5' + include DerivedLayer2b version '0.5' + + context Patient + + define "Denominator": + DerivedLayer2a."Denominator" + + define "Numerator": + DerivedLayer2b."Numerator" + """; private static final String EXPECTED_CQL_DERIVED_2_A = - "library DerivedLayer2a version '0.1' using FHIR version '4.0.1' define \"Has Initial Population\": true define \"Has Denominator\": true "; + """ + library opencds.withtwolayersderivedlibraries.DerivedLayer2a version '0.5' + + using FHIR version '4.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + + context Patient + + define "Initial Population": + exists ("Encounter Finished") + + define "Denominator": + exists ("Encounter Planned") + + define "Encounter Finished": + [Encounter] E + where E.status = 'finished' + + define "Encounter Planned": + [Encounter] E + where E.status = 'planned' + """; private static final String EXPECTED_CQL_DERIVED_2_B = - "library DerivedLayer2b version '0.1' using FHIR version '4.0.1' define \"Has Numerator\": true "; + """ + library opencds.withtwolayersderivedlibraries.DerivedLayer2b version '0.5' + + using FHIR version '4.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + + parameter "Measurement Period" Interval + default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) + + context Patient + + define "Numerator": + exists ("Encounter Triaged") + + define "Encounter Triaged": + [Encounter] E + where E.status = 'triaged' + """; private static final String EXPECTED_CQL_CROSS_SOURCE = """ diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/WithDerivedLibrary.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/WithDerivedLibrary.tgz deleted file mode 100644 index fcb76377cfe5314e3018cf0012fea234cbf34310..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1327 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 A#{d8T diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/withderivedlibrary.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/withderivedlibrary.tgz new file mode 100644 index 0000000000000000000000000000000000000000..7a6dd561eff734ac0d8fadba0c79fae97f14f84e GIT binary patch literal 1821 zcmV+&2jci2iwFq&=CNo3|95G0Xk=w_X?A5~Y-wV0VRCscbZ2@1?V4+I;x-q6^O;}a z;mfu&8)F*`B-3^Zgo{Z?H;@IwPCob|Yz1Mvz63~{|K1}TZ!ouH`zE~Gjn2Rrq$BC* zdLGH*bA0aiPwKpdR0DZOZz4i0yPkrOFFgIG;i4dk(h)a5I)H?Jpn6dJ(fbss%uNG4 zMZbzAxmYNl6pAIjEK0IakTXY2et2_g@P!YVOz30Vo}uGP0)`j{EX8{FbZ3e~z13$U|J6hh7Up zpZk^jFkUA3z?QY|&;RE){lCck|3UnJY5V??xOB_J8(c9E}Ycm>wHR+|r>_1t5YX)K;fxcbd>6 zG|~zJ_cWK=!W_0c51?`~Xc?j0CutV%s5(x!^5Qima+R2I@tMxuxO9QJEh%^MjJ3_JH(QKVE8QQ+khI z|J_Ev9+%aKXvhvyR;MjaRFdAK@RS3DUo#|YsDVo3wI;#(w6Q?F>b>6Vhc{C9;legt zqiIb>!?tTo`;#Xfxt@ewKc$l(>_Huzgi1}HUEE#SHPaP!iG-s?wb`qap3)i--M(}+ zsbIDruEI{OFmKPCb9Er!X|4Wb)Qk0c&AtydEc8VxPvPRO5_WpJc_TG^t=XR_^^wx+ zx0TUgKBIb2-km1N!f0p-N~3BSO+qyL>by4XlF=0G=v8o+mX+^^2ILRvU2RGpRGQbF zbFS2?+G_l;S8ddA#f0!Y=q$j##)W+;+zh*-zNnliHDe04ySikNQB6O8>btn#ZZnUJ z|JeD@fBpO?=08y`3lamscgTMO@WrpqfY0!sB(9(TLr76%{P(@_-{#nO83XQ4_pdo{ zcb1F=-;xEZLv`47y1+;3a70G+xqD-`E#01&ptk`2yVr2V2G$cO>ls7fzo~9^CszA2 z-pY5tYXr)ii=*b}SZ`txtyKkf?4$cLow6x|N7;KFnXD*a==^KMCQR-cy zPl+@dbUW&xy|@{+Sn z5|vK0tP*FyeR|4ZZ~DibvA0`e9(Q$#hoNDxy1!e9YMUYWxYgb8F*ab2jK>&xd+G`^ z+=ZE$EL<nHr#L zSml0;{H=k;YDj{w(O-U-z~LMV^m8o;r_qNOj)fvTKXNoy+7bKnp3ACRaN#s6AI-5J z_}oux^KIMh)}P;c6=wdQ`TvdcKcQScIbr_)J^BCT^K~wKexv`Fh4uL_=fwi^{|7w( zOAhdMW5E7{JYBUN8-pMJrsKSSeGGU;U+s%Yay1Mr$BW*mLnkJo^hl~78|$Y}10(84 zALSVcayOs=jQsTB7(H#FS@B+h*BvivrIidG*(*>cOqeiX!h{JECQO(xVZwyTcP{?` LbD;&X08jt`AxFdC literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/withtwolayersderivedlibraries.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/withtwolayersderivedlibraries.tgz new file mode 100644 index 0000000000000000000000000000000000000000..8d91473cc4857aab58cf580e86732906fd248ff3 GIT binary patch literal 2599 zcmV+?3fT1@iwFpd?XhSA|95G0XmodPY+-q2a&u&5a%pyDWNc|-a$$05WpgfcXL|c??7w4U6V^)wp({mmmU~p*#A(}q@A)84_w#kCv8~^vYNr)0QkyeJ+ z)NO4fb#rS;QulYSMM)l!M}J<)!&6<>bbs$tgi2-6D@C!~>(B9Vnqp{Xj~MQ4K%yX& zeW-qKH-$MS>=4=dH@d_ZxiU?2C6eLUa*1d5_pto-;>l`XHuR&ca^7*%-zOLzpAF1YOo*t;(-~WEspWKT1pPilmX*~aLWd1MZ=6|Zp z(ImxF#}tdF|6Q8@Pjnv*bnRRo={_x!>VK?{eg2Caw{ZUF;9<`{|3#K5?h(``pZ^`5 z|1G5WvOgkRPj^%;ARkd^hL5fl-v@z~-V~`0(gQMomkSlu&i@ySB*T`940iuJ z>;IVz$NwdYneqQDgZ=+@`+sJw_&+W^)h!RorTt2XGws;dF5z_A}M$71;__bxR#3PRnU zXfJqa8#%7!8Y9xQN=d6~uBLYq#iKD&9VT^tnY9NsCqo10BlU2sUC8M@zr6dI{PaBT zM#3Uam}_;u#hFTWA!Bqo0-B#{NI{cBIWAY47EwNAt9%Nohzp|E(x8UV1S*G%2)#hRh-Kj_jvwLcWQ>`DfVTciET@$Pup$HwF@>Ml+#{c>Qw7l z`)-;IUb+=Vcc{w1F{rm32{je!=*D$CC=5?Yw5A)wF|55O!g!ymR`A^vl{}nmRe}6mvQ`X==<$@h>(2uw52w;b&75EvLaiAjMY?Hi**Z<)4KXX3+$>5KFV*X<`lmBCAo}J}C3iIDK z^B=QD{*U>z{2zn!e_O$Stq;oo#doQIQM)Xv+{Up>3)Q&lm1slL_&^BnN z^&~fYeF61XV#kHH8r9%VqKoKQxKd2Ms+adAD}=Fn7Z-jZrUv4$#!NX7RJ}}Q)PQ2Nu=X^FK})T z+Fu5KueK7lbtF?c8P*{NuzF1^lx{fS>1tmRB~ufJlu)-4Uf1kKAX1gl#fdOHzo{hO zy_3r0_lNZR{Gk$FoZJ;JPAY*=PxMA_=y|K;q;H%a^re;Nn6q^V%^n9a0i}*AuF#+0 zbX4uaH8CY-PJEJ!J=!n^4QK6`f_*vP+4eOKSTO%%{(leuGb~5rPXO&A|BJw4H;-;Y zRY8VWFD$lRIPez#v!$v19~l-xP|W|^#{Wz2jn%?|l}vm@bg)uII9S+VuwW&6UC_H2 z>rE`$c-8xy1tg94E+82$SU~c42OzG9y(D2S^l9tv_L5d@0CWIn0Qk7xfm9QWP72b@ zo%nd%ZZ;^X*Y*^#`_DMB{zh;yS~0kI`iS7-{R^HOZ!CEB;=aMP=o&K%WER^*V6zTm zI8a0@$`-=B83fI@lUsO$gA>gEnE&6)|9lbe0I*a1KmKs_|Aqfc)BFF%{EyfFY?A-q z0r=CQ!uZZvi}dFQ3%|nu%Thrulv#TajKHM;eOjTDt7a zFjE#Ysji>KLLUl7L8cP1g@si-Zi{jl5+;>`iPUHPwgt{g`-YP`VM- z#2C+M_GHuNTU$1ctF`Bs;?Qj5-|WTQi?Q+;4-07U`hUFs ze{%m%n&wHK;#iu;_`gg1znuGjZ9A~HKK}{-rud)cIQ;iNHX`ra|7+=8@~hi{)2nO; zKD+DqIy;0d*dvTtxV*Xn%|I3w2 zSg>Hhf&~i}ELgB$!GZ+~7A#n>V8Ma~3l=O`uwcQ01q&7|Sg?GK{10fY Jy&wRf000K3M Date: Fri, 29 Aug 2025 14:38:47 -0400 Subject: [PATCH 03/10] Start setting up R5. --- .../npm/r5/NpmResourceHolderR5Test.java | 140 ++++++++++++++---- .../cqf/fhir/utility/npm/r5/SimpleAlpha.tgz | Bin 724 -> 0 bytes .../cqf/fhir/utility/npm/r5/SimpleBravo.tgz | Bin 739 -> 0 bytes .../utility/npm/r5/WithDerivedLibrary.tgz | Bin 1317 -> 0 bytes .../utility/npm/r5/cross-package-source.tgz | Bin 1294 -> 0 bytes .../utility/npm/r5/cross-package-target.tgz | Bin 634 -> 0 bytes .../cqf/fhir/utility/npm/r5/simplealpha.tgz | Bin 0 -> 1557 bytes .../r5/with-two-layers-derived-libraries.tgz | Bin 1602 -> 0 bytes .../npm/r5/withtwolayersderivedlibraries.tgz | Bin 0 -> 2598 bytes 9 files changed, 111 insertions(+), 29 deletions(-) delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/SimpleAlpha.tgz delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/SimpleBravo.tgz delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/WithDerivedLibrary.tgz delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/cross-package-source.tgz delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/cross-package-target.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/simplealpha.tgz delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/with-two-layers-derived-libraries.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/withtwolayersderivedlibraries.tgz diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java index b63cabc2e8..5c5c7d2a10 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java @@ -2,11 +2,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import java.nio.file.Path; -import java.util.stream.Stream; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.opencds.cqf.fhir.utility.npm.BaseNpmResourceInfoForCqlTest; @SuppressWarnings("squid:S2699") @@ -14,51 +10,137 @@ class NpmResourceHolderR5Test extends BaseNpmResourceInfoForCqlTest { protected FhirVersionEnum fhirVersion = FhirVersionEnum.R5; - private static final String EXPECTED_CQL_ALPHA = - "library SimpleAlpha using FHIR version '5.0.1' parameter \"Measurement Period\" Interval default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": true "; - private static final String EXPECTED_CQL_BRAVO = - "library SimpleBravo using FHIR version '5.0.1' parameter \"Measurement Period\" Interval default Interval[@2024-01-01T00:00:00.0-06:00, @2025-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": true "; - private static final String EXPECTED_CQL_WITH_DERIVED = - "library WithDerivedLibrary version '0.1' using FHIR version '5.0.1' include DerivedLibrary version '0.1' parameter \"Measurement Period\" Interval default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": DerivedLibrary.\"Has Initial Population\" define \"Denominator\": \"Initial Population\" define \"Numerator\": \"Initial Population\" "; - private static final String EXPECTED_CQL_DERIVED = - "library DerivedLibrary version '0.1' using FHIR version '5.0.1' context Patient define \"Has Initial Population\": true "; + private static final String EXPECTED_CQL_ALPHA = """ + """; + private static final String EXPECTED_CQL_BRAVO = """ + """; + private static final String EXPECTED_CQL_WITH_DERIVED = """ + """; + private static final String EXPECTED_CQL_DERIVED = """ + """; private static final String EXPECTED_CQL_DERIVED_TWO_LAYERS = - "library WithTwoLayersDerivedLibraries version '0.1' using FHIR version '5.0.1' include DerivedLayer1a version '0.1' include DerivedLayer1b version '0.1' parameter \"Measurement Period\" Interval default Interval[@2022-01-01T00:00:00.0-06:00, @2023-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": DerivedLayer1a.\"Has Initial Population\" define \"Denominator\": DerivedLayer1b.\"Has Denominator\" define \"Numerator\": DerivedLayer1b.\"Has Numerator\" "; + """ + library opencds.withtwolayersderivedlibraries.WithTwoLayersDerivedLibraries version '0.5' + + using FHIR version '5.0.1' + + include DerivedLayer1a version '0.5' + include DerivedLayer1b version '0.5' + + parameter "Measurement Period" Interval + default Interval[@2022-01-01T00:00:00.0-06:00, @2023-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + DerivedLayer1a."Initial Population" + + define "Denominator": + DerivedLayer1b."Denominator" + + define "Numerator": + DerivedLayer1b."Numerator" + """; private static final String EXPECTED_CQL_DERIVED_1_A = - "library DerivedLayer1a version '0.1' using FHIR version '5.0.1' include DerivedLayer2a version '0.1' include DerivedLayer2b version '0.1' define \"Has Initial Population\": DerivedLayer2a.\"Has Initial Population\" "; + """ + library opencds.withtwolayersderivedlibraries.DerivedLayer1a version '0.5' + + using FHIR version '5.0.1' + + include DerivedLayer2a version '0.5' + include DerivedLayer2b version '0.5' + + context Patient + + define "Initial Population": + DerivedLayer2a."Initial Population" + """; private static final String EXPECTED_CQL_DERIVED_1_B = - "library DerivedLayer1b version '0.1' using FHIR version '5.0.1' include DerivedLayer2a version '0.1' include DerivedLayer2b version '0.1' define \"Has Denominator\": DerivedLayer2a.\"Has Denominator\" define \"Has Numerator\": DerivedLayer2b.\"Has Numerator\" "; + """ + library opencds.withtwolayersderivedlibraries.DerivedLayer1b version '0.5' + + using FHIR version '5.0.1' + + include DerivedLayer2a version '0.5' + include DerivedLayer2b version '0.5' + + context Patient + + define "Denominator": + DerivedLayer2a."Denominator" + + define "Numerator": + DerivedLayer2b."Numerator" + """; private static final String EXPECTED_CQL_DERIVED_2_A = - "library DerivedLayer2a version '0.1' using FHIR version '5.0.1' define \"Has Initial Population\": true define \"Has Denominator\": true "; + """ + library opencds.withtwolayersderivedlibraries.DerivedLayer2a version '0.5' + + using FHIR version '5.0.1' + + include FHIRHelpers version '5.0.1' called FHIRHelpers + + context Patient + + define "Initial Population": + exists ("Encounter Finished") + + define "Denominator": + exists ("Encounter Planned") + + define "Encounter Finished": + [Encounter] E + where E.status = 'finished' + + define "Encounter Planned": + [Encounter] E + where E.status = 'planned' + """; private static final String EXPECTED_CQL_DERIVED_2_B = - "library DerivedLayer2b version '0.1' using FHIR version '5.0.1' define \"Has Numerator\": true "; + """ + library opencds.withtwolayersderivedlibraries.DerivedLayer2b version '0.5' + + using FHIR version '5.0.1' - private static final String EXPECTED_CQL_CROSS_SOURCE = - "library CrossPackageSource version '0.3' using FHIR version '5.0.1' include opencds.crosspackagetarget.CrossPackageTarget version '0.5' called CrossPackageTarget parameter \"Measurement Period\" Interval default Interval[@2020-01-01T00:00:00.0-06:00, @2021-01-01T00:00:00.0-06:00) context Patient define \"Initial Population\": CrossPackageTarget.\"Has Initial Population\" define \"Denominator\": CrossPackageTarget.\"Has Denominator\" define \"Numerator\": CrossPackageTarget.\"Has Numerator\" "; + include FHIRHelpers version '5.0.1' called FHIRHelpers - private static final String EXPECTED_CQL_CROSS_TARGET = - "library CrossPackageTarget version '0.5' using FHIR version '5.0.1' define \"Has Initial Population\": true define \"Has Denominator\": true define \"Has Numerator\": true "; + parameter "Measurement Period" Interval + default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) + + context Patient + + define "Numerator": + exists ("Encounter Triaged") + + define "Encounter Triaged": + [Encounter] E + where E.status = 'triaged' + """; + + private static final String EXPECTED_CQL_CROSS_SOURCE = """ + """; + + private static final String EXPECTED_CQL_CROSS_TARGET = """ + """; @Override protected FhirVersionEnum getExpectedFhirVersion() { return FhirVersionEnum.R5; } - private static Stream simplePackagesParams() { - return Stream.of( - Arguments.of(SIMPLE_ALPHA_TGZ, MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA, EXPECTED_CQL_ALPHA), - Arguments.of(SIMPLE_BRAVO_TGZ, MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO, EXPECTED_CQL_BRAVO)); + @Test + void simpleAlpha() { + simpleCommon(Path.of(SIMPLE_ALPHA_TGZ), MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA, EXPECTED_CQL_ALPHA); } - @ParameterizedTest - @MethodSource("simplePackagesParams") - void simple(Path tgzPath, String measureUrl, String expectedLibraryUrl, String expectedCql) { - simpleCommon(tgzPath, measureUrl, expectedLibraryUrl, expectedCql); + @Test + void simpleBravo() { + simpleCommon(Path.of(SIMPLE_BRAVO_TGZ), MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO, EXPECTED_CQL_BRAVO); } @Test diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/SimpleAlpha.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/SimpleAlpha.tgz deleted file mode 100644 index 3c28e4a70249726ddf8836843908616fb15a04df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 724 zcmV;_0xSI=iwFQ4@||b^1MOB@Z<{a_?sI+x;(1LVT%z_3(v{LBo7yChF!jM02b>UK zV4AS1|9$4FR7=`U>Sa~)NyyRhcYJIgpD!l@a0y)0IeJBK9M_X1rWoaN>Y>#00a&3uuD2*oa2VvXT)SIa)7WQES&xwDw9`Qwkh`^E zxT;@ZXU9rDwqp=ybfuq#p)l@PVo>rPEKxb!wC-E)eb@gZ>iVy)?mgF5ETh zusvO$iJ%SXz+Zml-OwCBKt6A4K8CCF42{$=F!{Bu8LEAuhO?34I@9lwy@Kxb(z~2s zwQEf;e*^O*)%%biER=LOg06m{deom-(`z4M1&>X`gMyJ+Chyz9l{?XljQT8ftX|py zzpKZR*DBsY0VvR-D(u~z?Y9vm7i*rtt;X=uVBW_)M#?^Xw7M5RMTo1-d_s2q6 z5Bh4!!;ka6(euo@C#j*x&u08k`}D@M5-fgqM+1rAC; zRN4xJiogLVwxN8)NpHES8fijN%(mjE3t|%|PO+Qq;xXE6_J?_@b8N>Cf~e|$H8$r- z7`*lx>^@{se7gT4P&gC zTQORN{HHv#B5~q{L7oInv5cj$&!6xvrO`>NMb;>F|% ztV1bv!#H0xZ%Y}Qxn4Xex0A5UcNSw&sR^2pXWwrMl1(BK*d)Ln$w`}%wid4lE&nCRJ26PJMwB?@SOhJlK9*ICBOjzg7!iG?}ADp zo}FvrUh@mhu|!OgFpV)8XA$8!#;G2ZI$vAIj?xF-A=S|*-J_bgwPPmVV-^u-Th*7M zemuo_$$KzGRdB;pzVEyK7g6iKy1ISnAF`Lehwp#6{^f)I-vgT+GG~IV9*0_hPufGF z<`--6FFix9qwMZ!n*X2k2vcvhH6I)Xlmv9^3!AMw+)vkMqGU~5_=jIrZ)rB7D9>Ac z-^0sZMn>8g8SvKXPdeK_b=RlYj6do7)EtYB-q9AO zVY;@l2KuR=+N{Q2_7VfA*|4jF^Rc?lKa;L{`)yu+*!pzXy_SbvHPKHC9(;xuv32H_ zAOUmg0==iXcHb}dsP`{@bLpq%NcBdB6tTE#b+ikstwF2b1I3x=p<|y7Ame-~?e(s4 Vu-F3*IN*Q}hF`sCmyQ4w0025IakT&d diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/WithDerivedLibrary.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/WithDerivedLibrary.tgz deleted file mode 100644 index 207d268f9aae1ec2b028e290a3d8c1ca355cab7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1317 zcmV+=1={)_iwFQs2%-8RGtlF4_Gx1NgzzPt{_{c<{>08v}C(k z#Axx~@98Wk4(fQvWo}(h6-2tv*5Bzq{l(BQmxg0Ezr3MP6eaR}J%0+tlVZ5ySxTMe zM3ELKR$$pWMRP1ef2s3tnN%fdoEQ=*Nq9S3P-+UHql6LnB6_e(YJ8=n2NhbudZQQ%`Gs=-vFY2*?I z#3D(;gJyFD#-vqPlWw{D{LpIzq3xSi+z2A4+NjOPBbAmA>HA*oMZQZ%yL{)a>-8UH zjMxk!twPDaWXmqlA4%2ogfZ-5OlOcT(ty*hgYtCr$Hjw}C9% zPtXOjqcRj>Ux#oisy+Qkur;p9{uZ{Bq_Az8mPkhGC?waBI8yG)D#>%as;;i?&3DY_XC`DjdTrtk{;@BK?6d z`l)9}kAZl+nS474w_~g~y#c<~dw2c6y8p{Q_YwC$T>JFq{ufy;zyF2L`~O|!cE|k= zyXV1``W!vTukre8-L?OYQ?S)2R4PehJZP8+SXJX@;3sfqa$bN3Zp%BpkZQU@pgL%! z^+wGy5_~j?dCx_rr)Kt0o*L6ZFzAglW2(D)_d1;U{e|f-9m^YMCgWbsk>npK+;u;b z9BXThNh$S+;qlV(|T{@{HkBGr)IMW?OLF( zDg9%8qTX23zB`|cQ%jN0AWk@E&XCr1Q>hN4*PA;X@p&}Q0=xM#bhLSU5+Bgpm z=Qf)TLVdzhnj$ati4ORT9Bt{QfNMCLtejD=y|O0PkSkuZCg40lw~N;VSS-&swcyaO zr{3T-K4J^l4qV-HQ++5Qz&4#TG{jS>IjfyUq)ZmoA1(!w-C98ZUgID~wY-KUto3ILpHeE z=G{5p4(_qVxiPx+Kj`E%tTx>Z$802}}Sx)Ybw diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/cross-package-source.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/cross-package-source.tgz deleted file mode 100644 index edbc0155a87a2979b20d259105ed48ecc1a65b13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1294 zcmV+p1@ZbHiwFS3nCfQ$1MOE?Q`<-o&MW*1m!FZ)Y1ygT2T8UZd9gW+Qun48Wr!2v63Zl_d6I{W*>o*$*w|2c7X*`NQk~ z?_mq1joq{?_GOW{?T@W?-45-eN{t+{_-$`A`p693=Pj;XoT4})Mhwk2t2qwHil1f4 z$Kzw0?569Eq^pGJC<*B}aUDys%YqN^K6i1w_1$0=De5ds?DCP<$OG~J$)GDOYW9k4QHOk zD}fbGHg+Md%Q*Z#4zpa0qSfL&Kj%l+Ha|IE&L?;|cLHOm6-2LpS_O6l@i@U7ZRLCd zzL%VqT0>)_4QqseNnZJos^u;xbY zsLDCy3_j11J_vGROXGr#R@Ut2k;c{W5uxDgN7m&-!x3o+iujOK_ zGB@KX{2Avr<9=NIslv_4rOchK<(m^#sqCiD+xY}hd^>$E-sSQTx4`r*{tR( zz329L`otwi(3{vQ6Oem(X~%xupLWv40<%S6Lm} z%rig6ByUo?YVFEL*r>8h_e9?oX_7B|{!$t_E_ignDRPNX{R;F0i6+}=EYNn0OF;G; zcW`44zmMkcC(ZpIyJnhW_gBn+9Nhf7`_J>${KviD|DPd`&3`TE{JJ0BXaM9M41h0U zfI6{f=D(nxUCH%7_(8Mfe`IK_2f;7RoCz#}BClkSg9Df!o$A}?^7}zMEz-;l@6ETF zi$fH}LF^SYVH*-sjU9Jd8XkxB6>R(-m0rMYiEM)7B*qzmuu?cEO#+h~vwe1_1Jw@R z?*}_DYI``&lYK^Y5z9r2ExojpILQMus|wR3UfHoyw=Z}5U5e@S1WHi4U8<{T14`i3 zK}TW-g8?r-%(2}4EfBAO;$C`3wW|gX6rl{6!DA-(kkKov*4dp^X;fl#P@+V9Vgv~x z^X?3R8F@KW|Nbul8o@gwLs!Vfb*Q#`o50O!`ud4rJ5rOQ2iP*Q%J*1SrZ~B;QbJ!A zC}p5%lqiUr*6nxtUAFfNv4wNS;KIye{1~YHd+N7C^Xn*&z{mv$9d*cM0wsq9Fia^?E&bT29;^!8kqWE9Fqic zo8!=iiSIr;7piTPscO4v8~=w`I=9$9C%?oHZJ>i2wO5oZ%Pmb4@>MTZSy79%lmOIL zOVNM|R7H@1+5~z{&|Y&>mC}Sl!nWe48*F2Tq{z*V#j&u}>O`IL9EbHUQC#-_##<6d zvbQ}FCCSIaIEKW*R9Yud_^fxZ`_NVO(f$LO4aax=SCnQ`t_kuBo$;jp^ZtLW3rr%x zNt6M!T_I+*kY1+QOJX2Nvo_W@wvsNG`?sw5YLv% ziIiwDePJ9(QH(=mCsIV5YET=GzlwE=rU|bL5%spXvZE-ZIHb(+IX4tXuT;6K8=&}} zHqaNp%szzKkV5WX8oq~us|=62mndlO5a_ydg}Mu6I+|mN9#osQyPB`eP zpr3Ud+xieM1LO=hZhzu+m4(&c!C5;sdkEaDJLzDA9Mc&3i@;A8qmDN+u6E{oKZVvX zp3m-_iP65ZXZNmc@VcgMUJo;SHaU_r^p`=K!kNZm+nKqJ9Q(QtnBQ3#(}3B1v)4E0 z*1#EuQ_A-7W86D2Kl(m@-q!c^KRv8xy|^m=S6|(;VShxw;J>VDYQ_Ji&i(~5&~?x{ z^Z$25Y<++@W4KzF5DcTxLy&R&DHt!3bNoMv{+9%QGww3FVX|c=X0D4iAWC?JX-R40yW)*E#)WbZe`~|vxOXBGgQ6&_S{6Y?idq%%A U{?0nDd>2_Ymf zT02`KC=e891PziYADW~|qMgg;BH*n5eNS!(a#?2AVQ0tw3M%P7eK~zS-#KB6Z1Lk~ zG-+X^VQ=rdfJ?CM$#LcK^LvzE;`mZ&kD2W40HGfs54zubo8YUA6_^(OP^yX*;gE;< zXRGkSm2-O(KR;Zg-Xk3szh@uOp{|Nz@~k9Jju#fs{KEM!b7gLi5qEk1NqCPntb3tq zOjnhIVx!T|xBl!8asLa;_dieX|DD|b?^oXc)l#KI4Zs_^|If{#hrDT_XId`7$HX-d zd*?g$Yn}g!AgsIpc}{qF|5rsG?tgBV=l_k~|6g+qAn#${2|X2GOoi~bbg`S(=2rxzdfbHP#rtKcCeuJqQ+T~ zY_$SB35x0;z#N&a3~ z%fU_8jdX-D+$(*>9a`6*KM4`!gO2)j^Kh*7Cx+G}4?|O`M0!f~8o^ESsI@x!S-ssD zS{DymJT6J^T2b8#N9kXhQD4!RV4jJzq1-3k(WOQ(CvR)g>GhdZhrU{x18D;3P%U>2 zttCH7C!F5qBVEZOg7tv4{P4U#lf8MHd!iQd=LY5fl>aaDe~~LyDwO}fA^zXN$PYdI zpYVUNEH3l^3g!PhV}U+e|2r2=?8!6VZ)186vw7hJ~;35wD+ay-$9%+2u%wdYk|-P)#k+)kf=DS2AAB%TQqW1`K=LXfS8V7cb~--4-uS8R?<>}?n;oNyOTW_CGk zJIv+bE#}<*FP$fre*dN4e`oyvy;v;?EH82uQKa<$E%E<(-VZnFtDpZ3{GY2V@qe*e z7AXJU1yU-_WHfo6KF{rmTrTv11u>15)MZj;&+H2zkKAu zS`@hGcg1l>@sH&STm_y7OeE#kNZ;jhaUgvHU4r3#bHI-Zz*;4{DE zPi@r+BPEyyP%YoUTK?U0{T0%v{+C()Epr;TH#V?`nNv3Tront*_D5-j{axI^H2z29 zf6MVdzEY|jQUdUX?0@=vwV2 zdfy%fJc+*W&$>flU^rg+*9FtNWFF^fVlQB$lZ zu&U$56EfGp6a5-WK|w)5K|w)5K|w)5K|w)5K|w)5K|w)5K|w)5K|w)5@uT4{VQxd< H08jt`2+$Cl literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/with-two-layers-derived-libraries.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/with-two-layers-derived-libraries.tgz deleted file mode 100644 index 78cfe21087989f24573388317e19c811e537f137..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1602 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 IHvpgj0M9;LaR2}S literal 0 HcmV?d00001 From b9a2fa5057f285d711d591b3958d2308f7d27422 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 29 Aug 2025 14:47:22 -0400 Subject: [PATCH 04/10] More R5 done. --- .../npm/r5/NpmResourceHolderR5Test.java | 62 ++++++++++++++++-- .../utility/npm/r5/crosspackagesource.tgz | Bin 0 -> 1616 bytes .../utility/npm/r5/crosspackagetarget.tgz | Bin 0 -> 1361 bytes .../cqf/fhir/utility/npm/r5/simplebravo.tgz | Bin 0 -> 1576 bytes .../utility/npm/r5/withderivedlibrary.tgz | Bin 0 -> 1817 bytes 5 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/crosspackagesource.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/crosspackagetarget.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/simplebravo.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/withderivedlibrary.tgz diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java index 5c5c7d2a10..84b4b114d2 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java @@ -14,9 +14,36 @@ class NpmResourceHolderR5Test extends BaseNpmResourceInfoForCqlTest { """; private static final String EXPECTED_CQL_BRAVO = """ """; - private static final String EXPECTED_CQL_WITH_DERIVED = """ + private static final String EXPECTED_CQL_WITH_DERIVED = + """ + library opencds.withderivedlibrary WithDerivedLibrary version '0.4' + + using FHIR version '5.0.1' + + include FHIRHelpers version '5.0.1' called FHIRHelpers + include DerivedLibrary version '0.4' + + parameter "Measurement Period" Interval + default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + exists (DerivedLibrary."Encounter Finished") """; - private static final String EXPECTED_CQL_DERIVED = """ + private static final String EXPECTED_CQL_DERIVED = + """ + library opencds.withderivedlibrary.DerivedLibrary version '0.4' + + using FHIR version '5.0.1' + + include FHIRHelpers version '5.0.1' called FHIRHelpers + + context Patient + + define "Encounter Finished": + [Encounter] E + where E.status = 'finished' """; private static final String EXPECTED_CQL_DERIVED_TWO_LAYERS = @@ -122,10 +149,37 @@ class NpmResourceHolderR5Test extends BaseNpmResourceInfoForCqlTest { where E.status = 'triaged' """; - private static final String EXPECTED_CQL_CROSS_SOURCE = """ + private static final String EXPECTED_CQL_CROSS_SOURCE = + """ + library opencds.crosspackagesource.CrossPackageSource version '0.2' + + using FHIR version '5.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + include opencds.crosspackagetarget.CrossPackageTarget version '0.3' called CrossPackageTarget + + parameter "Measurement Period" Interval + default Interval[@2020-01-01T00:00:00.0-06:00, @2021-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + exists (CrossPackageTarget."Encounter Finished") """; - private static final String EXPECTED_CQL_CROSS_TARGET = """ + private static final String EXPECTED_CQL_CROSS_TARGET = + """ + library opencds.crosspackagetarget.CrossPackageTarget version '0.3' + + using FHIR version '5.0.1' + + include FHIRHelpers version '5.0.1' called FHIRHelpers + + context Patient + + define "Encounter Finished": + [Encounter] E + where E.status = 'finished' """; @Override diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/crosspackagesource.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/crosspackagesource.tgz new file mode 100644 index 0000000000000000000000000000000000000000..595519d9deb90a1b463f329185e691e1cc338cc7 GIT binary patch literal 1616 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 T2m}IwepCGegE5et08jt`vgOCt literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/simplebravo.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/simplebravo.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ca650e7fb5ab65ee153813fb9d6fb3759d2da96a GIT binary patch literal 1576 zcmV+@2G{u?iwFpL^s#6H|8r?=aBO8_a$$CFE_7#l0PR^>Q`<-o=GnhO|i5vEMSmrrFa-gV_7gdR)-I={P%5LVBsTVmw2;bzJii9-P6-^_SYk}!d5<> zqv;Vw2KKkU3%C@ko*Y-LKK+i%c~0Q@EoQp40YpKFd}w~_Z9=Fsc4*o717DYF;-0W4 z@T|BiaW$c|Me*~)L;4*uapilq5pU|cB&E+<`s4($xaSM|zsgm)Ek@ep{wL8LHn8bO z`e?RH57dUkpKtzIdj8Y%f8qQWckA^0e^clGiKY3-pY3!l+atK)qp{202Cnm3`@bfN ztImIct3HbbuolJo7Q=0F|G&}m|4WGhlzkkyk+0*cnTN~!OgUQ)<@c%BGUCFywUG

3Q;WA+QicB)(m$WW4*0J1L5-i#5wb{ej`wOtw3*&MkgeDzwXQieRbS4Jzf_` zq|O@c?jh;QM=H_dI)Z3+?ysZMra0|P++);}ZnY!jPVJ_8-L?Yul$kzj2x`A`k9vEN z+|;>~+k@z|tD6I%6=-ecPCiuSuF{d!-gFY{L3@)yzcbQ$3a9pEQ@-QLpnt~ewi2p6 ziIZCmqT6JwHf6I5?@&h=^d`HP?G~phm!8(U{TpF{}C!AJADy*Dv>(~;38W6hFlv7d6YL2#Sg8%L++akJCX?6a|v z^h*Z)=}5CB0)6$0HsiV5aiZmO>^42{Dd)j3@7gZ(pYu4{JT3I_)1n)3E zEtqo-eC74o!JgbSCV73B#}7|yGhLTAr3V@ve|}K@Px=3n`yZiRrTqUb@&8kdg2>1J z3ICU>(gOdN1j_$6$p2Ro{O5^5woYEa^p~dwFX#VROUw9wPN(M?@;^)|{)&A|2~TDm zxHNV$&sP%gVmI>qU^ue;N)!MUAKIz+L8TkQk_zGHCp@$qD+I%de8+RWh@kkrfdVUd z&fUj|M7Wt{5t{1~jGP6Imghsd>!*3n>*PL#{$xGb$s1Xd?C9>u^~0BCIi~MM-nt=v z8lQzeEbqtMLQV!6k-0Hm;`Y4j%dM>&$Cvr@uX4_SN z8eq+o zf38*)p6!1r{#;D?{|1mzX(p%1DSbx78zFXXO_WMe04#`U9m$tW#_XAGi3Rq&T`F0Q zP9g&*(j!bfkUuGji4vnDLa;HfQYv}Khx`G8{bOEXL;PmWA^N*UnUNh30T2G|6AJMa z98$kSU^Va%MI?L_e*P%HTH4`w_`Bk`{p643cDNcm-!rjPS|NQ`DkT@`Q)m(bKQvt` z88J)8OIg8w64vq53c`T-rF`hZRDDqx*Z%xie8U_j)+QtDW9E=e zzi2QYm~AiB-`>#aOXEL{|BLs3yjZQ(Y5adf@jrXM+MVCG#D7t$J&*rYp-SWb2Jip! zd%PkI(7t}3rz6L8EC}u|yVd*lYrs$O7yifQP!x__Kl!i@^Ocvz_f$&iUQoOgOc+Y> ze$T*Q-GBxx929TN;wzZg6zhwu?)u4q%o%u~UqmS=C@3f>C@3f>C@3f>C@3f>C@3f> aC@3f>C@3f>C@3g?H2e+mC7hlBPyhf@9x(<0 literal 0 HcmV?d00001 diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/withderivedlibrary.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/withderivedlibrary.tgz new file mode 100644 index 0000000000000000000000000000000000000000..201a3c71b18a67f559ac41efef3f91e181dfb13b GIT binary patch literal 1817 zcmV+!2j=)6iwFqD^s#6H|95G0Xk=w_X?A5~Y-wV0VRCscbZ2@1?VDS3;x-h3^USaC z@Urd9#@Gf!$h4gT;bIcfT}Xg+%&*b z^s88si-odOE}!t_l6)dda^{H14==8!Pc#$d-m(ljsY|jPKa25Gkk;mWYX0Yiyl}+H zhn)W;oFD_4UZ`7(ZF-+Cug!nZhWRfXa{j;5^`FEY&N(i!b;IXpIIxU(EG(n=^w8k$ zk>_K_<~|5~;X@`9`q(zPMoZ~F#mKzCix7uxorDI8Vq1u~5WlX(=^7!(SXIenbjJ?R zJm5ZQ0Y-L^$rxyiZNz2kw(f)w=Wz{eW8XqX_B6wBU%rOla&`F4aWe~fh^zC^YeDF9 zzj7bO%LE_Tvc~p*w*Nol|CJ@N%qGe^+W(iU`|%nF-1(0E*89IGi)-^=5~RG$_Wwi3 zU(t4%^^os`o{p{-F3O&A*=7G{KgQA6pn>VJk;E-GbgBSEkc8UW6zxtEdW1$=LExU| za$A_gcBg!TB#vh$!!x%%yLgwUIU1fe4wB#?u1s9TC`5M*pcM%F@->XgU#VYlmJ2^X z(-k8DE~eOah+{5zi9dQw&vwmpMO`AHx~MjLb<$H>D$(srcO(_e_QQ49 zsTJn!nRBiUxAqYc`N zn_*i{^Jpc!1cs~GRSW7Bs6)`l6?`@5TY4VYZy-{ZG_Pqr2jz)^tL>2uI<{(!&gOMs zLeg)NK)oseM*=f8DRVZ&QR}nW8&D=~OE-i0=<}H}>)h3Y&Kdn{TzAeY#pNIHurRy4 zb7q~|%#8JvXY?k}R+snG4%DKyH3V${>{@Zo)n=cvDN$7Jl$y1L>kGS?8Bcx`=4Hb8pYi|R^IuUaGX8&0{NF*EAA0D2;s2sg z+Ti~Zd;fb#{(lbe*ZqU#aCtfDZ}$+sng5sN?&1Fgr&kI2RUC}JsogR5EQ2J zYD=GD#;KAqlVVVN*ix6d)4&qx$SGmm)Y~5JkF%$pRL8#&UkSfq#a1?yF{ZCEvv*CaG#zs z*qi=wXYB3Pn8#gR;$dhQtnKd>qS;L~D=fwis{||WnmmJ{j z#(@0?dAephHimutn~wAT^)cWXeYGzp$+a-B94~sK4xN~U(j%#UY^ Date: Fri, 29 Aug 2025 15:02:45 -0400 Subject: [PATCH 05/10] All NPM tests working. --- .../npm/BaseNpmResourceInfoForCqlTest.java | 25 +++-- .../npm/r4/NpmResourceHolderR4Test.java | 14 ++- .../npm/r5/NpmResourceHolderR5Test.java | 98 +++++++++++++----- .../cqf/fhir/utility/npm/r4/SimpleAlpha.tgz | Bin 1563 -> 1556 bytes .../cqf/fhir/utility/npm/r4/SimpleBravo.tgz | Bin 1566 -> 1576 bytes .../cqf/fhir/utility/npm/r5/simplealpha.tgz | Bin 1557 -> 1546 bytes 6 files changed, 100 insertions(+), 37 deletions(-) diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java index 0af2872ea2..bfbbf67f9f 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java @@ -95,10 +95,12 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected static final String MEASURE_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION = MEASURE_URL_CROSS_PACKAGE_SOURCE + PIPE + VERSION_0_2; - protected static final String LIBRARY_URL_ALPHA = + protected static final String LIBRARY_URL_ALPHA_NO_VERSION = SIMPLE_ALPHA_NAMESPACE_URL + SLASH_LIBRARY_SLASH + SIMPLE_ALPHA_MIXED; - protected static final String LIBRARY_URL_BRAVO = + protected static final String LIBRARY_URL_ALPHA_WITH_VERSION = LIBRARY_URL_ALPHA_NO_VERSION + PIPE + VERSION_0_1; + protected static final String LIBRARY_URL_BRAVO_NO_VERSION = SIMPLE_BRAVO_NAMESPACE_URL + SLASH_LIBRARY_SLASH + SIMPLE_BRAVO_MIXED; + protected static final String LIBRARY_URL_BRAVO_WITH_VERSION = LIBRARY_URL_BRAVO_NO_VERSION + PIPE + VERSION_0_1; protected static final String LIBRARY_URL_WITH_DERIVED_LIBRARY_NO_VERSION = DERIVED_URL + SLASH_LIBRARY_SLASH + WITH_DERIVED_LIBRARY_MIXED; @@ -129,15 +131,20 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected abstract FhirVersionEnum getExpectedFhirVersion(); - protected void simpleCommon(Path tgzPath, String measureUrl, String expectedLibraryUrl, String expectedCql) { + protected void simpleCommon( + Path tgzPath, + String measureUrl, + String expectedLibraryUrlFromMeasure, + String expectedLibraryUrlWithinLibrary, + String expectedCql) { final NpmPackageLoaderInMemory loader = setup(tgzPath); final NpmResourceHolder npmResourceHolder = loader.loadNpmResources(new org.hl7.fhir.r5.model.CanonicalType(measureUrl)); - verifyMeasure(measureUrl, expectedLibraryUrl, npmResourceHolder); + verifyMeasure(measureUrl, expectedLibraryUrlFromMeasure, npmResourceHolder); verifyLibrary( - expectedLibraryUrl, + expectedLibraryUrlWithinLibrary, expectedCql, npmResourceHolder.getOptMainLibrary().orElse(null)); } @@ -151,15 +158,15 @@ protected void multiplePackages(String expectedCqlAlpha, String expectedCqlBravo final NpmResourceHolder resourceInfoBravo = loader.loadNpmResources(new org.hl7.fhir.r5.model.CanonicalType(MEASURE_URL_BRAVO)); - verifyMeasure(MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA, resourceInfoAlpha); + verifyMeasure(MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA_WITH_VERSION, resourceInfoAlpha); verifyLibrary( - LIBRARY_URL_ALPHA, + LIBRARY_URL_ALPHA_NO_VERSION, expectedCqlAlpha, resourceInfoAlpha.getOptMainLibrary().orElse(null)); - verifyMeasure(MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO, resourceInfoBravo); + verifyMeasure(MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO_WITH_VERSION, resourceInfoBravo); verifyLibrary( - LIBRARY_URL_BRAVO, + LIBRARY_URL_BRAVO_NO_VERSION, expectedCqlBravo, resourceInfoBravo.getOptMainLibrary().orElse(null)); } diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java index 4cdfe391f1..565515f42c 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java @@ -228,12 +228,22 @@ protected FhirVersionEnum getExpectedFhirVersion() { @Test void simpleAlpha() { - simpleCommon(Path.of(SIMPLE_ALPHA_TGZ), MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA, EXPECTED_CQL_ALPHA); + simpleCommon( + Path.of(SIMPLE_ALPHA_TGZ), + MEASURE_URL_ALPHA, + LIBRARY_URL_ALPHA_WITH_VERSION, + LIBRARY_URL_ALPHA_NO_VERSION, + EXPECTED_CQL_ALPHA); } @Test void simpleBravo() { - simpleCommon(Path.of(SIMPLE_BRAVO_TGZ), MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO, EXPECTED_CQL_BRAVO); + simpleCommon( + Path.of(SIMPLE_BRAVO_TGZ), + MEASURE_URL_BRAVO, + LIBRARY_URL_BRAVO_WITH_VERSION, + LIBRARY_URL_BRAVO_NO_VERSION, + EXPECTED_CQL_BRAVO); } @Test diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java index 84b4b114d2..f742b35e83 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java @@ -10,37 +10,73 @@ class NpmResourceHolderR5Test extends BaseNpmResourceInfoForCqlTest { protected FhirVersionEnum fhirVersion = FhirVersionEnum.R5; - private static final String EXPECTED_CQL_ALPHA = """ + private static final String EXPECTED_CQL_ALPHA = + """ + library opencds.simplealpha.SimpleAlpha + + using FHIR version '5.0.1' + + include FHIRHelpers version '5.0.1' called FHIRHelpers + + parameter "Measurement Period" Interval + default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + exists ("Encounter Finished") + + define "Encounter Finished": + [Encounter] E + where E.status = 'finished' """; - private static final String EXPECTED_CQL_BRAVO = """ + private static final String EXPECTED_CQL_BRAVO = + """ + library opencds.simplealpha.SimpleBravo + + using FHIR version '5.0.1' + + include FHIRHelpers version '4.0.1' called FHIRHelpers + + parameter "Measurement Period" Interval + default Interval[@2024-01-01T00:00:00.0-06:00, @2025-01-01T00:00:00.0-06:00) + + context Patient + + define "Initial Population": + exists ("Encounter Planned") + + define "Encounter Planned": + [Encounter] E + where E.status = 'planned' """; private static final String EXPECTED_CQL_WITH_DERIVED = - """ + """ library opencds.withderivedlibrary WithDerivedLibrary version '0.4' - + using FHIR version '5.0.1' - + include FHIRHelpers version '5.0.1' called FHIRHelpers include DerivedLibrary version '0.4' - + parameter "Measurement Period" Interval default Interval[@2021-01-01T00:00:00.0-06:00, @2022-01-01T00:00:00.0-06:00) - + context Patient - + define "Initial Population": exists (DerivedLibrary."Encounter Finished") """; private static final String EXPECTED_CQL_DERIVED = - """ + """ library opencds.withderivedlibrary.DerivedLibrary version '0.4' - + using FHIR version '5.0.1' - + include FHIRHelpers version '5.0.1' called FHIRHelpers - + context Patient - + define "Encounter Finished": [Encounter] E where E.status = 'finished' @@ -150,33 +186,33 @@ class NpmResourceHolderR5Test extends BaseNpmResourceInfoForCqlTest { """; private static final String EXPECTED_CQL_CROSS_SOURCE = - """ + """ library opencds.crosspackagesource.CrossPackageSource version '0.2' - + using FHIR version '5.0.1' - + include FHIRHelpers version '4.0.1' called FHIRHelpers include opencds.crosspackagetarget.CrossPackageTarget version '0.3' called CrossPackageTarget - + parameter "Measurement Period" Interval default Interval[@2020-01-01T00:00:00.0-06:00, @2021-01-01T00:00:00.0-06:00) - + context Patient - + define "Initial Population": exists (CrossPackageTarget."Encounter Finished") """; private static final String EXPECTED_CQL_CROSS_TARGET = - """ + """ library opencds.crosspackagetarget.CrossPackageTarget version '0.3' - + using FHIR version '5.0.1' - + include FHIRHelpers version '5.0.1' called FHIRHelpers - + context Patient - + define "Encounter Finished": [Encounter] E where E.status = 'finished' @@ -189,12 +225,22 @@ protected FhirVersionEnum getExpectedFhirVersion() { @Test void simpleAlpha() { - simpleCommon(Path.of(SIMPLE_ALPHA_TGZ), MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA, EXPECTED_CQL_ALPHA); + simpleCommon( + Path.of(SIMPLE_ALPHA_TGZ), + MEASURE_URL_ALPHA, + LIBRARY_URL_ALPHA_WITH_VERSION, + LIBRARY_URL_ALPHA_NO_VERSION, + EXPECTED_CQL_ALPHA); } @Test void simpleBravo() { - simpleCommon(Path.of(SIMPLE_BRAVO_TGZ), MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO, EXPECTED_CQL_BRAVO); + simpleCommon( + Path.of(SIMPLE_BRAVO_TGZ), + MEASURE_URL_BRAVO, + LIBRARY_URL_BRAVO_WITH_VERSION, + LIBRARY_URL_BRAVO_NO_VERSION, + EXPECTED_CQL_BRAVO); } @Test diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleAlpha.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleAlpha.tgz index 22074561c50e2e66174317c2a262d8f4b885d102..260b0639f0a2fba221d8dab5fb02576838a5156f 100644 GIT binary patch literal 1556 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 N{sv<1?B)Pa002=~8xH^g diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleBravo.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleBravo.tgz index ec8cf55f26a18aa8a054caa1a44743e23c118077..c0850409fde137246df22f3880ca305931c5b490 100644 GIT binary patch literal 1576 zcmV+@2G{u?iwFqX^|5FI|8r?=aBO8_a$$CFE_7#l0PR^>Q{zSu=GnhOui?!3QE>=PfyR;Uys-lTl#c? zCdU|=*x&jt;8Lu5a-1kW|BlOfPT=`1X0o*bL_vsrXnyN`LZ~oyXxaE9Uy;jFRTL@` z%L!F+uUgom`1#=>;~oug>3g;jZ|aIHr_XZw|LOU^c>eQciJt%O>HI&nbRYTC-L_?W1lN2ta@o7Ub>3?KmnCV{ z`7dzdi&y|_QL1b)+$Q(`J3aru6c|9!$AKI92ELwpxVX<0XUn0ulZq`fE}UB%nP7zd zz;c~f%CUS_>L44hs1W%imJGwt+b@-ZL_tqho8#H6>tV+*1J?Bi*@`SJF#>ZDMnP=H z^ie;|io?|Tv`a~e;hL5+fU?g|Wih_w-cpm(AjI~ZYa#Y+%W;W2n6g7sNnEPCCT=H+ zB{JJDNt-*1_Mqit2%x@Y?9Q}{IlafHx7*~`x9?|b6)2?&Q!A53h^W?8Gn)& zjucH4qFAmsiDlT0DQ?$BMpKP?!qvm6bMEQgTA+0rfzccePs!~4rZw$#wNZWG@rFPm zZCY!#k4RfN)`$_;5u`!u;W9d_OOw{vJwYA$PCr)fwRWo4ZmM8Uof^}cpmkdhs8fxU zy1|{^9YkksW6%>Cf!3>}spI#pbf7~66PwTZ*uf1OHx*%GBlfHq+3ck_EV|X2yT)G^Z0CVQg1bM`+Q_3 z{Zc@GGSqFEKwrJQ&3Nvre9|wgcBwZTk#56S-nl1WPrr)iLR4eFrrma}F>6Oe@DB6S zfH~*DSKgQ&RF%3p&g#QFetce=>AJivJksd+^W(?h|I#A=7bySV2>;((%Kyuiia-;9 zcf|kCFbX0c|0n!k7Uc#0FAJ3aZ;=15B=|2AgV{QH1=C-i8oZwW&sti>|1&y0&yfFN zO7U0hTS|Cl#(_&?XXg1z0-o-)ktaf|uNVgh+(zvn)b)U4oIbz|pdN$Z-8M&v~2NC)b}@Pj<6L)+9TIJ9Pc< zby?29cO!4z5I>L4LLZj*V{RcQ1C7Ys7%y>q-u30y){W!q{P|ZoW;XI)?Zwi`c=Z_M zQ${QB2#U8>QArIj7taRP)_+_nylD_%= zU&H^ovM9XR|8o4fnDYM(U`C~xj3%e_84+)Y*ts=PC`19UAf|DwTro3d&umL9u;=YU z!Ey`|nK+RiW8#7Qr4nNxLa;fnQYd)Hhx`G8{Zn3HL;PkgAo{yzkx?8F0T2G|5QX>} z4yoTEu$s7!A`(6cKYtQnE$wnV{9SY0e)7k1yIdKbJ4`HvywLd=<-!eyuweb-9m^ot8 zFB;58X4^~kw>Nb9((nKD`~SlIA20H}OvC?sivP3co89?+OZ=DQ^2_)y3L=gF8@&I^ z?(y<8K>zxEo`D?Ku^_m=?pE*HuK^F^FZ@re)3@*<|`|W@2S+(y&!)nn2?YB z{ho=zx(N+fILP0a#aA$~Db|-*!}XH^nKST6zlc&$P*6}%P*6}%P*6}%P*6}%P*6}% aP*6}%P*6}%P*70(X!sjMF}s)mPyhh`1ulsI literal 1566 zcmV+(2I2W1iwFpN+p%Z@|8r?=aBO8_a$$CFE_7#l0PR^>Q{zSu_OpM5$}ePV7g>j8 zFqKU$HrU7<0UKmnTRe=Uu`C!J(cyzF{(D*%_zs(}Bw6MwC|T1zJw0cCJz~pj`O77m zo?v8PzwkrArP%c3II;5lJ1*xrffowQwD1Oqf)M%8eBpgU*k|m}vhinrPpVe-tCAqH zHBk_x+J3P>@zcXY`aLpn`A4=9Z|XfsN}tvA$q7q)eqsNMoX8az=`Hs^iSDt1O+V5{ zvvqo)HXQzZ^Uu=rpPv6K=RaTD6D4~7zoYa2%+h@1&vv_(?Gaq}(b#400@r!1{a>wA zHl6<5V)pe5_g@od)hu%jCR>-uKCCd+G#z-)w3 z5Zf_)Gz|0NFm*reR8pe5hUJ)0_VuYO#2`C5yY;c&Qlf#-c{p<}Jgr|3)Lt{tTcgn#$=+|;vq4`SH%yP$ z1rn*VdaHX(y7Gxi^tg^tG20JU(RrgXZBN`&)RXSC6Xjm*rh46$0``=dKC26Azx{xE zwMcI0+}Yh>bl%m?fzS-JmU1s2t8!Oq%W7{riS?kp$)MjJX+4Eg`?4wD^JLJ!;B{LG z)tTEDwDJ8L*e(k!^v5rp6YeC>z!V?rvsrD87-v(?OYA| z9WV9~`f#uIR8O;Sz<&}UC&>hk5y3mm zPZQ>x17CT4c36`e#w4!~^Z5CBZKmt;w)jZnfgg{(p{95c&9D z;s27zukim$mGb{LAR7)ZHS-8 zXQdD8`!TnOlYvHLZj9HsJ@5K@Yum>0W&Zr5>@pwuzwE`@$$0S?EGCOq;1LvWtCE}= zU@o5xY_0#eQF^;p^0;YAdAjga#YRzZ9yOsg@ur;)>zooCR!#&uU`N8zk zZmxE3vE=rDn>tbcPx=2m|F2YeRuuOHsY1X1z9;@a&->vtef9mnh5vI^QCQmlQvA7? z^8YtLMx~jYCa3fn5pRUpxiwKNMgcG(rgfw ziO~@u*qB!-7Cq!c{s000DX*|0hBKGI09>QQ$PS2r3;%kALVOK})Nc@64Ln2<37>>t zzX-6Fb~zsYt~u@?`D3|Vt_sgjOe~c)NIw*d$u;^EnuOpFO&5zs%+&E(W^j;%c09F$ zFkpTu9Xq-k#ZEAnz(*Wxls-IHUlqo+zdjY;GRKLv$q4(HIcC%E8O $4m8h-stqD z@t?;3)%!nAlz5TG|92Gsv*)Yb`F&6PuSm<^|0PinY5ae~`@j4iZ;=LQ8}IY@yi`Yy z>sS!nUv{ha{nvm;@%R0w=1>%lTtE4&4)c|l#&=ap>RzyTDVUIt#XCL&gLMNMuyC+= zYZhO_#HLuk!s@P{49J{;NBTXKf`WpAf`WpAf`WpAf`WpAf`WpAf`WpAf`WpAf`WpA Q;=hJJ0rg;YhX7Cj056+4mjD0& diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/simplealpha.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r5/simplealpha.tgz index c825db87ec684819c747c74517afe5cfa747798b..a31cb42b492f3c592f4334b3444661ad433b2789 100644 GIT binary patch literal 1546 zcmV+l2KD(LiwFoj__1gL|8r?=aBO8^Y;b5{E_7#l0PR^>bJ|D{_A|di#fvwUAcR0* zyEa<^Y(UsJG1vl>^1~nvlI+p3It)1L|Guru=3cVvu)Fbm1trb&9NlyE*CRI1=0ANw z(-uZ5b`O3CxEPzB99Jy8yodP(jxX>B%=BOn2s|IT(EY*N1b@VszG32zg^Exv9T$&| zN~~~HIV$pe=78d-hl7-Rq~ZLJ>?7RN6+wuf<@m|*OM8A{{};I;cfbhy-2WuF$12v` zK+$LG%0aR5_~)B{mY)Cg{9ir)%R-?@&;K`c{$Cg)7rC=s&oCW=tHjX}d*|8KYwiDX zskG_*=eg4I`Ck!uIRCkQ?*BJ>{(s9bfUJu>J8%_zJ#%pOgvln$A^RZ~8)}$2w>DD2 z19m;bwn8b#7Sdb`nRr8mz$KwX_kHIipZB5yC|YfnW3sk`Ek*TM+ttz)nP1Zg%!MC# zVLPge#(tXZ$I8bhMLCMC8kPomU!U_rd|ODxP0lqOs>ny7eAFa{Vm4;DS9?&JQZVGZj~CXZGaA%9x!>@VrmkO*Bz+U7>zY$gcoN97 zTC;aXdSXi^N?3<4Y3;|Gpi?hR+Y|d7^@Y1pOS+ePak*Yog8I@-nbmlC(0)Yy<3Ow{ zg^Rmt(CI1KkZ*XSrgSf!$zo4xi*kQD3Clr$li{GPkNOfP4@6D8FOcEjvY?ofFZTsb zY}ANik_WjiYCT9p9e&uK9Ce!wPL{gPsDJamZd5h7DVf88IgR6 z%YE4ynKz(62@vFij@nK0c%t^Fy4oZUBSS2QdW!WL!A{sE7QyLHR%B|7-aFaj`(}f8G-R?_lHwF8){ezfcrb`G1-6 z|2^{mjRb!=Fi6(P4orW2Xs|o~PkLI%|5G|W&yfFSNbyIgTTFJ6#(_&?E75!-0bevD zjSt4U;pPJmsQB27wfFKpAC{C4ucvryScVS`NAjFwI{`sqx`sT%TjuT$hy=KvL=hv~ z2AixEewM~VitWa6&g;ZJ3-%m+@J#VZ9x53ppBS zMCQhLjoS07uWxPJIPRv;KZ-Hak^f68)=tLGW3U)98lFQ?xV5rk+yL|CrGc&WpEgQw z*Gir?O^Iv3z!+%zv=C%z0eG(Y_%~;$5EUC^1G`&ng&pp|&P)%dUCmrIZ!zcge;GVc z{!jV;0{<@-*-D{UDRX7Y|KAY*pXdE>lD_)>-@^a7@(TYKDsUuF{=Ww#RGLX?@;rT> zTN9Z~-~kI_8ZEKQB+Q;U6j)%-hnb9FDI`#FByC~hfPA?SqaZ@CI$tD{agYn~133HV zxWWYg&3pmxZ>w2Gv_J$r_}5e9<7+siegns<;xP({|1A9ai3e|y(sp0t^^Go*3QtTj9f@uKN@^q|b z-@VjdBMs|+ea^pS&Z71vI(9L0#>U??n2*fiIH_>Bj{}(cf9n5N@Bf4XUlOSQe?$I1 zdA{16-}m_cl2Bgu|3$t?{r?{C|I&NBH8f!ReV&3W+cLnp?{=&A{nvn};TQhr?oi-8HFtE3HV-{Y)giWz-iB)Vj8j!gLp6J(5 w3JMAe3JMAe3JMAe3JMAe3JMAe3JMAe3JMAe3JMAeivJq^1l-a8vj9*40IlH}QUCw| literal 1557 zcmV+w2I~1AiwFqd@UdtD|8r?=aBO8^Y;b5{E_7#l0PR^@Q{qSz_L*Oi;>Dd>2_Ymf zT02`KC=e891PziYADW~|qMgg;BH*n5eNS!(a#?2AVQ0tw3M%P7eK~zS-#KB6Z1Lk~ zG-+X^VQ=rdfJ?CM$#LcK^LvzE;`mZ&kD2W40HGfs54zubo8YUA6_^(OP^yX*;gE;< zXRGkSm2-O(KR;Zg-Xk3szh@uOp{|Nz@~k9Jju#fs{KEM!b7gLi5qEk1NqCPntb3tq zOjnhIVx!T|xBl!8asLa;_dieX|DD|b?^oXc)l#KI4Zs_^|If{#hrDT_XId`7$HX-d zd*?g$Yn}g!AgsIpc}{qF|5rsG?tgBV=l_k~|6g+qAn#${2|X2GOoi~bbg`S(=2rxzdfbHP#rtKcCeuJqQ+T~ zY_$SB35x0;z#N&a3~ z%fU_8jdX-D+$(*>9a`6*KM4`!gO2)j^Kh*7Cx+G}4?|O`M0!f~8o^ESsI@x!S-ssD zS{DymJT6J^T2b8#N9kXhQD4!RV4jJzq1-3k(WOQ(CvR)g>GhdZhrU{x18D;3P%U>2 zttCH7C!F5qBVEZOg7tv4{P4U#lf8MHd!iQd=LY5fl>aaDe~~LyDwO}fA^zXN$PYdI zpYVUNEH3l^3g!PhV}U+e|2r2=?8!6VZ)186vw7hJ~;35wD+ay-$9%+2u%wdYk|-P)#k+)kf=DS2AAB%TQqW1`K=LXfS8V7cb~--4-uS8R?<>}?n;oNyOTW_CGk zJIv+bE#}<*FP$fre*dN4e`oyvy;v;?EH82uQKa<$E%E<(-VZnFtDpZ3{GY2V@qe*e z7AXJU1yU-_WHfo6KF{rmTrTv11u>15)MZj;&+H2zkKAu zS`@hGcg1l>@sH&STm_y7OeE#kNZ;jhaUgvHU4r3#bHI-Zz*;4{DE zPi@r+BPEyyP%YoUTK?U0{T0%v{+C()Epr;TH#V?`nNv3Tront*_D5-j{axI^H2z29 zf6MVdzEY|jQUdUX?0@=vwV2 zdfy%fJc+*W&$>flU^rg+*9FtNWFF^fVlQB$lZ zu&U$56EfGp6a5-WK|w)5K|w)5K|w)5K|w)5K|w)5K|w)5K|w)5K|w)5@uT4{VQxd< H08jt`2+$Cl From 3cfeec32b424d473b166115e883dae30361578fc Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 29 Aug 2025 15:19:56 -0400 Subject: [PATCH 06/10] Delete tgzs that on normal OSs are mixed case instead of the lowercase macOS shows me. --- .../cqf/fhir/utility/npm/r4/SimpleAlpha.tgz | Bin 1556 -> 0 bytes .../cqf/fhir/utility/npm/r4/SimpleBravo.tgz | Bin 1576 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleAlpha.tgz delete mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleBravo.tgz diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleAlpha.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/SimpleAlpha.tgz deleted file mode 100644 index 260b0639f0a2fba221d8dab5fb02576838a5156f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1556 zcmV+v2J87BiwFqA^|5FI|8r?=aBO8^Y;b5{E_7#l0PR^>bJ|D{_A|di#fvw!K?s4s zc5Su-*nqHcVz31$<%dBUB-x{5br^8g|9xAR&AnvTVYBgk1trb&96e`$-D2}>{^Msf zZDFKh_u#vLi?Qj+amCWhdzfF~_yT{xOb_;e!1Iv{-5=9QwDj!uc2NXX&9HiVM4d=gSAK{^{2txcU$4`!5I`a$XzsME214h{A`6t0WRzRYICrmck4%zpy z*iggFxxJAJ9Y8+OC%F z$o!f{U@iQ>3)@j$H1^YMKUO{-Qk0|Es$pr6_vJY+#MgyX+~myju{o!jk6qKSY+`FO zRwtE6rMj)+UQ{qolJgSv`F^!M=sB7KsBb8_q;z(n+|zUFHhOzXhN0M&k1c;e>qU*z zBI#=RcN>E>#DYF_c z5898Ye;kN)g}b<`2A!Uw4f%#QYD)LwnJo6CwkY?fldv50HyIAv`lv5)@<7zY`vMsb zE(?k&`Ep<2#72!MCV7zSqSk{n)ZvHy$x*l2;AE-mjQTgf){UwrHzjj8kUX_sYmclh z=q&4_hF|U6RcDtLLFfY_i|r$M&=FZPXYw_prdxvJf5iisjfE3!^jZJp`K#BMsSlns;!Q8UT-%> z=H-JLjZ4(JW?1*qmit#m*jG3vm}eqxDfdaUHL>B($=j-Uc5^P)p|6%|Lz+N3P>Nk$ zZAp*fDW|phP*?JZU_D?hKfLVEcyHcjo~VWVxxv!-4~_q<@PD9R)B$)?{J(>d7r6L8 z;r~KWSmpm^%K!Js|2Go+Wyc`dCp$3x^{&D0{6Fbw9sf`1^gKiUhc3mRp>8qRNg4+( zjjcrUjRbsg7^#0S)(tlwctFL+W~{xJ@A@8QwB? ze?TO_^~8%9**1*HTH$A@Kcv`h?B~3W@3SzTWF)z?lP%7UV(Yf+@8;!bt{phrrub!k zR>rV?9CI5vnrKAk=6H?T^Qy0JZQDHVrq93fG1Hm<+fl4tjGgCT(PcC|hoJCiWyQDw z=JQJfTkAh|4s4#dEO5<>8qdrE&QJ=uke4NQY=ya zzYio-nn`K$Jbj+q6PZll0SjUpEwRfa%$_+cvA~`WGa18DNTA|K+QP&E`EnsfL4;s+ zzDOqHAQ$`xF!s-Wg$eeX`3%Pw|*KkSw4u)05V-yhoS@`8657r{b zg}-Z#JBfZQm*dLte9weZeuMN~CKCnHr_d!Be&{-rQA3uF*Rq0>$gJb3;rSl(OZLoC z>>yNvX#myobgX9Iz0_YL4eNh-&c9{OqV^^_b}@6t#@{rU56s~>sc^WD8<^UEYX6tx zf0g4(K`2uD|Ca23@_e-RQ$QtXG;F#;K%OJ#R567?F>~_@0WPbrm`=u(ud73qvqrQ>Q{zSu=GnhOui?!3QE>=PfyR;Uys-lTl#c? zCdU|=*x&jt;8Lu5a-1kW|BlOfPT=`1X0o*bL_vsrXnyN`LZ~oyXxaE9Uy;jFRTL@` z%L!F+uUgom`1#=>;~oug>3g;jZ|aIHr_XZw|LOU^c>eQciJt%O>HI&nbRYTC-L_?W1lN2ta@o7Ub>3?KmnCV{ z`7dzdi&y|_QL1b)+$Q(`J3aru6c|9!$AKI92ELwpxVX<0XUn0ulZq`fE}UB%nP7zd zz;c~f%CUS_>L44hs1W%imJGwt+b@-ZL_tqho8#H6>tV+*1J?Bi*@`SJF#>ZDMnP=H z^ie;|io?|Tv`a~e;hL5+fU?g|Wih_w-cpm(AjI~ZYa#Y+%W;W2n6g7sNnEPCCT=H+ zB{JJDNt-*1_Mqit2%x@Y?9Q}{IlafHx7*~`x9?|b6)2?&Q!A53h^W?8Gn)& zjucH4qFAmsiDlT0DQ?$BMpKP?!qvm6bMEQgTA+0rfzccePs!~4rZw$#wNZWG@rFPm zZCY!#k4RfN)`$_;5u`!u;W9d_OOw{vJwYA$PCr)fwRWo4ZmM8Uof^}cpmkdhs8fxU zy1|{^9YkksW6%>Cf!3>}spI#pbf7~66PwTZ*uf1OHx*%GBlfHq+3ck_EV|X2yT)G^Z0CVQg1bM`+Q_3 z{Zc@GGSqFEKwrJQ&3Nvre9|wgcBwZTk#56S-nl1WPrr)iLR4eFrrma}F>6Oe@DB6S zfH~*DSKgQ&RF%3p&g#QFetce=>AJivJksd+^W(?h|I#A=7bySV2>;((%Kyuiia-;9 zcf|kCFbX0c|0n!k7Uc#0FAJ3aZ;=15B=|2AgV{QH1=C-i8oZwW&sti>|1&y0&yfFN zO7U0hTS|Cl#(_&?XXg1z0-o-)ktaf|uNVgh+(zvn)b)U4oIbz|pdN$Z-8M&v~2NC)b}@Pj<6L)+9TIJ9Pc< zby?29cO!4z5I>L4LLZj*V{RcQ1C7Ys7%y>q-u30y){W!q{P|ZoW;XI)?Zwi`c=Z_M zQ${QB2#U8>QArIj7taRP)_+_nylD_%= zU&H^ovM9XR|8o4fnDYM(U`C~xj3%e_84+)Y*ts=PC`19UAf|DwTro3d&umL9u;=YU z!Ey`|nK+RiW8#7Qr4nNxLa;fnQYd)Hhx`G8{Zn3HL;PkgAo{yzkx?8F0T2G|5QX>} z4yoTEu$s7!A`(6cKYtQnE$wnV{9SY0e)7k1yIdKbJ4`HvywLd=<-!eyuweb-9m^ot8 zFB;58X4^~kw>Nb9((nKD`~SlIA20H}OvC?sivP3co89?+OZ=DQ^2_)y3L=gF8@&I^ z?(y<8K>zxEo`D?Ku^_m=?pE*HuK^F^FZ@re)3@*<|`|W@2S+(y&!)nn2?YB z{ho=zx(N+fILP0a#aA$~Db|-*!}XH^nKST6zlc&$P*6}%P*6}%P*6}%P*6}%P*6}% aP*6}%P*6}%P*70(X!sjMF}s)mPyhh`1ulsI From 4cebd931a5c7d7800f863fc945edc8b3b87b4a68 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 29 Aug 2025 15:21:45 -0400 Subject: [PATCH 07/10] Add back files in lowercase. --- .../cqf/fhir/utility/npm/r4/simplealpha.tgz | Bin 0 -> 1564 bytes .../cqf/fhir/utility/npm/r4/simplebravo.tgz | Bin 0 -> 1577 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/simplealpha.tgz create mode 100644 cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/simplebravo.tgz diff --git a/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/simplealpha.tgz b/cqf-fhir-utility/src/test/resources/org/opencds/cqf/fhir/utility/npm/r4/simplealpha.tgz new file mode 100644 index 0000000000000000000000000000000000000000..6fdbe3d7ba6f5055da35f5f13931a2f0894d78b4 GIT binary patch literal 1564 zcmV+%2IKi3iwFpe{jq2Q|8r?=aBO8^Y;b5{E_7#l0PR^@Q{qSz_L*Oi;>DfXA%u{C zT02`KC=e891PzcWADW~|qMgg;BH*n5eNS!*a#?2AVQ0tw3M%P7eK~zS-#KB6Z1Lk~ zG;Lv|VQ=rdfJ?CM$#LcK^LvzE;`mZ&kD2c60HGfs54zubo8YUA6_^(OP^yX*;ZWoS zfh|`{2jyyRkK*Tti_|-$5`DSjWY-s`;$r>Di7fu4n|-A_)Dc z9nC}IAj=LC<&z=BIjWat)Ho}WtyX}iK~epK zyig>sp#VklQInXe)tKR4?Otul;gIh>oZA=fXi)Q&e#2LrhH*~P^mUT1>uyQqNvO8bjVZ}_98d?%eMQcrG6N`E?u%0YjV;h=4d`ZA{sBwe~Ik>TK?q*`*I^hHi; z)QD=4d!;VvJxD_xe%PNJbej!Mk-P4wfBkFSJl2(_Yz+spuhnbqk=+HI6=T#0jyt!< zvrdnj#DDp6(m8RGe_fcIUmf$GZjUGD^;%NS++6PL(m5OQhoRP#1?cB;IOw{OjxdHh zrLVXn>l*YYA%c9+QNL~;PPG2i(3<3aWJ;AtPpMubxJe$gR!2Xpw;Lnt;$Dl#CFxx& zs(axm{Yx|ID;g8bGm$ow`=mR%)ClI}ZB078K9lOuS4(psO&}er<*uQ%;kmi?Q__EV_)Q?-CRpt-O>p zz+@!C5{x|S{uCm1c#cElg z{C^insWg+(AFdvxxaav)27dP;aVgLDM z`_EDPzZ3gkSrPxM@Fn{B|83d-^!aKrzi+Ysf>?QJ|I2)t+W#HK|FSsV3L3CEo~I() zu}v`U+eP)hJq&mfec^xV4uyf?c=3mISg$NMic`sndj4WiFeV?1@jVSg>l$=mVt+AW z7KLDiwFp&{jq2Q|8r?=aBO8_a$$CFE_7#l0PR^>Q{zSu=GnhOD=1mhJv}{Ve?4LgY~j-- znx0^!V{hlXfJ?CM$#Es&`FB)a?D>WLU*byK4kK=J|C8_@>)7x@ zZ8Te@2WrFN&o}?<4{`pNme2npJ^#0I{#RC=|HVqBMvwpZbpD^2s)xMUe%G{Ig6kd{ zJM5kB*l)G}%Yv}({O6bXUquuuI}EqY{r^tS|1UWPkoU0fgr0`4XD-ekF!^*j zsYiu#Ya-MTeb%+$3p=1O>IBb&M)XaKE2(>zaGv(gM#)adErQ2M*)iDYKxef)tupOeXO_nY=? z&{xI{!!2q&36)vB)jcL%=|mw~REHOg_QO?p-VmnkiF1m2;+=XT-z(ijuiKKro;=fL zbzbSWA5gCrN)3%WyE_ceyP7fJo4(qT@1dI|N=}jk*9<(r&rGDfUkvm zOBSG=t3kixMm|Cx?vtd|O{gOa`I#Ml>KwsTOo6+3WigCZp`jy&hhN=Z)dFPyhJ+%|fMM;kQn)SQ& zX4*~(!8^=P6Xu))UyIu8uqHM1Nmd``@#FK_OxERX?vX~vpC6R}Q~tl={-;zcQU3p) z`2RUZe(2%#K5T_>+#`m0le*Yp3hrB(btqto*Y`5&ef ze?`6}geNr)TpHV{=W7Z0VmGq}bx&@q*W7 zIfmzi?xrDr9-pN?tnSC$LXHO-k-0Hm;r6`itF3Js$JhDuuX4<6utvSM>+VsArV zVTZf0Gqc0#wcT9q-eSq^|1yDM>Gxmy{r8FgSIa`V$d*bKUKFY{0eDCJf1dZlN&4pd ze*^#L$|e5A{uiUq#gzYV0V$PcGMb#wXGGi)V&~RGE*JX1f|%xs)L~L)&+G~;u;<-e z&a^cW>Nu93VB&)Or4pkdLa;uslFPZsgZu%4{Zn3HLHuSeA^JOdo{?-20T2G|2nF~W z4yoTEuHI>h8C2B=@Z&(o0Y*d_$`*WK!U`!(QE^o9SaITQvX$BRF#!+d3>(LI%%xaTik3dZDP z@qSOoU|oj>Ozbb-m_=7Gktx;_Sk3X`0hu%KNWX|uP*6}%P*6}%P*6}%P*6}%P*6}% bP*6}%P*6}%P*6}%{Al Date: Fri, 29 Aug 2025 15:38:52 -0400 Subject: [PATCH 08/10] Address sonar feedback. --- .../cqf/fhir/utility/npm/NpmConfigDependencySubstitutor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutor.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutor.java index 720094606e..b7197d46aa 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutor.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutor.java @@ -14,6 +14,10 @@ */ public class NpmConfigDependencySubstitutor { + private NpmConfigDependencySubstitutor() { + // static utility class + } + public static NpmPackageLoader substituteNpmPackageLoaderIfEmpty(Optional optNpmPackageLoader) { return NpmPackageLoader.getDefaultIfEmpty(optNpmPackageLoader.orElse(null)); } From 770470982d2c97115a15163f6c81bfb337884054 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 2 Sep 2025 09:30:22 -0400 Subject: [PATCH 09/10] Increase test coverage. --- .../npm/BaseNpmResourceInfoForCqlTest.java | 103 +++++++++++++++--- .../NpmConfigDependencySubstitutorTest.java | 29 +++++ .../npm/r4/NpmResourceHolderR4Test.java | 4 +- .../npm/r5/NpmResourceHolderR5Test.java | 4 +- 4 files changed, 121 insertions(+), 19 deletions(-) create mode 100644 cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutorTest.java diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java index bfbbf67f9f..2ad2cf8eb8 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/BaseNpmResourceInfoForCqlTest.java @@ -1,6 +1,7 @@ package org.opencds.cqf.fhir.utility.npm; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -14,6 +15,7 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; +import org.hl7.cql.model.NamespaceInfo; import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.fhir.instance.model.api.ICompositeType; import org.hl7.fhir.r4.model.CanonicalType; @@ -40,6 +42,9 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected static final String WITH_TWO_LAYERS_DERIVED_LIBRARIES = "withtwolayersderivedlibraries"; protected static final String WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER = "WithTwoLayersDerivedLibraries"; + protected static final String SIMPLE_ALPHA_NAMESPACE = NAMESPACE_PREFIX + SIMPLE_ALPHA_LOWER; + protected static final String SIMPLE_BRAVO_NAMESPACE = NAMESPACE_PREFIX + SIMPLE_BRAVO_LOWER; + protected static final String WITH_DERIVED_NAMESPACE = NAMESPACE_PREFIX + WITH_DERIVED_LIBRARY_LOWER; protected static final String WITH_TWO_LAYERS_NAMESPACE = NAMESPACE_PREFIX + WITH_TWO_LAYERS_DERIVED_LIBRARIES; protected static final String DERIVED_LAYER_1_A = "DerivedLayer1a"; @@ -65,15 +70,13 @@ public abstract class BaseNpmResourceInfoForCqlTest { private static final String PIPE = "|"; private static final String VERSION_0_1 = "0.1"; private static final String VERSION_0_2 = "0.2"; - private static final String VERSION_0_3 = "0.3"; private static final String VERSION_0_4 = "0.4"; private static final String VERSION_0_5 = "0.5"; - private static final String VERSION_0_6 = "0.6"; protected static final String SIMPLE_ALPHA_NAMESPACE_URL = "http://simplealpha.npm.opencds.org"; protected static final String SIMPLE_BRAVO_NAMESPACE_URL = "http://simplebravo.npm.opencds.org"; - protected static final String DERIVED_URL = "http://withderivedlibrary.npm.opencds.org"; - protected static final String DERIVED_TWO_LAYERS_URL = "http://withtwolayersderivedlibraries.npm.opencds.org"; + protected static final String WITH_DERIVED_URL = "http://withderivedlibrary.npm.opencds.org"; + protected static final String WITH_DERIVED_TWO_LAYERS_URL = "http://withtwolayersderivedlibraries.npm.opencds.org"; protected static final String CROSS_PACKAGE_SOURCE_URL = "http://crosspackagesource.npm.opencds.org"; protected static final String CROSS_PACKAGE_TARGET_URL = "http://crosspackagetarget.npm.opencds.org"; @@ -82,11 +85,11 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected static final String MEASURE_URL_BRAVO = SIMPLE_BRAVO_NAMESPACE_URL + SLASH_MEASURE_SLASH + SIMPLE_BRAVO_MIXED; protected static final String MEASURE_URL_WITH_DERIVED_LIBRARY = - DERIVED_URL + SLASH_MEASURE_SLASH + WITH_DERIVED_LIBRARY_MIXED; + WITH_DERIVED_URL + SLASH_MEASURE_SLASH + WITH_DERIVED_LIBRARY_MIXED; protected static final String MEASURE_URL_WITH_DERIVED_LIBRARY_WITH_VERSION = MEASURE_URL_WITH_DERIVED_LIBRARY + PIPE + VERSION_0_4; protected static final String MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES = - DERIVED_TWO_LAYERS_URL + SLASH_MEASURE_SLASH + WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER; + WITH_DERIVED_TWO_LAYERS_URL + SLASH_MEASURE_SLASH + WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER; protected static final String MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION = MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES + PIPE + VERSION_0_5; @@ -103,10 +106,11 @@ public abstract class BaseNpmResourceInfoForCqlTest { protected static final String LIBRARY_URL_BRAVO_WITH_VERSION = LIBRARY_URL_BRAVO_NO_VERSION + PIPE + VERSION_0_1; protected static final String LIBRARY_URL_WITH_DERIVED_LIBRARY_NO_VERSION = - DERIVED_URL + SLASH_LIBRARY_SLASH + WITH_DERIVED_LIBRARY_MIXED; + WITH_DERIVED_URL + SLASH_LIBRARY_SLASH + WITH_DERIVED_LIBRARY_MIXED; protected static final String LIBRARY_URL_WITH_DERIVED_LIBRARY_WITH_VERSION = - DERIVED_URL + SLASH_LIBRARY_SLASH + WITH_DERIVED_LIBRARY_MIXED + PIPE + VERSION_0_4; - protected static final String LIBRARY_URL_DERIVED_LIBRARY = DERIVED_URL + SLASH_LIBRARY_SLASH + DERIVED_LIBRARY; + WITH_DERIVED_URL + SLASH_LIBRARY_SLASH + WITH_DERIVED_LIBRARY_MIXED + PIPE + VERSION_0_4; + protected static final String LIBRARY_URL_DERIVED_LIBRARY = + WITH_DERIVED_URL + SLASH_LIBRARY_SLASH + DERIVED_LIBRARY; protected static final String LIBRARY_URL_CROSS_PACKAGE_SOURCE = CROSS_PACKAGE_SOURCE_URL + SLASH_LIBRARY_SLASH + CROSS_PACKAGE_SOURCE_ID; @@ -116,23 +120,56 @@ public abstract class BaseNpmResourceInfoForCqlTest { CROSS_PACKAGE_TARGET_URL + SLASH_LIBRARY_SLASH + CROSS_PACKAGE_TARGET_ID; protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_NO_VERSION = - DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER; + WITH_DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + WITH_TWO_LAYERS_DERIVED_LIBRARIES_UPPER; protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION = LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_NO_VERSION + PIPE + VERSION_0_5; protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1A = - DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_1_A; + WITH_DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_1_A; protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1B = - DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_1_B; + WITH_DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_1_B; protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_2A = - DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_2_A; + WITH_DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_2_A; protected static final String LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_2B = - DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_2_B; + WITH_DERIVED_TWO_LAYERS_URL + SLASH_LIBRARY_SLASH + DERIVED_LAYER_2_B; protected abstract FhirVersionEnum getExpectedFhirVersion(); + protected void simpleAlpha( + Path tgzPath, + String measureUrl, + String expectedLibraryUrlFromMeasure, + String expectedLibraryUrlWithinLibrary, + String expectedCql) { + + simpleCommon( + tgzPath, + List.of(new NamespaceInfo(SIMPLE_ALPHA_NAMESPACE, SIMPLE_ALPHA_NAMESPACE_URL)), + measureUrl, + expectedLibraryUrlFromMeasure, + expectedLibraryUrlWithinLibrary, + expectedCql); + } + + protected void simpleBravo( + Path tgzPath, + String measureUrl, + String expectedLibraryUrlFromMeasure, + String expectedLibraryUrlWithinLibrary, + String expectedCql) { + + simpleCommon( + tgzPath, + List.of(new NamespaceInfo(SIMPLE_BRAVO_NAMESPACE, SIMPLE_BRAVO_NAMESPACE_URL)), + measureUrl, + expectedLibraryUrlFromMeasure, + expectedLibraryUrlWithinLibrary, + expectedCql); + } + protected void simpleCommon( Path tgzPath, + List expectedNamespaceInfos, String measureUrl, String expectedLibraryUrlFromMeasure, String expectedLibraryUrlWithinLibrary, @@ -142,6 +179,10 @@ protected void simpleCommon( final NpmResourceHolder npmResourceHolder = loader.loadNpmResources(new org.hl7.fhir.r5.model.CanonicalType(measureUrl)); + sanityCheckNpmResourceHolder(npmResourceHolder); + + assertEquals(expectedNamespaceInfos, npmResourceHolder.getNamespaceInfos()); + verifyMeasure(measureUrl, expectedLibraryUrlFromMeasure, npmResourceHolder); verifyLibrary( expectedLibraryUrlWithinLibrary, @@ -158,6 +199,13 @@ protected void multiplePackages(String expectedCqlAlpha, String expectedCqlBravo final NpmResourceHolder resourceInfoBravo = loader.loadNpmResources(new org.hl7.fhir.r5.model.CanonicalType(MEASURE_URL_BRAVO)); + assertEquals( + List.of(new NamespaceInfo(SIMPLE_ALPHA_NAMESPACE, SIMPLE_ALPHA_NAMESPACE_URL)), + resourceInfoAlpha.getNamespaceInfos()); + assertEquals( + List.of(new NamespaceInfo(SIMPLE_BRAVO_NAMESPACE, SIMPLE_BRAVO_NAMESPACE_URL)), + resourceInfoBravo.getNamespaceInfos()); + verifyMeasure(MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA_WITH_VERSION, resourceInfoAlpha); verifyLibrary( LIBRARY_URL_ALPHA_NO_VERSION, @@ -177,12 +225,23 @@ protected void derivedLibrary(String expectedCql, String expectedCqlDerived) { final NpmResourceHolder resourceInfoWithNoVersion = loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_DERIVED_LIBRARY)); + + sanityCheckNpmResourceHolder(resourceInfoWithNoVersion); + + var expectedNamespaceInfos = List.of(new NamespaceInfo(WITH_DERIVED_NAMESPACE, WITH_DERIVED_URL)); + + assertEquals(expectedNamespaceInfos, resourceInfoWithNoVersion.getNamespaceInfos()); + verifyMeasure( MEASURE_URL_WITH_DERIVED_LIBRARY, LIBRARY_URL_WITH_DERIVED_LIBRARY_WITH_VERSION, resourceInfoWithNoVersion); + final NpmResourceHolder resourceInfoWithVersion = loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_DERIVED_LIBRARY_WITH_VERSION)); + sanityCheckNpmResourceHolder(resourceInfoWithVersion); + assertEquals(expectedNamespaceInfos, resourceInfoWithVersion.getNamespaceInfos()); + verifyMeasure( MEASURE_URL_WITH_DERIVED_LIBRARY, LIBRARY_URL_WITH_DERIVED_LIBRARY_WITH_VERSION, @@ -222,8 +281,12 @@ protected void derivedLibraryTwoLayers( final NpmPackageLoaderInMemory loader = setup(WITH_TWO_LAYERS_DERIVED_LIBRARIES_TGZ); + var expectedNamespaceInfos = List.of(new NamespaceInfo(WITH_TWO_LAYERS_NAMESPACE, WITH_DERIVED_TWO_LAYERS_URL)); + final NpmResourceHolder resourceInfoNoVersion = loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES)); + sanityCheckNpmResourceHolder(resourceInfoNoVersion); + assertEquals(expectedNamespaceInfos, resourceInfoNoVersion.getNamespaceInfos()); verifyMeasure( MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION, @@ -235,6 +298,8 @@ protected void derivedLibraryTwoLayers( final NpmResourceHolder resourceInfoWithVersion = loader.loadNpmResources(new CanonicalType(MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION)); + sanityCheckNpmResourceHolder(resourceInfoWithVersion); + assertEquals(expectedNamespaceInfos, resourceInfoWithVersion.getNamespaceInfos()); verifyMeasure( MEASURE_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES, LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARIES_WITH_VERSION, @@ -248,7 +313,7 @@ protected void derivedLibraryTwoLayers( .findMatchingLibrary(new VersionedIdentifier() .withId(DERIVED_LAYER_1_A) .withVersion(VERSION_0_5) - .withSystem(DERIVED_TWO_LAYERS_URL)) + .withSystem(WITH_DERIVED_TWO_LAYERS_URL)) .orElse(null); verifyLibrary(LIBRARY_URL_WITH_TWO_LAYERS_DERIVED_LIBRARY_1A, expectedCqlDerived1a, derivedLibrary1a); @@ -281,12 +346,14 @@ protected void crossPackage(String expectedCqlSource, String expectedCqlTarget) final NpmResourceHolder resourceInfoWithNoVersion = loader.loadNpmResources(new CanonicalType(MEASURE_URL_CROSS_PACKAGE_SOURCE)); + sanityCheckNpmResourceHolder(resourceInfoWithNoVersion); verifyMeasure( MEASURE_URL_CROSS_PACKAGE_SOURCE, LIBRARY_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION, resourceInfoWithNoVersion); final NpmResourceHolder resourceInfoWithVersion = loader.loadNpmResources(new CanonicalType(MEASURE_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION)); + sanityCheckNpmResourceHolder(resourceInfoWithVersion); verifyMeasure( MEASURE_URL_CROSS_PACKAGE_SOURCE, LIBRARY_URL_CROSS_PACKAGE_SOURCE_WITH_VERSION, @@ -356,4 +423,10 @@ private void verifyMeasure(String measureUrl, String expectedLibraryUrl, NpmReso private NpmPackageLoaderInMemory setup(Path... tgzPaths) { return NpmPackageLoaderInMemory.fromNpmPackageClasspath(getClass(), tgzPaths); } + + private static void sanityCheckNpmResourceHolder(NpmResourceHolder npmResourceHolder) { + assertNotNull(npmResourceHolder); + assertFalse(npmResourceHolder.getNpmPackages().isEmpty()); + assertFalse(npmResourceHolder.getNamespaceInfos().isEmpty()); + } } diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutorTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutorTest.java new file mode 100644 index 0000000000..1bac236b3e --- /dev/null +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/NpmConfigDependencySubstitutorTest.java @@ -0,0 +1,29 @@ +package org.opencds.cqf.fhir.utility.npm; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import java.util.Optional; +import org.junit.jupiter.api.Test; + +class NpmConfigDependencySubstitutorTest { + + @Test + void present() { + var npmPackageLoader = mock(NpmPackageLoader.class); + + var npmPackageLoaderFromSubstitutor = + NpmConfigDependencySubstitutor.substituteNpmPackageLoaderIfEmpty(Optional.of(npmPackageLoader)); + + assertNotEquals(NpmPackageLoader.DEFAULT, npmPackageLoaderFromSubstitutor); + assertEquals(npmPackageLoader, npmPackageLoaderFromSubstitutor); + } + + @Test + void empty() { + var npmPackageLoaderFromSubstitutor = + NpmConfigDependencySubstitutor.substituteNpmPackageLoaderIfEmpty(Optional.empty()); + + assertEquals(NpmPackageLoader.DEFAULT, npmPackageLoaderFromSubstitutor); + } +} diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java index 565515f42c..f57cc84d72 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r4/NpmResourceHolderR4Test.java @@ -228,7 +228,7 @@ protected FhirVersionEnum getExpectedFhirVersion() { @Test void simpleAlpha() { - simpleCommon( + simpleAlpha( Path.of(SIMPLE_ALPHA_TGZ), MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA_WITH_VERSION, @@ -238,7 +238,7 @@ void simpleAlpha() { @Test void simpleBravo() { - simpleCommon( + simpleBravo( Path.of(SIMPLE_BRAVO_TGZ), MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO_WITH_VERSION, diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java index f742b35e83..5c8cb61ed1 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/r5/NpmResourceHolderR5Test.java @@ -225,7 +225,7 @@ protected FhirVersionEnum getExpectedFhirVersion() { @Test void simpleAlpha() { - simpleCommon( + simpleAlpha( Path.of(SIMPLE_ALPHA_TGZ), MEASURE_URL_ALPHA, LIBRARY_URL_ALPHA_WITH_VERSION, @@ -235,7 +235,7 @@ void simpleAlpha() { @Test void simpleBravo() { - simpleCommon( + simpleBravo( Path.of(SIMPLE_BRAVO_TGZ), MEASURE_URL_BRAVO, LIBRARY_URL_BRAVO_WITH_VERSION, From fbc36c2165f7df1eb6a20837401627729503eb19 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 2 Sep 2025 09:49:36 -0400 Subject: [PATCH 10/10] More test coverage. --- .../MeasureOrNpmResourceHolderListTest.java | 154 ++++++++++++++++++ .../npm/MeasureOrNpmResourceHolderTest.java | 94 +++++++++++ 2 files changed, 248 insertions(+) create mode 100644 cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderListTest.java create mode 100644 cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderTest.java diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderListTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderListTest.java new file mode 100644 index 0000000000..ae3914ea67 --- /dev/null +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderListTest.java @@ -0,0 +1,154 @@ +package org.opencds.cqf.fhir.utility.npm; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import java.util.List; +import org.hl7.fhir.r4.model.Measure; +import org.junit.jupiter.api.Test; + +class MeasureOrNpmResourceHolderListTest { + + @Test + void shouldCreateFromSingleMeasureOrNpmResourceHolder() { + var measure = new Measure().setUrl("http://example.org/Measure/test"); + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + var holderList = MeasureOrNpmResourceHolderList.of(holder); + + assertEquals(1, holderList.size()); + assertEquals(List.of(holder), holderList.getMeasuresOrNpmResourceHolders()); + } + + @Test + void shouldCreateFromListOfMeasureOrNpmResourceHolders() { + var measure1 = (Measure) new Measure().setUrl("http://example.org/Measure/test1"); + var measure2 = (Measure) new Measure().setUrl("http://example.org/Measure/test2"); + + MeasureOrNpmResourceHolder holder1 = MeasureOrNpmResourceHolder.measureOnly(measure1); + MeasureOrNpmResourceHolder holder2 = MeasureOrNpmResourceHolder.measureOnly(measure2); + + MeasureOrNpmResourceHolderList list = MeasureOrNpmResourceHolderList.of(List.of(holder1, holder2)); + + assertEquals(2, list.size()); + assertEquals(List.of(holder1, holder2), list.getMeasuresOrNpmResourceHolders()); + } + + @Test + void shouldCreateFromSingleMeasure() { + var measure = new Measure().setUrl("http://example.org/Measure/test"); + + var holderList = MeasureOrNpmResourceHolderList.of(measure); + + // Assert + assertEquals(1, holderList.size()); + assertEquals(measure, holderList.getMeasures().get(0)); + } + + @Test + void shouldCreateFromListOfMeasures() { + var measure1 = new Measure().setUrl("http://example.org/Measure/test1"); + var measure2 = new Measure().setUrl("http://example.org/Measure/test2"); + + var holderList = MeasureOrNpmResourceHolderList.ofMeasures(List.of(measure1, measure2)); + + assertEquals(2, holderList.size()); + assertEquals(List.of(measure1, measure2), holderList.getMeasures()); + } + + @Test + void shouldReturnCorrectNpmResourceHolders() { + var measure1 = new Measure(); + var measure2 = new Measure(); + + var holder1 = MeasureOrNpmResourceHolder.measureOnly(measure1); + var holder2 = MeasureOrNpmResourceHolder.measureOnly(measure2); + + var holderList = MeasureOrNpmResourceHolderList.of(List.of(holder1, holder2)); + + var npmResourceHolders = holderList.npmResourceHolders(); + + assertEquals(2, npmResourceHolders.size()); + assertEquals(List.of(NpmResourceHolder.EMPTY, NpmResourceHolder.EMPTY), npmResourceHolders); + } + + @Test + void shouldReturnCorrectMeasureUrls() { + var measure1 = new Measure().setUrl("http://example.org/Measure/test1"); + var measure2 = new Measure().setUrl("http://example.org/Measure/test2"); + + var holder1 = MeasureOrNpmResourceHolder.measureOnly(measure1); + var holder2 = MeasureOrNpmResourceHolder.measureOnly(measure2); + + var holderList = MeasureOrNpmResourceHolderList.of(List.of(holder1, holder2)); + + var measureUrls = holderList.getMeasureUrls(); + + assertEquals(List.of("http://example.org/Measure/test1", "http://example.org/Measure/test2"), measureUrls); + } + + @Test + void shouldCheckMeasureLibrariesSuccessfully() { + var measure = new Measure() + .setUrl("http://example.org/Measure/test") + .addLibrary("http://example.org/Library/testLib"); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + var holderList = MeasureOrNpmResourceHolderList.of(holder); + + assertDoesNotThrow(holderList::checkMeasureLibraries); + } + + @Test + void shouldThrowExceptionWhenMeasureHasNoLibrary() { + // Arrange + var measure = new Measure().setUrl("http://example.org/Measure/test"); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + var holderList = MeasureOrNpmResourceHolderList.of(holder); + + var exception = assertThrows(InvalidRequestException.class, holderList::checkMeasureLibraries); + assertEquals( + "Measure http://example.org/Measure/test does not have a primary library specified", + exception.getMessage()); + } + + @Test + void shouldImplementEqualsAndHashCodeCorrectly() { + var measure1 = new Measure().setUrl("http://example.org/Measure/test1"); + var measure2 = new Measure().setUrl("http://example.org/Measure/test2"); + + var holder1 = MeasureOrNpmResourceHolder.measureOnly(measure1); + var holder2 = MeasureOrNpmResourceHolder.measureOnly(measure2); + + // Act + var holderList1 = MeasureOrNpmResourceHolderList.of(List.of(holder1, holder2)); + var holderList2 = MeasureOrNpmResourceHolderList.of(List.of(holder1, holder2)); + var holderList3 = MeasureOrNpmResourceHolderList.of(List.of(holder1)); + + // Assert + assertEquals(holderList1, holderList2); + assertEquals(holderList1.hashCode(), holderList2.hashCode()); + assertNotEquals(holderList1, holderList3); + assertNotEquals(holderList1.hashCode(), holderList3.hashCode()); + } + + @Test + void shouldHaveCorrectToStringRepresentation() { + // Arrange + var measure = new Measure().setUrl("http://example.org/Measure/test"); + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + var holderList = MeasureOrNpmResourceHolderList.of(holder); + + // Act + var toStringResult = holderList.toString(); + + // Assert + assertTrue(toStringResult.contains("MeasurePlusNpmResourceHolderList")); + assertTrue(toStringResult.contains("measuresPlusNpmResourceHolders")); + } +} diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderTest.java new file mode 100644 index 0000000000..bb5709c6e9 --- /dev/null +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/npm/MeasureOrNpmResourceHolderTest.java @@ -0,0 +1,94 @@ +package org.opencds.cqf.fhir.utility.npm; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; +import org.hl7.fhir.r4.model.Measure; +import org.junit.jupiter.api.Test; + +class MeasureOrNpmResourceHolderTest { + + @Test + void shouldCreateMeasureOnlyInstance() { + var measure = new Measure(); + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + assertEquals(measure, holder.getMeasure()); + assertEquals(NpmResourceHolder.EMPTY, holder.npmResourceHolder()); + } + + @Test + void hasLibraryShouldReturnTrueWhenMeasureHasLibrary() { + var measure = new Measure(); + measure.addLibrary("http://example.org/Library/123"); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + assertTrue(holder.hasLibrary()); + } + + @Test + void hasLibraryShouldReturnFalseWhenMeasureHasNoLibrary() { + var measure = new Measure(); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + assertFalse(holder.hasLibrary()); + } + + @Test + void getMainLibraryUrlShouldReturnUrlFromMeasure() { + var measure = new Measure().addLibrary("http://example.org/Library/123"); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + assertEquals(Optional.of("http://example.org/Library/123"), holder.getMainLibraryUrl()); + } + + @Test + void getMainLibraryUrlShouldReturnEmptyWhenMeasureHasNoLibrary() { + var measure = new Measure(); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + assertEquals(Optional.empty(), holder.getMainLibraryUrl()); + } + + @Test + void getMeasureIdElementShouldReturnIdFromMeasure() { + var measure = (Measure) new Measure().setId("measure-123"); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + assertEquals("measure-123", holder.getMeasureIdElement().getIdPart()); + } + + @Test + void hasMeasureUrlShouldReturnTrueWhenMeasureHasUrl() { + var measure = new Measure().setUrl("http://example.org/Measure/123"); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + assertTrue(holder.hasMeasureUrl()); + } + + @Test + void hasMeasureUrlShouldReturnFalseWhenMeasureHasNoUrl() { + var measure = new Measure(); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + assertFalse(holder.hasMeasureUrl()); + } + + @Test + void getMeasureUrlShouldReturnUrlFromMeasure() { + var measure = new Measure().setUrl("http://example.org/Measure/123"); + + var holder = MeasureOrNpmResourceHolder.measureOnly(measure); + + assertEquals("http://example.org/Measure/123", holder.getMeasureUrl()); + } +}