Implement private NPM server (#1337)

* WIP start adding NPM data.

* WIP 2 Testing against verdaccio npm

* WIP private npm package servers

* WIP2 add server type config

* WIP3 parse tarball url

* Fix package date parsing, start mocking server tests

* Add dummy package, assert authorization and content

* Add more tests

* Add serverType to settings test

* Ignore tgz files for bidi check

* tighter bidi ignore

* different ignore regex

* Make packageManagement settings, allow ignoring default package servers

* New tests + token authentication

* Manage 404s when configured servers don't have package
This commit is contained in:
dotasek 2023-07-22 11:01:36 -04:00 committed by GitHub
parent b62f9afc5a
commit 2654e85df3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 746 additions and 75 deletions

View File

@ -24,5 +24,7 @@ jobs:
- name: Check for bidi characters
id: bidi_check
uses: HL7/bidi-checker-action@v1.9
env:
IGNORE: dummy-package.tgz$
- name: Get the output time
run: echo "The time was ${{ steps.bidi_check.outputs.time }}"

View File

@ -157,6 +157,7 @@ public class TerminologyCacheManager {
String url = "https://tx.fhir.org/post/tx-cache/"+ghOrg+"/"+ghRepo+"/"+ghBranch+".zip";
System.out.println("Sending tx-cache to "+url+" ("+Utilities.describeSize(bs.toByteArray().length)+")");
SimpleHTTPClient http = new SimpleHTTPClient();
http.setAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC);
http.setUsername(token.substring(0, token.indexOf(':')));
http.setPassword(token.substring(token.indexOf(':')+1));
HTTPResult res = http.put(url, "application/zip", bs.toByteArray(), null); // accept doesn't matter

View File

@ -157,6 +157,7 @@ public class TerminologyCacheManager {
String url = "https://tx.fhir.org/post/tx-cache/"+ghOrg+"/"+ghRepo+"/"+ghBranch+".zip";
System.out.println("Sending tx-cache to "+url+" ("+Utilities.describeSize(bs.toByteArray().length)+")");
SimpleHTTPClient http = new SimpleHTTPClient();
http.setAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC);
http.setUsername(token.substring(0, token.indexOf(':')));
http.setPassword(token.substring(token.indexOf(':')+1));
HTTPResult res = http.put(url, "application/zip", bs.toByteArray(), null); // accept doesn't matter

View File

@ -153,6 +153,13 @@
<version>3.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<!--
<dependency>

View File

@ -11,12 +11,20 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.utilities.settings.FhirSettings;
public class SimpleHTTPClient {
public class Header {
public enum AuthenticationMode {
NONE,
BASIC,
TOKEN
}
public class Header {
private String name;
private String value;
public Header(String name, String value) {
@ -85,34 +93,23 @@ public class SimpleHTTPClient {
}
private List<Header> headers = new ArrayList<>();
@Getter @Setter
private AuthenticationMode authenticationMode;
@Getter @Setter
private String username;
@Getter @Setter
private String password;
@Getter @Setter
private String token;
public void addHeader(String name, String value) {
headers.add(new Header(name, value));
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public HTTPResult get(String url) throws IOException {
return get(url, null);
}
@ -170,10 +167,20 @@ public class SimpleHTTPClient {
}
c.setConnectTimeout(15000);
c.setReadTimeout(15000);
if (username != null) {
setAuthenticationHeader(c);
}
private void setAuthenticationHeader(HttpURLConnection c) {
String authHeaderValue = null;
if (authenticationMode == AuthenticationMode.TOKEN) {
authHeaderValue = "Bearer " + new String(token);
} else if (authenticationMode == AuthenticationMode.BASIC) {
String auth = username+":"+password;
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8));
String authHeaderValue = "Basic " + new String(encodedAuth);
authHeaderValue = "Basic " + new String(encodedAuth);
}
if (authHeaderValue != null) {
c.setRequestProperty("Authorization", authHeaderValue);
}
}

View File

@ -76,6 +76,7 @@ public abstract class BasePackageCacheManager implements IPackageCacheManager {
if (version.endsWith(".x")) {
version = packageClient.getLatestVersion(id, version);
}
InputStream stream = packageClient.fetch(id, version);
String url = packageClient.url(id, version);
return new InputStreamWithSrc(stream, url, version);

View File

@ -14,7 +14,6 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
@ -22,7 +21,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.apache.commons.io.FileUtils;
@ -37,13 +35,13 @@ import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonElement;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager.FilesystemPackageCacheMode;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager.IPackageProvider;
import org.hl7.fhir.utilities.npm.NpmPackage.NpmPackageFolder;
import org.hl7.fhir.utilities.npm.PackageList.PackageListEntry;
import org.hl7.fhir.utilities.settings.FhirSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
@ -137,10 +135,11 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
this.cacheFolder = new File(customFolder);
init(FilesystemPackageCacheMode.CUSTOM);
}
public void init(FilesystemPackageCacheMode mode) throws IOException {
myPackageServers.addAll(PackageServer.publicServers());
protected void init(FilesystemPackageCacheMode mode) throws IOException {
initPackageServers();
switch (mode) {
case SYSTEM:
@ -173,6 +172,26 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
}
}
private void initPackageServers() {
myPackageServers.addAll(getConfiguredServers());
if (!isIgnoreDefaultPackageServers()) {
myPackageServers.addAll(getDefaultServers());
}
}
protected boolean isIgnoreDefaultPackageServers() {
return FhirSettings.isIgnoreDefaultPackageServers();
}
@Nonnull
protected List<PackageServer> getDefaultServers() {
return PackageServer.defaultServers();
}
protected List<PackageServer> getConfiguredServers() {
return PackageServer.getConfiguredServers();
}
public boolean isMinimalMemory() {
return minimalMemory;
}

View File

@ -4,12 +4,11 @@ import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.time.format.DateTimeParseException;
import java.util.*;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.SimpleHTTPClient;
@ -22,10 +21,11 @@ import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.model.JsonProperty;
import org.hl7.fhir.utilities.json.parser.JsonParser;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class PackageClient {
private PackageServer server;
private String address;
@ -47,26 +47,34 @@ public class PackageClient {
}
public InputStream fetch(String id, String ver) throws IOException {
return fetchCached(Utilities.pathURL(address, id, ver));
return fetchCached(getPackageTarballUrl(id, ver));
}
private String getPackageTarballUrl(String id, String ver) throws IOException {
if (server.getServerType() == PackageServer.PackageServerType.NPM) {
return getNpmServerTarballUrl(id, ver);
}
return Utilities.pathURL(address, id, ver);
}
private String getNpmServerTarballUrl(String id, String ver) throws IOException {
String packageDescriptorUrl = Utilities.pathURL(address, id, ver);
JsonObject json;
json = fetchJson(packageDescriptorUrl);
JsonObject dist = json.getJsonObject("dist");
return dist.getJsonString("tarball").asString();
}
public InputStream fetch(PackageInfo info) throws IOException {
return fetchCached(Utilities.pathURL(address, info.getId(), info.getVersion()));
}
public InputStream fetchNpm(String id, String ver) throws IOException {
return fetchCached(Utilities.pathURL(address, id, "-", id+"-"+ver+".tgz"));
return fetchCached(getPackageTarballUrl(info.getId(), info.getVersion()));
}
public InputStream fetchCached(String url) throws IOException, FileNotFoundException {
return fetchUrl(url, null);
}
protected String fn(String url) {
String[] p = url.split("\\/");
return p[2]+"-"+p[p.length-2]+"-"+p[p.length-1]+".tgz";
}
public List<PackageInfo> getVersions(String id) throws IOException {
String url = Utilities.pathURL(address, id);
List<PackageInfo> res = new ArrayList<>();
@ -78,7 +86,7 @@ public class PackageClient {
if (versions != null) {
for (JsonProperty v : versions.getProperties()) {
JsonObject obj = versions.getJsonObject(v.getName());
Instant d = obj.hasString("date") ? obj.asDate("date") : null;
Instant d = getInstantFromPackageDate(obj);
if (d == null) {
hasDates = false;
}
@ -101,7 +109,21 @@ public class PackageClient {
}
return res;
}
@Nullable
private static Instant getInstantFromPackageDate(JsonObject obj) {
try {
return obj.hasString("date") ? obj.asDate("date") : null;
} catch (DateTimeParseException e) {
//FIXME Some IGs use an older date format:
try {
return new SimpleDateFormat("yyyyMMddhhmmss").parse(obj.getJsonString("date").asString()).toInstant();
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
}
public List<PackageInfo> search(String name, String canonical, String fhirVersion, boolean preRelease) throws IOException {
CommaSeparatedStringBuilder params = new CommaSeparatedStringBuilder("&");
if (!Utilities.noString(name)) {
@ -148,12 +170,22 @@ public class PackageClient {
}
private InputStream fetchUrl(String source, String accept) throws IOException {
SimpleHTTPClient http = new SimpleHTTPClient();
SimpleHTTPClient http = getSimpleHTTPClient();
HTTPResult res = http.get(source, accept);
res.checkThrowException();
return new ByteArrayInputStream(res.getContent());
}
@Nonnull
private SimpleHTTPClient getSimpleHTTPClient() {
SimpleHTTPClient client = new SimpleHTTPClient();
client.setAuthenticationMode(server.getAuthenticationMode());
client.setUsername(server.getUsername());
client.setPassword(server.getPassword());
client.setToken(server.getToken());
return client;
}
private JsonObject fetchJson(String source) throws IOException {
String src = TextFile.streamToString(fetchUrl(source, "application/json"));
//System.out.println(src);

View File

@ -1,36 +1,48 @@
package org.hl7.fhir.utilities.npm;
import lombok.Getter;
import org.hl7.fhir.utilities.SimpleHTTPClient;
import org.hl7.fhir.utilities.settings.FhirSettings;
import org.hl7.fhir.utilities.settings.PackageServerPOJO;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class PackageServer {
public enum PackageServerAuthenticationMode {
NONE
public enum PackageServerType {
FHIR,
NPM
}
public PackageServer(String url) {
this.url = url;
mode = PackageServerAuthenticationMode.NONE;
authenticationMode = SimpleHTTPClient.AuthenticationMode.NONE;
serverType = PackageServerType.FHIR;
}
private String url;
private PackageServerAuthenticationMode mode;
@Getter
private SimpleHTTPClient.AuthenticationMode authenticationMode;
@Getter
private PackageServerType serverType;
@Getter
private String username;
@Getter
private String password;
@Getter
private String token;
public String getUrl() {
return url;
}
public PackageServerAuthenticationMode getMode() {
return mode;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public static final String PRIMARY_SERVER = "http://packages.fhir.org";
public static final String SECONDARY_SERVER = "https://packages2.fhir.org/packages";
@ -43,15 +55,79 @@ public class PackageServer {
return new PackageServer(SECONDARY_SERVER);
}
public static List<PackageServer> publicServers() {
public static List<PackageServer> defaultServers() {
List<PackageServer> servers = new ArrayList<>();
servers.add(primaryServer());
servers.add(secondaryServer());
return servers;
}
public static PackageServer getPackageServerFromPOJO(PackageServerPOJO pojo) {
return new PackageServer(pojo.getUrl())
.withAuthenticationMode(getModeFromPOJO(pojo))
.withServerType(
pojo.getServerType() != null && pojo.getServerType().equalsIgnoreCase("npm") ? PackageServerType.NPM : PackageServerType.FHIR
)
.withUsername(pojo.getUsername())
.withPassword(pojo.getPassword())
.withToken(pojo.getToken());
}
@Nullable
private static SimpleHTTPClient.AuthenticationMode getModeFromPOJO(PackageServerPOJO pojo) {
if (pojo.getAuthenticationType().equalsIgnoreCase("basic")) return SimpleHTTPClient.AuthenticationMode.BASIC;
if (pojo.getAuthenticationType().equalsIgnoreCase("token")) return SimpleHTTPClient.AuthenticationMode.TOKEN;
return null;
}
public static List<PackageServer> getConfiguredServers() {
return FhirSettings.getPackageServers().stream().map(
PackageServer::getPackageServerFromPOJO
).collect(Collectors.toList());
}
@Override
public String toString() {
return url;
}
public PackageServer copy() {
PackageServer packageServer = new PackageServer(url);
packageServer.authenticationMode = this.authenticationMode;
packageServer.serverType = this.serverType;
packageServer.username = this.username;
packageServer.password = this.password;
packageServer.token = this.token;
return packageServer;
}
public PackageServer withAuthenticationMode( SimpleHTTPClient.AuthenticationMode mode) {
PackageServer packageServer = this.copy();
packageServer.authenticationMode = mode;
return packageServer;
}
public PackageServer withServerType(PackageServerType serverType) {
PackageServer packageServer = this.copy();
packageServer.serverType = serverType;
return packageServer;
}
public PackageServer withPassword(String password) {
PackageServer packageServer = this.copy();
packageServer.password = password;
return packageServer;
}
public PackageServer withUsername(String username) {
PackageServer packageServer = this.copy();
packageServer.username = username;
return packageServer;
}
public PackageServer withToken(String token) {
PackageServer packageServer = this.copy();
packageServer.token = token;
return packageServer;
}
}

View File

@ -2,13 +2,14 @@ package org.hl7.fhir.utilities.settings;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import org.hl7.fhir.utilities.Utilities;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
public class FhirSettings {
@ -210,4 +211,19 @@ public class FhirSettings {
return Utilities.path(System.getProperty("user.home"), ".fhir", "fhir-settings.json");
}
public static boolean isIgnoreDefaultPackageServers() {
getInstance();
if (instance.fhirSettings.getPackageManagement() == null || instance.fhirSettings.getPackageManagement().getIgnoreDefaultServers() == null) {
return false;
}
return instance.fhirSettings.getPackageManagement().getIgnoreDefaultServers();
}
public static List<PackageServerPOJO> getPackageServers() {
getInstance();
if (instance.fhirSettings.getPackageManagement() == null) {
return Collections.emptyList();
}
return List.of(instance.fhirSettings.getPackageManagement().getServers().toArray(new PackageServerPOJO[]{}));
}
}

View File

@ -5,6 +5,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.extern.jackson.Jacksonized;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Data
@ -39,6 +41,8 @@ public class FhirSettingsPOJO {
private String txFhirDevelopment;
private String txFhirLocal;
private PackageManagementPOJO packageManagement;
protected FhirSettingsPOJO() {
apiKeys = null;
npmPath = null;
@ -50,5 +54,7 @@ public class FhirSettingsPOJO {
txFhirProduction = TX_SERVER_PROD;
txFhirDevelopment = TX_SERVER_DEV;
txFhirLocal = TX_SERVER_LOCAL;
packageManagement = null;
}
}

View File

@ -0,0 +1,25 @@
package org.hl7.fhir.utilities.settings;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.extern.jackson.Jacksonized;
import java.util.ArrayList;
import java.util.List;
@Data
@Builder
@Jacksonized
@AllArgsConstructor
public class PackageManagementPOJO {
private Boolean ignoreDefaultServers;
private List<PackageServerPOJO> servers;
protected PackageManagementPOJO() {
ignoreDefaultServers = false;
servers = new ArrayList<>();
}
}

View File

@ -0,0 +1,26 @@
package org.hl7.fhir.utilities.settings;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.extern.jackson.Jacksonized;
@Data
@Builder
@Jacksonized
@AllArgsConstructor
public class PackageServerPOJO {
String url;
String authenticationType;
String serverType;
String username;
String password;
String token;
}

View File

@ -0,0 +1,105 @@
package org.hl7.fhir.utilities.npm;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.utilities.SimpleHTTPClient;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class BasePackageCacheManagerTests {
@Test
public void testPackageBasicAuth() throws IOException {
BasePackageCacheManager basePackageCacheManager = getFakeBasePackageCacheManager();
MockPackageServer server = new MockPackageServer();
String packageServerUrl = server.getPackageServerUrl();
server.enqueueDummyPackageDescription();
server.enqueueDummyPackage();
PackageServer testServer = new PackageServer(packageServerUrl)
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC)
.withServerType(PackageServer.PackageServerType.NPM)
.withUsername(MockPackageServer.DUMMY_USERNAME)
.withPassword(MockPackageServer.DUMMY_PASSWORD);
basePackageCacheManager.addPackageServer(testServer);
basePackageCacheManager.myPackageServers.addAll(PackageServer.defaultServers());
BasePackageCacheManager.InputStreamWithSrc inputWithSrc = basePackageCacheManager.loadFromPackageServer("example.fhir.uv.myig", "0.2.0");
assertCorrectPackageContent(inputWithSrc);
server.shutdown();
}
@Test
@DisplayName("Test that package management moves to next server after 404")
public void testPackageWithConfiguredServer404() throws IOException {
BasePackageCacheManager basePackageCacheManager = getFakeBasePackageCacheManager();
MockPackageServer serverA = new MockPackageServer();
serverA.enqueueResponseCode(404);
MockPackageServer serverB = new MockPackageServer();
serverB.enqueueDummyPackageDescription();
serverB.enqueueDummyPackage();
String packageServerAUrl = serverA.getPackageServerUrl();
String packageServerBUrl = serverB.getPackageServerUrl();
PackageServer testServerA = new PackageServer(packageServerAUrl)
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC)
.withServerType(PackageServer.PackageServerType.NPM);
PackageServer testServerB = new PackageServer(packageServerBUrl)
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC)
.withServerType(PackageServer.PackageServerType.NPM);
basePackageCacheManager.addPackageServer(testServerA);
basePackageCacheManager.addPackageServer(testServerB);
basePackageCacheManager.myPackageServers.addAll(PackageServer.defaultServers());
BasePackageCacheManager.InputStreamWithSrc inputWithSrc = basePackageCacheManager.loadFromPackageServer("example.fhir.uv.myig", "0.2.0");
assertCorrectPackageContent(inputWithSrc);
serverA.shutdown();
serverB.shutdown();
}
private static void assertCorrectPackageContent(BasePackageCacheManager.InputStreamWithSrc inputWithSrc) throws IOException {
NpmPackage npmPackage = NpmPackage.fromPackage(inputWithSrc.stream, inputWithSrc.url, false);
assertEquals("Dummy IG For Testing", npmPackage.title())
;
assertEquals("Dummy IG description (built Thu, Jul 6, 2023 15:16-0400-04:00)", npmPackage.description());
}
@Nonnull
private static BasePackageCacheManager getFakeBasePackageCacheManager() {
return new BasePackageCacheManager() {
@Override
public NpmPackage loadPackageFromCacheOnly(String id, @Nullable String version) throws IOException {
return null;
}
@Override
public NpmPackage addPackageToCache(String id, String version, InputStream packageTgzInputStream, String sourceDesc) throws IOException {
return null;
}
@Override
public NpmPackage loadPackage(String id, String version) throws FHIRException, IOException {
return null;
}
};
}
}

View File

@ -0,0 +1,66 @@
package org.hl7.fhir.utilities.npm;
import org.junit.jupiter.api.Test;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FilesystemPackageManagerTests {
private static final String DUMMY_URL_1 = "http://dummy1.org";
private static final String DUMMY_URL_2 = "http://dummy2.org";
private static final String DUMMY_URL_3 = "http://dummy3.org";
private static final String DUMMY_URL_4 = "http://dummy4.org";
private List<PackageServer> dummyPrivateServers = List.of(
new PackageServer(DUMMY_URL_1),
new PackageServer(DUMMY_URL_2)
);
private List<PackageServer> dummyDefaultServers = List.of(
new PackageServer(DUMMY_URL_3),
new PackageServer(DUMMY_URL_4)
);
@Test
public void testDefaultServers() throws IOException {
FilesystemPackageCacheManager filesystemPackageCacheManager = getFilesystemPackageCacheManager(false);
assertEquals(4, filesystemPackageCacheManager.myPackageServers.size());
assertEquals(DUMMY_URL_1, filesystemPackageCacheManager.myPackageServers.get(0).getUrl());
assertEquals(DUMMY_URL_2, filesystemPackageCacheManager.myPackageServers.get(1).getUrl());
assertEquals(DUMMY_URL_3, filesystemPackageCacheManager.myPackageServers.get(2).getUrl());
assertEquals(DUMMY_URL_4, filesystemPackageCacheManager.myPackageServers.get(3).getUrl());
}
@Test
public void testIgnoreDefaultServers() throws IOException {
FilesystemPackageCacheManager filesystemPackageCacheManager = getFilesystemPackageCacheManager(true);
assertEquals(2, filesystemPackageCacheManager.myPackageServers.size());
assertEquals(DUMMY_URL_1, filesystemPackageCacheManager.myPackageServers.get(0).getUrl());
assertEquals(DUMMY_URL_2, filesystemPackageCacheManager.myPackageServers.get(1).getUrl());
}
@Nonnull
private FilesystemPackageCacheManager getFilesystemPackageCacheManager(final boolean ignoreDefaultPackageServers) throws IOException {
return new FilesystemPackageCacheManager(FilesystemPackageCacheManager.FilesystemPackageCacheMode.TESTING) {
protected boolean isIgnoreDefaultPackageServers() {
return ignoreDefaultPackageServers;
}
@Nonnull
protected List<PackageServer> getDefaultServers() {
return dummyDefaultServers;
}
protected List<PackageServer> getConfiguredServers() {
return dummyPrivateServers;
}
};
}
}

View File

@ -0,0 +1,63 @@
package org.hl7.fhir.utilities.npm;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okio.Buffer;
import java.io.IOException;
public class MockPackageServer {
public static final String DUMMY_PACKAGE_NAME = "example.fhir.uv.myig";
public static final String DUMMY_PACKAGE_VERSION = "0.2.0";
public static final String DUMMY_USERNAME = "alfred";
public static final String DUMMY_PASSWORD = "numan";
public static final String DUMMY_TOKEN = "dummyTokenValue";
MockWebServer server = new MockWebServer();
HttpUrl httpUrl;
public MockPackageServer() throws IOException {
server = new MockWebServer();
server.start();
httpUrl = server.url("/");
}
public MockWebServer getMockWebServer() {
return server;
}
public String getPackageServerUrl() {
return "http://" + httpUrl.host() + ":" + httpUrl.port();
}
public void shutdown() throws IOException {
server.shutdown();
}
public void enqueueDummyPackageDescription() {
server.enqueue(new MockResponse().setBody("{" +
"\"dist\": { \"tarball\": \"" + getTarballUrl() + "\"}"+
"}"));
}
public String getTarballUrl() {
return getPackageServerUrl() + "/tarballUrl";
}
public void enqueueDummyPackage() throws IOException {
server.enqueue(new MockResponse().setBody(getDummyPackageAsBuffer()));
}
public void enqueueResponseCode(int code) throws IOException {
server.enqueue(new MockResponse().setResponseCode(code));
}
private Buffer getDummyPackageAsBuffer() throws IOException {
byte[] fileData = this.getClass().getResourceAsStream("/npm/dummy-package.tgz").readAllBytes();
Buffer buf = new Buffer();
buf.write(fileData);
return buf;
}
}

View File

@ -0,0 +1,186 @@
package org.hl7.fhir.utilities.npm;
import okhttp3.mockwebserver.RecordedRequest;
import okio.Buffer;
import org.hl7.fhir.utilities.SimpleHTTPClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class PackageServerTest {
MockPackageServer server;
@BeforeEach
public void beforeEach() throws IOException {
server = new MockPackageServer();
}
@AfterEach
public void afterEach() throws IOException {
server.shutdown();
}
@Test
public void testPackageServerBasicAuth() throws IOException, InterruptedException {
String packageServerUrl = server.getPackageServerUrl();
server.enqueueDummyPackageDescription();
server.enqueueDummyPackage();
PackageServer testServer = new PackageServer(packageServerUrl)
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.BASIC)
.withServerType(PackageServer.PackageServerType.NPM)
.withUsername(MockPackageServer.DUMMY_USERNAME)
.withPassword(MockPackageServer.DUMMY_PASSWORD);
PackageClient packageClient = new PackageClient(testServer);
InputStream inputStream = packageClient.fetch(MockPackageServer.DUMMY_PACKAGE_NAME, MockPackageServer.DUMMY_PACKAGE_VERSION);
RecordedRequest packageRequest = server.getMockWebServer().takeRequest();
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
assertBasicAuthorization(packageRequest);
RecordedRequest tarballRequest = server.getMockWebServer().takeRequest();
assertEquals(server.getTarballUrl(), tarballRequest.getRequestUrl().toString());
assertBasicAuthorization(tarballRequest);
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
NpmPackage npmPackage = NpmPackage.fromPackage(inputStream);
assertDummyPackageContent(npmPackage);
}
@Test
public void testPackageServerTokenAuth() throws IOException, InterruptedException {
String packageServerUrl = server.getPackageServerUrl();
server.enqueueDummyPackageDescription();
server.enqueueDummyPackage();
PackageServer testServer = new PackageServer(packageServerUrl)
.withAuthenticationMode(SimpleHTTPClient.AuthenticationMode.TOKEN)
.withServerType(PackageServer.PackageServerType.NPM)
.withToken(MockPackageServer.DUMMY_TOKEN);
PackageClient packageClient = new PackageClient(testServer);
InputStream inputStream = packageClient.fetch(MockPackageServer.DUMMY_PACKAGE_NAME, MockPackageServer.DUMMY_PACKAGE_VERSION);
RecordedRequest packageRequest = server.getMockWebServer().takeRequest();
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
assertTokenAuthorization(packageRequest);
RecordedRequest tarballRequest = server.getMockWebServer().takeRequest();
assertEquals(server.getTarballUrl(), tarballRequest.getRequestUrl().toString());
assertTokenAuthorization(tarballRequest);
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
NpmPackage npmPackage = NpmPackage.fromPackage(inputStream);
assertDummyPackageContent(npmPackage);
}
@Test
public void testPackageNpmServer() throws IOException, InterruptedException {
String packageServerUrl = server.getPackageServerUrl();
server.enqueueDummyPackageDescription();
server.enqueueDummyPackage();
PackageServer testServer = new PackageServer(packageServerUrl)
.withServerType(PackageServer.PackageServerType.NPM);
PackageClient packageClient = new PackageClient(testServer);
InputStream inputStream = packageClient.fetch(MockPackageServer.DUMMY_PACKAGE_NAME, MockPackageServer.DUMMY_PACKAGE_VERSION);
RecordedRequest packageRequest = server.getMockWebServer().takeRequest();
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
assertNull(packageRequest.getHeader("Authorization"));
RecordedRequest tarballRequest = server.getMockWebServer().takeRequest();
assertEquals(server.getTarballUrl(), tarballRequest.getRequestUrl().toString());
assertNull(tarballRequest.getHeader("Authorization"));
assertEquals(packageServerUrl + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, packageRequest.getRequestUrl().toString());
NpmPackage npmPackage = NpmPackage.fromPackage(inputStream);
assertDummyPackageContent(npmPackage);
}
@Test
public void testPackageFhirServer() throws IOException, InterruptedException {
String packageServerUrl = server.getPackageServerUrl();
server.enqueueDummyPackage();
PackageServer testServer = new PackageServer(packageServerUrl);
PackageClient packageClient = new PackageClient(testServer);
InputStream inputStream = packageClient.fetch(MockPackageServer.DUMMY_PACKAGE_NAME, MockPackageServer.DUMMY_PACKAGE_VERSION);
RecordedRequest tarballRequest = server.getMockWebServer().takeRequest();
assertEquals(server.getPackageServerUrl() + "/" + MockPackageServer.DUMMY_PACKAGE_NAME + "/" + MockPackageServer.DUMMY_PACKAGE_VERSION, tarballRequest.getRequestUrl().toString());
assertNull(tarballRequest.getHeader("Authorization"));
NpmPackage npmPackage = NpmPackage.fromPackage(inputStream);
assertDummyPackageContent(npmPackage);
}
private static void assertBasicAuthorization(RecordedRequest packageRequest) {
String authorizationHeader = packageRequest.getHeader("Authorization");
assertThat(authorizationHeader, startsWith("Basic"));
byte[] data = Base64.getDecoder().decode(authorizationHeader.substring(6));
String authorizationValue = new String(data, StandardCharsets.UTF_8);
String[] authorizationColumns = authorizationValue.split(":");
assertEquals(MockPackageServer.DUMMY_USERNAME, authorizationColumns[0]);
assertEquals(MockPackageServer.DUMMY_PASSWORD, authorizationColumns[1]);
}
private static void assertTokenAuthorization(RecordedRequest packageRequest) {
String authorizationHeader = packageRequest.getHeader("Authorization");
assertThat(authorizationHeader, startsWith("Bearer"));
String token = authorizationHeader.substring(7);
assertEquals(MockPackageServer.DUMMY_TOKEN, token);
}
private void assertDummyPackageContent(NpmPackage npmPackage) throws IOException {
assertEquals("Dummy IG For Testing", npmPackage.title())
;
assertEquals("Dummy IG description (built Thu, Jul 6, 2023 15:16-0400-04:00)", npmPackage.description());
}
public Buffer getDummyPackageAsBuffer() throws IOException {
byte[] fileData = this.getClass().getResourceAsStream("/npm/dummy-package.tgz").readAllBytes();
Buffer buf = new Buffer();
buf.write(fileData);
return buf;
}
}

View File

@ -10,6 +10,7 @@ import org.junit.jupiter.api.parallel.Isolated;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -83,5 +84,20 @@ public class FhirSettingsTests implements ResourceLoaderTests {
assertEquals("dummy-diff-tool-path", fhirSettings.getDiffToolPath());
assertEquals("dummy-temp-path", fhirSettings.getTempPath());
assertEquals("dummy-test-igs-path", fhirSettings.getTestIgsPath());
assertTrue(fhirSettings.getPackageManagement().getIgnoreDefaultServers());
List<PackageServerPOJO> packageServers = fhirSettings.getPackageManagement().getServers();
assertEquals(2, packageServers.size());
assertEquals("http://dummy.org", packageServers.get(0).url);
assertEquals("npm", packageServers.get(0).serverType);
assertEquals("joe", packageServers.get(0).username);
assertEquals("swordfish", packageServers.get(0).password);
assertEquals("BASIC", packageServers.get(0).authenticationType);
assertEquals("http://dummy2.com", packageServers.get(1).url);
}
}

View File

@ -9,6 +9,22 @@
"tempPath": "dummy-temp-path",
"testIgsPath": "dummy-test-igs-path",
"unusedField" : "unused",
"packageManagement" : {
"ignoreDefaultServers" : true,
"servers": [
{
"url": "http://dummy.org",
"serverType": "npm",
"authenticationType": "BASIC",
"username": "joe",
"password": "swordfish"
},
{
"url": "http://dummy2.com",
"goobledy-goo": 5
}
]
},
"unusedData" : {
"unusedDateField" : "unused-data"
}