Skip to content

Commit

Permalink
Merge branch 'new-fpl' into new-fpl-db-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mint-thompson committed Aug 22, 2024
2 parents 83d23a7 + 993e036 commit 9755b34
Show file tree
Hide file tree
Showing 10 changed files with 1,252 additions and 52 deletions.
17 changes: 13 additions & 4 deletions src/cache/DiskBasedPackageCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,20 @@ export class DiskBasedPackageCache implements PackageCache {
let resource = this.lruCache.get(resourcePath);
if (!resource) {
if (/.xml$/i.test(resourcePath)) {
// TODO: Consider error handling
const xml = fs.readFileSync(resourcePath).toString();
resource = this.fhirConverter.xmlToObj(xml);
try {
const xml = fs.readFileSync(resourcePath).toString();
resource = this.fhirConverter.xmlToObj(xml);
} catch {
throw new Error(`Failed to get XML resource at path ${resourcePath}`);
}
} else if (/.json$/i.test(resourcePath)) {
try {
resource = fs.readJSONSync(resourcePath);
} catch {
throw new Error(`Failed to get JSON resource at path ${resourcePath}`);
}
} else {
resource = fs.readJSONSync(resourcePath);
throw new Error(`Failed to find XML or JSON file at path ${resourcePath}`);
}
this.lruCache.set(resourcePath, resource);
}
Expand Down
46 changes: 4 additions & 42 deletions src/registry/FHIRRegistryClient.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Readable } from 'stream';
import { maxSatisfying } from 'semver';
import { LogFunction, axiosGet } from '../utils';
import { RegistryClient, RegistryClientOptions } from './RegistryClient';
import { IncorrectWildcardVersionFormatError, LatestVersionUnavailableError } from '../errors';
import { IncorrectWildcardVersionFormatError } from '../errors';
import { lookUpLatestVersion, lookUpLatestPatchVersion } from './utils';

export class FHIRRegistryClient implements RegistryClient {
public endpoint: string;
Expand All @@ -17,9 +17,9 @@ export class FHIRRegistryClient implements RegistryClient {
async download(name: string, version: string): Promise<Readable> {
// Resolve version if necessary
if (version === 'latest') {
version = await this.lookUpLatestVersion(name);
version = await lookUpLatestVersion(this.endpoint, name);
} else if (/^\d+\.\d+\.x$/.test(version)) {
version = await this.lookUpLatestPatchVersion(name, version);
version = await lookUpLatestPatchVersion(this.endpoint, name, version);
} else if (/^\d+\.x$/.test(version)) {
throw new IncorrectWildcardVersionFormatError(name, version);
}
Expand All @@ -34,42 +34,4 @@ export class FHIRRegistryClient implements RegistryClient {
}
throw new Error(`Failed to download ${name}#${version} from ${url}`);
}

private async lookUpLatestVersion(name: string): Promise<string> {
try {
const res = await axiosGet(`${this.endpoint}/${name}`, {
responseType: 'json'
});
if (res?.data?.['dist-tags']?.latest?.length) {
return res.data['dist-tags'].latest;
} else {
throw new LatestVersionUnavailableError(name);
}
} catch {
throw new LatestVersionUnavailableError(name);
}
}

private async lookUpLatestPatchVersion(name: string, version: string): Promise<string> {
if (!/^\d+\.\d+\.x$/.test(version)) {
throw new IncorrectWildcardVersionFormatError(name, version);
}
try {
const res = await axiosGet(`${this.endpoint}/${name}`, {
responseType: 'json'
});
if (res?.data?.versions) {
const versions = Object.keys(res.data.versions);
const latest = maxSatisfying(versions, version);
if (latest == null) {
throw new LatestVersionUnavailableError(name, null, true);
}
return latest;
} else {
throw new LatestVersionUnavailableError(name, null, true);
}
} catch {
throw new LatestVersionUnavailableError(name, null, true);
}
}
}
22 changes: 19 additions & 3 deletions src/registry/NPMRegistryClient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Readable } from 'stream';
import { LogFunction, axiosGet } from '../utils';
import { RegistryClient, RegistryClientOptions } from './RegistryClient';
import { IncorrectWildcardVersionFormatError } from '../errors';
import { lookUpLatestVersion, lookUpLatestPatchVersion } from './utils';

