diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CIBuildClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CIBuildClient.java index de7f8ab297..f86a5093b3 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CIBuildClient.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CIBuildClient.java @@ -33,16 +33,22 @@ public class CIBuildClient { @Getter private final String rootUrl; - /** key = packageId - * value = url of built package on https://build.fhir.org/ig/ */ + /** + * key = packageId + * value = url of built package on https://build.fhir.org/ig/ + **/ private final Map ciPackageUrls = new HashMap<>(); private final boolean silent; public CIBuildClient() { - rootUrl = DEFAULT_ROOT_URL; - ciQueryInterval = DEFAULT_CI_QUERY_INTERVAL; - silent = false; + this(DEFAULT_ROOT_URL, DEFAULT_CI_QUERY_INTERVAL, false); + } + + public CIBuildClient(String rootUrl, long ciQueryInterval, boolean silent) { + this.rootUrl = rootUrl; + this.ciQueryInterval = ciQueryInterval; + this.silent = silent; } String getPackageId(String canonical) { @@ -67,10 +73,6 @@ String getPackageId(String canonical) { return null; } - public boolean hasPackage(String packageId) { - return ciPackageUrls.containsKey(packageId); - } - String getPackageUrl(String packageId) { checkCIServerQueried(); for (JsonObject o : ciBuildInfo.asJsonObjects()) { @@ -81,13 +83,9 @@ String getPackageUrl(String packageId) { return null; } - String getCIPackageUrl(String packageId) { - return ciPackageUrls.get(packageId); - } - public boolean isCurrent(String id, NpmPackage npmPackage) throws IOException { checkCIServerQueried(); - String packageManifestUrl = getCIPackageUrl(id); + String packageManifestUrl = ciPackageUrls.get(id); JsonObject packageManifestJson = JsonParser.parseObjectFromUrl(Utilities.pathURL(packageManifestUrl, "package.manifest.json")); String currentDate = packageManifestJson.asString("date"); String packageDate = npmPackage.date(); @@ -97,8 +95,8 @@ public boolean isCurrent(String id, NpmPackage npmPackage) throws IOException { BasePackageCacheManager.InputStreamWithSrc loadFromCIBuild(String id, String branch) { checkCIServerQueried(); - if (hasPackage(id)) { - String packageBaseUrl = getCIPackageUrl(id); + if (ciPackageUrls.containsKey(id)) { + String packageBaseUrl = ciPackageUrls.get(id); if (branch == null) { InputStream stream; try { @@ -113,7 +111,7 @@ BasePackageCacheManager.InputStreamWithSrc loadFromCIBuild(String id, String bra } } else if (id.startsWith("hl7.fhir.r6")) { InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(rootUrl, id + ".tgz")); - return new BasePackageCacheManager.InputStreamWithSrc(stream, Utilities.pathURL("https://build.fhir.org", id + ".tgz"), "current"); + return new BasePackageCacheManager.InputStreamWithSrc(stream, Utilities.pathURL(rootUrl, id + ".tgz"), "current"); } else if (id.startsWith("hl7.fhir.uv.extensions.")) { InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(rootUrl + "/ig/HL7/fhir-extensions/", id + ".tgz")); return new BasePackageCacheManager.InputStreamWithSrc(stream, Utilities.pathURL(rootUrl + "/ig/HL7/fhir-extensions/", id + ".tgz"), "current"); @@ -132,7 +130,7 @@ private InputStream fetchFromUrlSpecific(String source) throws FHIRException { } } - void checkCIServerQueried() { + private void checkCIServerQueried() { if (System.currentTimeMillis() - ciLastQueriedTimeStamp > ciQueryInterval) { try { updateFromCIServer(); diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/CIBuildClientTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/CIBuildClientTests.java new file mode 100644 index 0000000000..23115ce0cf --- /dev/null +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/CIBuildClientTests.java @@ -0,0 +1,158 @@ +package org.hl7.fhir.utilities.npm; + +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.MockResponse; +import org.junit.jupiter.api.Test; + +import okhttp3.mockwebserver.MockWebServer; +import org.mockito.Mockito; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doReturn; + +public class CIBuildClientTests { + + static final String DUMMY_PACKAGE = "my.dummy.package"; + + public static final String DUMMY_PACKAGE_URL = "http://example.org/my-dummy-package"; + public static final String DUMMY_PACKAGE_NAME = "DummyPackage"; + static final String dummyQas = "[\n" + + " {\n" + + " \"url\": \"" + DUMMY_PACKAGE_URL + "\",\n" + + " \"name\": \"" + DUMMY_PACKAGE_NAME + "\",\n" + + " \"title\": \"Dummy Package\",\n" + + " \"description\": \"Not a real package.\",\n" + + " \"status\": \"draft\",\n" + + " \"package-id\": \""+ DUMMY_PACKAGE +"\",\n" + + " \"ig-ver\": \"1.1.0\",\n" + + " \"date\": \"Sat, 30 Nov, 2024 00:31:05 +0000\",\n" + + " \"dateISO8601\": \"2024-11-30T00:31:05+00:00\",\n" + + " \"version\": \"4.0.1\",\n" + + " \"repo\": \"Dummy-Organisation/dummy-project/branches/main/qa.json\"\n" + + " }]"; + + public static final String DUMMY_PACKAGE_DATE = "20241227181832"; + static final String dummyManifest = "{\n" + + " \"version\" : \"1.1.0\",\n" + + " \"fhirVersion\" : [\"4.0.1\"],\n" + + " \"date\" : \"" + DUMMY_PACKAGE_DATE + "\",\n" + + " \"name\" : \""+ DUMMY_PACKAGE_NAME +"\",\n" + + " \"jurisdiction\" : \"urn:iso:std:iso:3166#CA\"\n" + + "}"; + + @Test + public void testOnlyOneQasQueryUnderRefreshThreshold() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + CIBuildClient ciBuildClient = getCiBuildClient(server); + + enqueueJson(server, dummyQas); + enqueueDummyTgzStream(server, "fake tgz"); + verifyInputStreamContent(ciBuildClient.loadFromCIBuild(DUMMY_PACKAGE, "main"), "fake tgz"); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertThat(server.takeRequest().getPath()).startsWith("/ig/qas.json?nocache="); + assertThat(server.takeRequest().getPath()).startsWith("/ig/Dummy-Organisation/dummy-project/branches/main/package.tgz"); + + enqueueDummyTgzStream(server, "fake tgz 2"); + + verifyInputStreamContent(ciBuildClient.loadFromCIBuild(DUMMY_PACKAGE, "main"), "fake tgz 2"); + } + + @Test + public void testNewQasQueryOverRefreshThreshold() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + CIBuildClient ciBuildClient = getCiBuildClient(server); + + enqueueJson(server, dummyQas); + enqueueDummyTgzStream(server, "fake tgz"); + verifyInputStreamContent(ciBuildClient.loadFromCIBuild(DUMMY_PACKAGE, "main"), "fake tgz"); + + assertThat(server.getRequestCount()).isEqualTo(2); + assertThat(server.takeRequest().getPath()).startsWith("/ig/qas.json?nocache="); + assertThat(server.takeRequest().getPath()).startsWith("/ig/Dummy-Organisation/dummy-project/branches/main/package.tgz"); + + enqueueJson(server, dummyQas); + enqueueDummyTgzStream(server, "fake tgz 2"); + + Thread.sleep(1500); + verifyInputStreamContent(ciBuildClient.loadFromCIBuild(DUMMY_PACKAGE, "main"), "fake tgz 2"); + } + + @Test + public void testGetPackageId() { + MockWebServer server = new MockWebServer(); + CIBuildClient ciBuildClient = getCiBuildClient(server); + + enqueueJson(server, dummyQas); + + String packageId = ciBuildClient.getPackageId(DUMMY_PACKAGE_URL); + assertThat(packageId).isEqualTo(DUMMY_PACKAGE); + } + + @Test + public void testGetPackageUrl() { + MockWebServer server = new MockWebServer(); + CIBuildClient ciBuildClient = getCiBuildClient(server); + + enqueueJson(server, dummyQas); + + String packageUrl = ciBuildClient.getPackageUrl(DUMMY_PACKAGE); + assertThat(packageUrl).isEqualTo(DUMMY_PACKAGE_URL); + } + + @Test + public void testIsCurrentPackage() throws IOException { + MockWebServer server = new MockWebServer(); + CIBuildClient ciBuildClient = getCiBuildClient(server); + + enqueueJson(server, dummyQas); + + enqueueJson(server, dummyManifest); + + NpmPackage npmPackage = Mockito.spy(NpmPackage.empty()); + doReturn(DUMMY_PACKAGE_DATE).when(npmPackage).date(); + doReturn(DUMMY_PACKAGE_URL).when(npmPackage).url(); + assertTrue(ciBuildClient.isCurrent(DUMMY_PACKAGE, npmPackage)); + enqueueJson(server, dummyManifest); + + String wrongDate = "1092302309"; + assertThat(wrongDate).isNotEqualTo(DUMMY_PACKAGE_DATE); + doReturn(wrongDate).when(npmPackage).date(); + assertFalse(ciBuildClient.isCurrent(DUMMY_PACKAGE, npmPackage)); + } + + private static void enqueueDummyTgzStream(MockWebServer server, String fake_tgz) { + server.enqueue( + new MockResponse() + .setBody(fake_tgz) + .addHeader("Content-Type", "application/text") + .setResponseCode(200)); + } + + + private static CIBuildClient getCiBuildClient(MockWebServer server) { + HttpUrl serverRoot = server.url(""); + return new CIBuildClient(serverRoot.toString().substring(0, serverRoot.toString().length() - 1), 1000, false); + } + + + private static void enqueueJson(MockWebServer server, String qasJson) { + byte[] payload = qasJson.getBytes(StandardCharsets.UTF_8); + server.enqueue( + new MockResponse() + .setBody(new String(payload)) + .addHeader("Content-Type", "application/json") + .setResponseCode(200) + ); + } + + private static void verifyInputStreamContent(BasePackageCacheManager.InputStreamWithSrc inputStream, String expected) throws IOException { + String content = new String(inputStream.stream.readAllBytes()); + assertThat(content).isEqualTo(expected); + } +} diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/FilesystemPackageManagerTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/FilesystemPackageManagerTests.java index 1f8c48871f..c1d7c7f127 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/FilesystemPackageManagerTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/npm/FilesystemPackageManagerTests.java @@ -21,7 +21,6 @@ import javax.annotation.Nonnull; -import okhttp3.mockwebserver.MockWebServer; import org.hl7.fhir.utilities.IniFile; import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; import org.junit.jupiter.api.Assertions;