Skip to content

Commit

Permalink
improvement: update to es6 syntax
Browse files Browse the repository at this point in the history
Use es6 sintax

BREAKING CHANGE: Must use es6 syntax to import this module
  • Loading branch information
lucaju committed Mar 15, 2020
1 parent 290ca64 commit 7ee03d7
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 100 deletions.
149 changes: 74 additions & 75 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,95 +10,94 @@
const gettyULAN = 'ulan';
const gettyTGN = 'tgn';

function fetchWithTimeout(url, config = {}, timeout = 30000) {

return new Promise((resolve, reject) => {
// the reject on the promise in the timeout callback won't have any effect, *unless*
// the timeout is triggered before the fetch resolves, in which case the setTimeout rejects
// the whole outer Promise, and the promise from the fetch is dropped entirely.
setTimeout(() => reject(new Error('Call to Getty timed out')), timeout);
fetch(url, config).then(resolve, reject);
}).then(
response=>{
// check for ok status
if (response.ok) {
return response.json()
}
// if status not ok, through an error
throw new Error(`Something wrong with the call to Getty, possibly a problem with the network or the server. HTTP error: ${response.status}`);
}/*,
// instead of handling and rethrowing the error here, we just let it bubble through
error => {
// we could instead handle a reject from either of the fetch or setTimeout promises,
// whichever first rejects, do some loggingor something, and then throw a new rejection.
console.log(error)
return Promise.reject(new Error(`some error jjk: ${error}`))
}*/
)
const fetchWithTimeout = async (url, config = {}, time = 30000) => {

/*
the reject on the promise in the timeout callback won't have any effect, *unless*
the timeout is triggered before the fetch resolves, in which case the setTimeout rejects
the whole outer Promise, and the promise from the fetch is dropped entirely.
*/

// Create a promise that rejects in <time> milliseconds
const timeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
clearTimeout(id);
reject('Call to Getty timed out')
}, time)
})

// Returns a race between our timeout and the passed in promise
return Promise.race([
fetch(url, config),
timeout
])

}