export class NPMRegistryClient implements RegistryClient {
public endpoint: string;
Expand All @@ -13,10 +15,24 @@ export class NPMRegistryClient implements RegistryClient {
}

async download(name: string, version: string): Promise<Readable> {
// Resolve version if necessary
if (version === 'latest') {
version = await lookUpLatestVersion(this.endpoint, name);
} else if (/^\d+\.\d+\.x$/.test(version)) {
version = await lookUpLatestPatchVersion(this.endpoint, name, version);
} else if (/^\d+\.x$/.test(version)) {
throw new IncorrectWildcardVersionFormatError(name, version);
}

// Get the manifest information about the package from the registry
const manifestRes = await axiosGet(`${this.endpoint}/${name}`);
// Find the NPM tarball location in the manifest
let url = manifestRes.data?.versions?.[version]?.dist?.tarball;
let url;
try {
const manifestRes = await axiosGet(`${this.endpoint}/${name}`);
// Find the NPM tarball location in the manifest
url = manifestRes.data?.versions?.[version]?.dist?.tarball;
} catch {
// Do nothing. Undefined url handled below.
}
// If tarball URL is not found, fallback to standard NPM approach per
// https://docs.fire.ly/projects/Simplifier/features/api.html#package-server-api
if (!url) {
Expand Down
2 changes: 1 addition & 1 deletion src/registry/RedundantRegistryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { RegistryClient, RegistryClientOptions } from './RegistryClient';
export class RedundantRegistryClient implements RegistryClient {
protected log: LogFunction;
constructor(
private clients: RegistryClient[],
public clients: RegistryClient[],
options: RegistryClientOptions = {}
) {
this.log = options.log ?? (() => {});
Expand Down
45 changes: 45 additions & 0 deletions src/registry/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { IncorrectWildcardVersionFormatError, LatestVersionUnavailableError } from '../errors';
import { axiosGet } from '../utils';
import { maxSatisfying } from 'semver';

export async function lookUpLatestVersion(endpoint: string, name: string): Promise<string> {
try {
const res = await axiosGet(`${endpoint}/${name}`, {
responseType: 'json'
});
if (res?.data?.['dist-tags']?.latest?.length) {
return res.data['dist-tags'].latest;
} else {
throw new LatestVersionUnavailableError(name);
}
} catch {
throw new LatestVersionUnavailableError(name);
}
}

export async function lookUpLatestPatchVersion(
endpoint: string,
name: string,
version: string
): Promise<string> {
if (!/^\d+\.\d+\.x$/.test(version)) {
throw new IncorrectWildcardVersionFormatError(name, version);
}
try {
const res = await axiosGet(`${endpoint}/${name}`, {
responseType: 'json'
});
if (res?.data?.versions) {
const versions = Object.keys(res.data.versions);
const latest = maxSatisfying(versions, version);
if (latest == null) {
throw new LatestVersionUnavailableError(name, null, true);
}
return latest;
} else {
throw new LatestVersionUnavailableError(name, null, true);
}
} catch {
throw new LatestVersionUnavailableError(name, null, true);
}
}
48 changes: 46 additions & 2 deletions test/cache/DiskBasedPackageCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ describe('DiskBasedPackageCache', () => {
expect(loggerSpy.getAllLogs()).toHaveLength(0);
});

it('should return undefined for a package with different version in the cache', () => {
it('should return empty array for a package with different version in the cache', () => {
const potentials = cache.getPotentialResourcePaths('fhir.small', '0.2.0');
expect(potentials).toHaveLength(0);
expect(loggerSpy.getAllLogs()).toHaveLength(0);
Expand Down Expand Up @@ -231,6 +231,50 @@ describe('DiskBasedPackageCache', () => {
});

describe('#getResourceAtPath', () => {
// tests go here
it('should return a resource with a given resource path', () => {
const rootPath = path.resolve(cacheFolder, 'fhir.small#0.1.0', 'package');
const totalPath = path.resolve(rootPath, 'StructureDefinition-MyPatient.json');
const resource = cache.getResourceAtPath(totalPath);
expect(resource).toBeDefined();
expect(resource.id).toBe('MyPatient');
expect(loggerSpy.getAllLogs('error')).toHaveLength(0);
});

it('should return a resource with an xml path where xml was converted to a resource', () => {
const totalPath = path.resolve(local1Folder, 'StructureDefinition-true-false.xml');
const resource = cache.getResourceAtPath(totalPath);
expect(resource).toBeDefined();
expect(resource.id).toBe('true-false');
expect(resource.xml).toBeUndefined();
expect(loggerSpy.getAllLogs('error')).toHaveLength(0);
});

it('should throw error when path points to a xml file that does not exist', () => {
const totalPath = path.resolve(local1Folder, 'example-file-that-doesnt-exist.xml');
expect(() => {
cache.getResourceAtPath(totalPath);
}).toThrow(/Failed to get XML resource at path/);
});

it('should throw error when path points to a json file that does not exist', () => {
const totalPath = path.resolve(local1Folder, 'example-file-that-doesnt-exist.json');
expect(() => {
cache.getResourceAtPath(totalPath);
}).toThrow(/Failed to get JSON resource at path/);
});

it('should throw error when path points to an invalid file type that is not json or xml', () => {
const totalPath = path.resolve(local1Folder, 'example-file-that-doesnt-exist.txt');
expect(() => {
cache.getResourceAtPath(totalPath);
}).toThrow(/Failed to find XML or JSON file/);
});

it('should throw error when path points to a file that does not exist', () => {
const totalPath = path.resolve(local1Folder, '');
expect(() => {
cache.getResourceAtPath(totalPath);
}).toThrow(/Failed to find XML or JSON file/);
});
});
});
39 changes: 39 additions & 0 deletions test/registry/DefaultRegistryClient.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { loggerSpy } from '../testhelpers';
import { DefaultRegistryClient } from '../../src/registry/DefaultRegistryClient';
import { NPMRegistryClient } from '../../src/registry/NPMRegistryClient';
import { FHIRRegistryClient } from '../../src/registry/FHIRRegistryClient';

describe('DefaultRegistryClient', () => {
describe('#constructor', () => {
beforeEach(() => {
loggerSpy.reset();
delete process.env.FPL_REGISTRY;
});

it('should make a client with custom registry when it has been specified', () => {
process.env.FPL_REGISTRY = 'https://custom-registry.example.org';
const defaultClient = new DefaultRegistryClient({ log: loggerSpy.log });
expect(defaultClient.clients).toHaveLength(1);
expect(defaultClient.clients[0]).toHaveProperty(
'endpoint',
'https://custom-registry.example.org'
);
expect(defaultClient.clients[0]).toBeInstanceOf(NPMRegistryClient);
expect(loggerSpy.getLastMessage('info')).toBe(
'Using custom registry specified by FPL_REGISTRY environment variable: https://custom-registry.example.org'
);
});

it('should make a client with fhir registries if no custom registry specified', () => {
const defaultClient = new DefaultRegistryClient({ log: loggerSpy.log });
expect(defaultClient.clients).toHaveLength(2);
expect(defaultClient.clients[0]).toHaveProperty('endpoint', 'https://packages.fhir.org');
expect(defaultClient.clients[0]).toBeInstanceOf(FHIRRegistryClient);
expect(defaultClient.clients[1]).toHaveProperty(
'endpoint',
'https://packages2.fhir.org/packages'
);
expect(defaultClient.clients[1]).toBeInstanceOf(FHIRRegistryClient);
});
});
});
Loading

0 comments on commit 9755b34

Please sign in to comment.