// note that this method is exposed on the npm module to simplify testing,
// i.e., to allow intercepting the HTTP call during testing, using sinon or similar.
function getEntitySourceURI(queryString, gettyVocab) {
const getEntitySourceURI = (queryString, gettyVocab) => {

// Calls a cwrc proxy (https://lookup.services.cwrc.ca/getty), so that we can make https calls from the browser.
// The proxy in turn then calls http://vocab.getty.edu
// The getty lookup doesn't seem to have an https endpoint
return `https://lookup.services.cwrc.ca/getty/sparql.json?query=` + encodeURIComponent(`select ?Subject ?Term ?Parents ?Descr ?ScopeNote ?Type (coalesce(?Type1,?Type2) as ?ExtraType) {
?Subject luc:term "${queryString}"; a ?typ; skos:inScheme ${gettyVocab}:.
?typ rdfs:subClassOf gvp:Subject; rdfs:label ?Type.
filter (?typ != gvp:Subject)
optional {?Subject gvp:placeTypePreferred [gvp:prefLabelGVP [xl:literalForm ?Type1]]}
optional {?Subject gvp:agentTypePreferred [gvp:prefLabelGVP [xl:literalForm ?Type2]]}
optional {?Subject gvp:prefLabelGVP [xl:literalForm ?Term]}
optional {?Subject gvp:parentStringAbbrev ?Parents}
optional {?Subject foaf:focus/gvp:biographyPreferred/schema:description ?Descr}
optional {?Subject skos:scopeNote [dct:language gvp_lang:en; rdf:value ?ScopeNote]}}
LIMIT 5`);
return 'https://lookup.services.cwrc.ca/getty/sparql.json?query=' + encodeURIComponent(`select ?Subject ?Term ?Parents ?Descr ?ScopeNote ?Type (coalesce(?Type1,?Type2) as ?ExtraType) {
?Subject luc:term "${queryString}"; a ?typ; skos:inScheme ${gettyVocab}:.
?typ rdfs:subClassOf gvp:Subject; rdfs:label ?Type.
filter (?typ != gvp:Subject)
optional {?Subject gvp:placeTypePreferred [gvp:prefLabelGVP [xl:literalForm ?Type1]]}
optional {?Subject gvp:agentTypePreferred [gvp:prefLabelGVP [xl:literalForm ?Type2]]}
optional {?Subject gvp:prefLabelGVP [xl:literalForm ?Term]}
optional {?Subject gvp:parentStringAbbrev ?Parents}
optional {?Subject foaf:focus/gvp:biographyPreferred/schema:description ?Descr}
optional {?Subject skos:scopeNote [dct:language gvp_lang:en; rdf:value ?ScopeNote]}}
LIMIT 5`);
}

function getPersonLookupURI(queryString) {
return getEntitySourceURI(queryString, gettyULAN)
}
const getPersonLookupURI = (queryString) => getEntitySourceURI(queryString, gettyULAN);

function getPlaceLookupURI(queryString) {
return getEntitySourceURI(queryString, gettyTGN)
}
const getPlaceLookupURI = (queryString) => getEntitySourceURI(queryString, gettyTGN);

function callGetty(url, queryString, gettyVocab) {

return fetchWithTimeout(url).then((parsedJSON) => {
return parsedJSON.results.bindings.map(
({
Subject: {value: uri},
Term: {value: name},
Descr: {value: description = 'No description available'} = "No description available"
}) => {
return {
nameType: gettyVocab,
id: uri,
uri,
uriForDisplay: uri.replace('http://vocab.getty.edu', 'https://getty.lookup.services.cwrc.ca'),
name,
repository: 'getty',
originalQueryString: queryString,
description
}
})
})
}
const callGetty = async (url, queryString, gettyVocab) => {

function findPerson(queryString) {
return callGetty(getPersonLookupURI(queryString), queryString, gettyULAN)
}
let response = await fetchWithTimeout(url)
.catch((error) => {
return error;
})

//if status not ok, through an error
if (!response.ok) throw new Error(`Something wrong with the call to Getty, possibly a problem with the network or the server. HTTP error: ${response.status}`)

response = await response.json()

function findPlace(queryString) {
return callGetty(getPlaceLookupURI(queryString), queryString, gettyTGN)
const mapResponse = response.results.bindings.map(
({
Subject: {value: uri},
Term: {value: name},
Descr: {value: description = 'No description available'} = 'No description available'
}) => {
return {
nameType: gettyVocab,
id: uri,
uri,
uriForDisplay: uri.replace('http://vocab.getty.edu', 'https://getty.lookup.services.cwrc.ca'),
name,
repository: 'getty',
originalQueryString: queryString,
description
}
})

return mapResponse;
}

module.exports = {
findPerson: findPerson,
findPlace: findPlace,
getPersonLookupURI: getPersonLookupURI,
getPlaceLookupURI: getPlaceLookupURI
const findPerson = (queryString) => callGetty(getPersonLookupURI(queryString), queryString, gettyULAN);

const findPlace = (queryString) => callGetty(getPlaceLookupURI(queryString), queryString, gettyTGN)

export default {
findPerson,
findPlace,
getPersonLookupURI,
getPlaceLookupURI
}
63 changes: 38 additions & 25 deletions test/browser.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
'use strict';

let getty = require('../src/index.js');
const fetchMock = require('fetch-mock');
import fetchMock from 'fetch-mock';
import getty from '../src/index.js';

const emptyResultFixture = JSON.stringify(require('./httpResponseMocks/noResults.json'));
const resultsFixture = JSON.stringify(require('./httpResponseMocks/results.json'));
const noDescResultsFixture = JSON.stringify(require('./httpResponseMocks/resultsWitoutDescription.json'));

const queryString = 'jones';
const queryStringWithNoResults = 'ldfjk';
const queryStringForTimeout = "chartrand";
const queryStringForError = "cuff";
const queryStringForTimeout = 'chartrand';
const queryStringForError = 'cuff';
const queryStringForMissingDescriptionInGettyResult = 'blash';
const expectedResultLength = 5;
const emptyResultFixture = JSON.stringify(require('./httpResponseMocks/noResults.json'));
const resultsFixture = JSON.stringify(require('./httpResponseMocks/results.json'));
const noDescResultsFixture = JSON.stringify(require('./httpResponseMocks/resultsWitoutDescription.json'));

jest.useFakeTimers();

Expand All @@ -21,19 +22,19 @@ jest.useFakeTimers();
{ uriBuilderFn: 'getPlaceLookupURI', testFixture: resultsFixture }
].forEach(entityLookup => {

let uriBuilderFn = getty[entityLookup.uriBuilderFn];
const uriBuilderFn = getty[entityLookup.uriBuilderFn];

fetchMock.get(uriBuilderFn(queryString), entityLookup.testFixture);
fetchMock.get(uriBuilderFn(queryStringWithNoResults), emptyResultFixture);
fetchMock.get(uriBuilderFn(queryStringForTimeout), (url, opts) => {
fetchMock.get(uriBuilderFn(queryStringForTimeout), () => {
setTimeout(Promise.resolve, 8100);
});
fetchMock.get(uriBuilderFn(queryStringForError), 500);
fetchMock.get(uriBuilderFn(queryStringForMissingDescriptionInGettyResult), noDescResultsFixture)
})

// from https://stackoverflow.com/a/35047888
function doObjectsHaveSameKeys(...objects) {
const doObjectsHaveSameKeys = (...objects) => {
const allKeys = objects.reduce((keys, object) => keys.concat(Object.keys(object)), []);
const union = new Set(allKeys);
return objects.every(object => union.size === Object.keys(object).length);
Expand All @@ -48,10 +49,9 @@ test('lookup builders', () => {

['findPerson', 'findPlace'].forEach((nameOfLookupFn) => {
test(nameOfLookupFn, async () => {
expect.assertions(21);
let lookupFn = getty[nameOfLookupFn];
expect(typeof lookupFn).toBe('function');
let results = await lookupFn(queryString);
expect.assertions(12);

const results = await getty[nameOfLookupFn](queryString);
expect(Array.isArray(results)).toBe(true);
expect(results.length).toBeLessThanOrEqual(expectedResultLength);
results.forEach(singleResult => {
Expand All @@ -67,9 +67,13 @@ test('lookup builders', () => {
})).toBe(true);
expect(singleResult.originalQueryString).toBe(queryString);
})
})

test(`${nameOfLookupFn} - no Description`, async () => {
// with a result from getty with no Description
results = await lookupFn(queryStringForMissingDescriptionInGettyResult);
expect.assertions(3);

const results = await getty[nameOfLookupFn](queryStringForMissingDescriptionInGettyResult);
expect(Array.isArray(results)).toBe(true);
expect(doObjectsHaveSameKeys(results[0], {
nameType: '',
Expand All @@ -82,28 +86,37 @@ test('lookup builders', () => {
description: ''
})).toBe(true);
expect(results[0].description).toBe('No description available');
})

test(`${nameOfLookupFn} - no results`, async () => {
// with no results
expect.assertions(2);

// with no results
results = await lookupFn(queryStringWithNoResults);
const results = await await getty[nameOfLookupFn](queryStringWithNoResults);
expect(Array.isArray(results)).toBe(true);
expect(results.length).toBe(0);
})

test(`${nameOfLookupFn} - server error`, async () => {
// with a server error
expect.assertions(2);

let shouldBeNullResult = false;
shouldBeNullResult = await lookupFn(queryStringForError).catch(error => {
shouldBeNullResult = await getty[nameOfLookupFn](queryStringForError).catch( () => {
// an http error should reject the promise
expect(true).toBe(true);
return false;
})
// a falsey result should be returned
expect(shouldBeNullResult).toBeFalsy();
})

// when query times out
try {
await lookupFn(queryStringForTimeout);
} catch (err) {
// the promise should be rejected
expect(true).toBe(true);
}
test(`${nameOfLookupFn} - times out`, async () => {
// when query times out
expect.assertions(1);
await getty[nameOfLookupFn](queryStringForTimeout)
.catch( () => {
expect(true).toBe(true);
})
})
})

0 comments on commit 7ee03d7

Please sign in to comment.