diff --git a/lib/availability_resolver.js b/lib/availability_resolver.js index 14fab431..8f2b7eb7 100644 --- a/lib/availability_resolver.js +++ b/lib/availability_resolver.js @@ -22,6 +22,22 @@ class AvailabilityResolver { this.barcodes = this._parseBarCodesFromESResponse() } + /** + * Given a map relating status strings to arrays of barcodes, + * returns a map relating each barcode to a status string. + */ + static invertBarcodeByStatusMapping (barcodesByStatus) { + if (!barcodesByStatus || typeof barcodesByStatus !== 'object') { + return {} + } + return Object.keys(barcodesByStatus) + .reduce((h, status) => { + const barcodeToStatusPairs = barcodesByStatus[status] + .map((barcode) => ({ [barcode]: status })) + return Object.assign(h, ...barcodeToStatusPairs) + }, {}) + } + // returns an updated elasticSearchResponse with the newest availability info from SCSB responseWithUpdatedAvailability (options = {}) { // If this serialization is a result of a hold request initializing, we want @@ -30,12 +46,21 @@ class AvailabilityResolver { ? () => this._checkScsbForRecapCustomerCode() : () => Promise.resolve() + // When options.recapBarcodesByStatus is set, we can use it in place of + // re-querying status by barcode: + const barcodeToStatusMap = async () => { + if (options.recapBarcodesByStatus) { + // Invert mapping to map barcodes to statuses: + return AvailabilityResolver.invertBarcodeByStatusMapping(options.recapBarcodesByStatus) + } else { + return this._createSCSBBarcodeAvailbilityMapping(this.barcodes) + } + } + // Get 1) barcode-availability mapping and 2) customer code query in // parallel because they don't depend on each other: return Promise.all([ - // TODO: When options.recapBarcodesByStatus is set, we should be able to - // use it in place of re-querying status by barcode: - this._createSCSBBarcodeAvailbilityMapping(this.barcodes), + barcodeToStatusMap(), updateRecapCustomerCodes() ]) .then((barcodeMappingAndCustomerCodeResult) => { @@ -181,22 +206,30 @@ class AvailabilityResolver { /** * Given an array of barcodes, returns a hash mapping barcode to SCSB availability */ - _createSCSBBarcodeAvailbilityMapping (barcodes) { + async _createSCSBBarcodeAvailbilityMapping (barcodes) { if (barcodes.length === 0) { - return Promise.resolve({}) + return {} } - return scsbClient.getItemsAvailabilityForBarcodes(this.barcodes) - .then((itemsStatus) => { - if (!Array.isArray(itemsStatus)) { - logger.warn(`Got bad itemAvailabilityStatus response from SCSB for barcodes (${barcodes}): ${JSON.stringify(itemsStatus)}`) - return {} - } - const barcodesAndAvailability = {} - itemsStatus.forEach((statusEntry) => { - barcodesAndAvailability[statusEntry.itemBarcode] = statusEntry.itemAvailabilityStatus - }) - return barcodesAndAvailability - }) + let itemsStatus + try { + itemsStatus = await scsbClient.getItemsAvailabilityForBarcodes(this.barcodes) + } catch (e) { + logger.warn(`Error retrieving SCSB statuses for barcodes: ${e}`) + return {} + } + + if (!Array.isArray(itemsStatus)) { + logger.warn(`Got bad itemAvailabilityStatus response from SCSB for barcodes (${barcodes}): ${JSON.stringify(itemsStatus)}`) + return {} + } + + // Convert SCSB API response into barcode => status map: + return itemsStatus + // Verify the entries have the properties we expect: + .filter((entry) => entry.itemBarcode && entry.itemAvailabilityStatus) + .reduce((h, entry) => { + return Object.assign(h, { [entry.itemBarcode]: entry.itemAvailabilityStatus }) + }, {}) } _parseBarCodesFromESResponse () { diff --git a/lib/delivery-locations-resolver.js b/lib/delivery-locations-resolver.js index 7c6f7ec7..1521ff07 100644 --- a/lib/delivery-locations-resolver.js +++ b/lib/delivery-locations-resolver.js @@ -7,15 +7,20 @@ const onsiteEddCriteria = require('../data/onsite-edd-criteria.json') const { isItemNyplOwned } = require('./ownership_determination') class DeliveryLocationsResolver { + static nyplCoreLocation (locationCode) { + return sierraLocations[locationCode] + } + static requestableBasedOnHoldingLocation (item) { - // Is this not requestable because of its holding location? - try { - const holdingLocationSierraCode = item.holdingLocation[0].id.split(':').pop() - return sierraLocations[holdingLocationSierraCode].requestable - } catch (e) { - logger.warn('There is an item in the index with missing or malformed holdingLocation', item) + const locationCode = this.extractLocationCode(item) + + if (!DeliveryLocationsResolver.nyplCoreLocation(locationCode)) { + logger.warn(`DeliveryLocationsResolver: Unrecognized holdingLocation for ${item.uri}: ${locationCode}`) return false } + + // Is this not requestable because of its holding location? + return DeliveryLocationsResolver.nyplCoreLocation(locationCode).requestable } // Currently, there is no physical delivery requests for onsite items through Discovery API @@ -24,11 +29,11 @@ class DeliveryLocationsResolver { // If holdingLocation given, strip code from @id for lookup: const locationCode = holdingLocation && holdingLocation.id ? holdingLocation.id.replace(/^loc:/, '') : null // Is Sierra location code mapped? - if (sierraLocations[locationCode] && sierraLocations[locationCode].sierraDeliveryLocations) { + if (DeliveryLocationsResolver.nyplCoreLocation(locationCode)?.sierraDeliveryLocations) { // It's mapped, but the sierraDeliveryLocation entities only have `code` and `label` // Do a second lookup to populate `deliveryLocationTypes` - return sierraLocations[locationCode].sierraDeliveryLocations.map((deliveryLocation) => { - deliveryLocation.deliveryLocationTypes = sierraLocations[deliveryLocation.code].deliveryLocationTypes + return DeliveryLocationsResolver.nyplCoreLocation(locationCode).sierraDeliveryLocations.map((deliveryLocation) => { + deliveryLocation.deliveryLocationTypes = DeliveryLocationsResolver.nyplCoreLocation(deliveryLocation.code).deliveryLocationTypes return deliveryLocation }) // Either holdingLocation is null or code not matched; Fall back on mocked data: @@ -141,11 +146,12 @@ class DeliveryLocationsResolver { } static extractLocationCode (item) { - try { - return item.holdingLocation[0].id.split(':').pop() - } catch (e) { - logger.warn('There is an item in the index with missing or malformed holdingLocation', item) + if (!Array.isArray(item.holdingLocation)) { + logger.warn(`DeliveryLocationsResolver#extractLocationCode: Item missing holdingLocation: ${item.uri}`) + return false } + + return item.holdingLocation[0]?.id?.split(':').pop() } static sortPosition (location) { @@ -224,7 +230,7 @@ class DeliveryLocationsResolver { deliveryLocation: [] } const holdingLocationCode = this.extractLocationCode(item) - const sierraData = sierraLocations[holdingLocationCode] + const sierraData = DeliveryLocationsResolver.nyplCoreLocation(holdingLocationCode) if (!sierraData) { // This case is mainly to satisfy a test which wants eddRequestable = false // for a made up location code. diff --git a/test/availability_resolver.test.js b/test/availability_resolver.test.js index 28d317a2..2ca4a3f8 100644 --- a/test/availability_resolver.test.js +++ b/test/availability_resolver.test.js @@ -52,184 +52,186 @@ const itemAvailabilityResponse = [ ] describe('Response with updated availability', function () { - beforeEach(() => { - sinon.stub(scsbClient, 'getItemsAvailabilityForBarcodes') - .callsFake(() => Promise.resolve(itemAvailabilityResponse)) - - sinon.stub(scsbClient, 'recapCustomerCodeByBarcode') - .callsFake(() => Promise.resolve('NC')) - }) - - afterEach(() => { - scsbClient.getItemsAvailabilityForBarcodes.restore() - scsbClient.recapCustomerCodeByBarcode.restore() - }) - - it('will change an items status to "Available" if ElasticSearch says it\'s unavailable but SCSB says it is Available', function () { - const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) + describe('responseWithUpdatedAvailability', () => { + beforeEach(() => { + sinon.stub(scsbClient, 'getItemsAvailabilityForBarcodes') + .callsFake(() => Promise.resolve(itemAvailabilityResponse)) - const indexedAsUnavailableURI = 'i10283664' + sinon.stub(scsbClient, 'recapCustomerCodeByBarcode') + .callsFake(() => Promise.resolve('NC')) + }) - const indexedAsUnavailable = elasticSearchResponse.fakeElasticSearchResponseNyplItem().hits.hits[0]._source.items.find((item) => { - return item.uri === indexedAsUnavailableURI + afterEach(() => { + scsbClient.getItemsAvailabilityForBarcodes.restore() + scsbClient.recapCustomerCodeByBarcode.restore() }) - // Test that it's unavailable at first - expect(indexedAsUnavailable.status[0].id).to.equal('status:na') - expect(indexedAsUnavailable.status[0].label).to.equal('Not available') + it('will change an items status to "Available" if ElasticSearch says it\'s unavailable but SCSB says it is Available', function () { + const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) - return availabilityResolver.responseWithUpdatedAvailability() - .then((modifiedResponse) => { - const theItem = modifiedResponse.hits.hits[0]._source.items.find((item) => { - return item.uri === indexedAsUnavailableURI - }) + const indexedAsUnavailableURI = 'i10283664' - // Test AvailabilityResolver munges it into availability - expect(theItem.status[0].id).to.equal('status:a') - expect(theItem.status[0].label).to.equal('Available') + const indexedAsUnavailable = elasticSearchResponse.fakeElasticSearchResponseNyplItem().hits.hits[0]._source.items.find((item) => { + return item.uri === indexedAsUnavailableURI }) - }) - - it('will change an items status to "Unavailable" if ElasticSearch says it\'s Available but SCSB says it is Unvailable', function () { - const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) - const indexedAsAvailableURI = 'i102836649' - const indexedAsAvailable = elasticSearchResponse.fakeElasticSearchResponseNyplItem().hits.hits[0]._source.items.find((item) => { - return item.uri === indexedAsAvailableURI - }) + // Test that it's unavailable at first + expect(indexedAsUnavailable.status[0].id).to.equal('status:na') + expect(indexedAsUnavailable.status[0].label).to.equal('Not available') - // Test that it's available at first - expect(indexedAsAvailable.status[0].id).to.equal('status:a') - expect(indexedAsAvailable.status[0].label).to.equal('Available') + return availabilityResolver.responseWithUpdatedAvailability() + .then((modifiedResponse) => { + const theItem = modifiedResponse.hits.hits[0]._source.items.find((item) => { + return item.uri === indexedAsUnavailableURI + }) - return availabilityResolver.responseWithUpdatedAvailability() - .then((modifiedResponse) => { - const theItem = modifiedResponse.hits.hits[0]._source.items.find((item) => { - return item.uri === indexedAsAvailableURI + // Test AvailabilityResolver munges it into availability + expect(theItem.status[0].id).to.equal('status:a') + expect(theItem.status[0].label).to.equal('Available') }) - - // Test AvailabilityResolver munges it into temporarily unavailable - expect(theItem.status[0].id).to.equal('status:na') - expect(theItem.status[0].label).to.equal('Not available') - }) - }) - - it('will return the original ElasticSearchResponse\'s status for the item if the SCSB can\'t find an item with the barcode', function () { - const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) - - const indexedButNotAvailableInSCSBURI = 'i22566485' - const indexedButNotAvailableInSCSB = elasticSearchResponse.fakeElasticSearchResponseNyplItem().hits.hits[0]._source.items.find((item) => { - return item.uri === indexedButNotAvailableInSCSBURI }) - expect(indexedButNotAvailableInSCSB.status[0].id).to.equal('status:a') - expect(indexedButNotAvailableInSCSB.status[0].label).to.equal('Available') - - return availabilityResolver.responseWithUpdatedAvailability() - .then((modifiedResponse) => { - const theItem = modifiedResponse.hits.hits[0]._source.items.find((item) => { - return item.uri === indexedButNotAvailableInSCSBURI - }) + it('will change an items status to "Unavailable" if ElasticSearch says it\'s Available but SCSB says it is Unvailable', function () { + const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) - // As this item is not available in SCSB, the elasticSearchResponse's availability for the item was returned - expect(theItem.status[0].id).to.equal('status:a') - expect(theItem.status[0].label).to.equal('Available') + const indexedAsAvailableURI = 'i102836649' + const indexedAsAvailable = elasticSearchResponse.fakeElasticSearchResponseNyplItem().hits.hits[0]._source.items.find((item) => { + return item.uri === indexedAsAvailableURI }) - }) - it('includes the latest availability status of items', function () { - const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) + // Test that it's available at first + expect(indexedAsAvailable.status[0].id).to.equal('status:a') + expect(indexedAsAvailable.status[0].label).to.equal('Available') - return availabilityResolver.responseWithUpdatedAvailability() - .then((response) => { - const items = response.hits.hits[0]._source.items + return availabilityResolver.responseWithUpdatedAvailability() + .then((modifiedResponse) => { + const theItem = modifiedResponse.hits.hits[0]._source.items.find((item) => { + return item.uri === indexedAsAvailableURI + }) - // A ReCAP item with Discovery status 'Available', but SCSB - // status 'Not Available' should be made 'Not Available' - const unavailableItem = items.find((item) => { - return item.uri === 'i102836649' + // Test AvailabilityResolver munges it into temporarily unavailable + expect(theItem.status[0].id).to.equal('status:na') + expect(theItem.status[0].label).to.equal('Not available') }) - expect(unavailableItem.status[0].id).to.equal('status:na') - expect(unavailableItem.status[0].label).to.equal('Not available') + }) - // A ReCAP item with Discovery status 'Not Avaiable', but SCSB - // status 'Available' should be made available: - const availableItem = items.find((item) => { - return item.uri === 'i10283664' - }) - expect(availableItem.status[0].id).to.equal('status:a') - expect(availableItem.status[0].label).to.equal('Available') + it('will return the original ElasticSearchResponse\'s status for the item if the SCSB can\'t find an item with the barcode', function () { + const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) + + const indexedButNotAvailableInSCSBURI = 'i22566485' + const indexedButNotAvailableInSCSB = elasticSearchResponse.fakeElasticSearchResponseNyplItem().hits.hits[0]._source.items.find((item) => { + return item.uri === indexedButNotAvailableInSCSBURI }) - }) - describe('CUL item', function () { - let availabilityResolver = null + expect(indexedButNotAvailableInSCSB.status[0].id).to.equal('status:a') + expect(indexedButNotAvailableInSCSB.status[0].label).to.equal('Available') - before(function () { - availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseCulItem()) - }) - - it('marks CUL item Available when SCSB API indicates it is so', function () { return availabilityResolver.responseWithUpdatedAvailability() - .then((response) => { - const items = response.hits.hits[0]._source.items + .then((modifiedResponse) => { + const theItem = modifiedResponse.hits.hits[0]._source.items.find((item) => { + return item.uri === indexedButNotAvailableInSCSBURI + }) - const availableItem = items.find((item) => item.uri === 'ci1455504') - expect(availableItem.requestable[0]).to.equal(true) - expect(availableItem.status[0].label).to.equal('Available') + // As this item is not available in SCSB, the elasticSearchResponse's availability for the item was returned + expect(theItem.status[0].id).to.equal('status:a') + expect(theItem.status[0].label).to.equal('Available') }) }) - it('marks CUL item not avilable when SCSB API indicates it is so', function () { + it('includes the latest availability status of items', function () { + const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) + return availabilityResolver.responseWithUpdatedAvailability() .then((response) => { const items = response.hits.hits[0]._source.items - const notAvailableItem = items.find((item) => item.uri === 'ci14555049999') - expect(notAvailableItem.status[0].label).to.equal('Not available') - }) - }) - }) + // A ReCAP item with Discovery status 'Available', but SCSB + // status 'Not Available' should be made 'Not Available' + const unavailableItem = items.find((item) => { + return item.uri === 'i102836649' + }) + expect(unavailableItem.status[0].id).to.equal('status:na') + expect(unavailableItem.status[0].label).to.equal('Not available') - describe('checks recapCustomerCodes when options specifies', () => { - let availabilityResolver = null - it('logs an error when item\'s code does not match SCSB', () => { - availabilityResolver = new AvailabilityResolver(recapScsbQueryMismatch()) - const loggerSpy = sinon.spy(logger, 'error') - return availabilityResolver.responseWithUpdatedAvailability({ queryRecapCustomerCode: true }) - .then(() => { - expect(loggerSpy.calledOnce).to.equal(true) - logger.error.restore() + // A ReCAP item with Discovery status 'Not Avaiable', but SCSB + // status 'Available' should be made available: + const availableItem = items.find((item) => { + return item.uri === 'i10283664' + }) + expect(availableItem.status[0].id).to.equal('status:a') + expect(availableItem.status[0].label).to.equal('Available') }) }) - it('updates recapCustomerCode when item\'s code does not match SCSB', () => { - return availabilityResolver.responseWithUpdatedAvailability() - .then((response) => { - const items = response.hits.hits[0]._source.items - // A ReCAP item with customer code XX - const queryItem = items.find((item) => { - return item.uri === 'i10283667' + describe('CUL item', function () { + let availabilityResolver = null + + before(function () { + availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseCulItem()) + }) + + it('marks CUL item Available when SCSB API indicates it is so', function () { + return availabilityResolver.responseWithUpdatedAvailability() + .then((response) => { + const items = response.hits.hits[0]._source.items + + const availableItem = items.find((item) => item.uri === 'ci1455504') + expect(availableItem.requestable[0]).to.equal(true) + expect(availableItem.status[0].label).to.equal('Available') }) - expect(queryItem.recapCustomerCode[0]).to.equal('NC') - }) - }) + }) - it('does nothing when current recapCustomerCode and SCSB code are a match', () => { - availabilityResolver = new AvailabilityResolver(recapScsbQueryMatch()) - const loggerSpy = sinon.spy(logger, 'error') - return availabilityResolver.responseWithUpdatedAvailability() - .then(() => { - expect(loggerSpy.notCalled).to.equal(true) - logger.error.restore() - }) + it('marks CUL item not avilable when SCSB API indicates it is so', function () { + return availabilityResolver.responseWithUpdatedAvailability() + .then((response) => { + const items = response.hits.hits[0]._source.items + + const notAvailableItem = items.find((item) => item.uri === 'ci14555049999') + expect(notAvailableItem.status[0].label).to.equal('Not available') + }) + }) }) - it('does not query SCSB unless specified in options', () => { - return availabilityResolver.responseWithUpdatedAvailability() - .then(() => { - expect(scsbClient.recapCustomerCodeByBarcode.notCalled).to.equal(true) - }) + describe('checks recapCustomerCodes when options specifies', () => { + let availabilityResolver = null + it('logs an error when item\'s code does not match SCSB', () => { + availabilityResolver = new AvailabilityResolver(recapScsbQueryMismatch()) + const loggerSpy = sinon.spy(logger, 'error') + return availabilityResolver.responseWithUpdatedAvailability({ queryRecapCustomerCode: true }) + .then(() => { + expect(loggerSpy.calledOnce).to.equal(true) + logger.error.restore() + }) + }) + + it('updates recapCustomerCode when item\'s code does not match SCSB', () => { + return availabilityResolver.responseWithUpdatedAvailability() + .then((response) => { + const items = response.hits.hits[0]._source.items + // A ReCAP item with customer code XX + const queryItem = items.find((item) => { + return item.uri === 'i10283667' + }) + expect(queryItem.recapCustomerCode[0]).to.equal('NC') + }) + }) + + it('does nothing when current recapCustomerCode and SCSB code are a match', () => { + availabilityResolver = new AvailabilityResolver(recapScsbQueryMatch()) + const loggerSpy = sinon.spy(logger, 'error') + return availabilityResolver.responseWithUpdatedAvailability() + .then(() => { + expect(loggerSpy.notCalled).to.equal(true) + logger.error.restore() + }) + }) + + it('does not query SCSB unless specified in options', () => { + return availabilityResolver.responseWithUpdatedAvailability() + .then(() => { + expect(scsbClient.recapCustomerCodeByBarcode.notCalled).to.equal(true) + }) + }) }) }) @@ -374,4 +376,98 @@ describe('Response with updated availability', function () { }) }) }) + + describe('SCSB outage', () => { + before(() => { + sinon.stub(scsbClient, 'getItemsAvailabilityForBarcodes') + .callsFake(() => { + throw new Error('oh no!') + }) + }) + + after(() => { + scsbClient.getItemsAvailabilityForBarcodes.restore() + }) + + it('makes recap items na when scsb is out', async () => { + const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) + + const modifiedResponse = await availabilityResolver.responseWithUpdatedAvailability() + + // Just examine items in rc locations: + const recapItems = modifiedResponse.hits.hits[0]._source.items + .filter((item) => item.holdingLocation && item.holdingLocation[0] && /^loc:rc/.test(item.holdingLocation[0].id)) + + expect(recapItems).to.have.lengthOf(3) + + // Assert that all recap items are Not available: + recapItems.forEach((item) => { + // Test AvailabilityResolver munges it into availability + expect(item.status[0].id).to.equal('status:na') + expect(item.status[0].label).to.equal('Not available') + }) + }) + }) + + describe('SCSB bad responses', () => { + // Imagine some unexpected responses: + ; [ + // Unexpected status string: + [{ itemBarcode: '10005468369', itemAvailabilityStatus: 'fladeedle' }], + // An entry without a barcode: + [{ itemAvailabilityStatus: 'Available' }], + // Truthy but useless: + [{ }], + // Some kind of error response: + { error: 'some other error' }, + // HTML! + 'oh no html { + it(`makes recap items "na" when scsb returns unexpected responses (#${index})`, async () => { + // Stub the scsb client to return the bad response: + sinon.stub(scsbClient, 'getItemsAvailabilityForBarcodes') + .callsFake(() => Promise.resolve(badResponse)) + + const availabilityResolver = new AvailabilityResolver(elasticSearchResponse.fakeElasticSearchResponseNyplItem()) + const modifiedResponse = await availabilityResolver.responseWithUpdatedAvailability() + + // Get the single item for which we've mocked the bad scsb response above: + const item = modifiedResponse.hits.hits[0]._source.items + .find((item) => item.identifier && item.identifier[0] === 'urn:barcode:10005468369') + + // Expect the item's Available status to have been flipped to 'na' even + // through scsb api returned a weird response: + expect(item.status[0].id).to.equal('status:na') + expect(item.status[0].label).to.equal('Not available') + + // Restore the client method: + scsbClient.getItemsAvailabilityForBarcodes.restore() + }) + }) + }) + + describe('invertBarcodeByStatusMapping', () => { + it('returns empty map if invalid input given', () => { + ;[null, false, true, 'fladeedle'].forEach((badValue) => { + expect(AvailabilityResolver.invertBarcodeByStatusMapping(badValue)).to.deep.eq({}) + }) + }) + + it('inverts a status to barcode map', () => { + const map = AvailabilityResolver.invertBarcodeByStatusMapping({ + Available: ['b1', 'b2'], + 'Not available': ['b3', 'b4'] + }) + expect(map).to.deep.eq({ + b1: 'Available', + b2: 'Available', + b3: 'Not available', + b4: 'Not available' + }) + }) + }) }) diff --git a/test/delivery-locations-resolver.test.js b/test/delivery-locations-resolver.test.js index 57da2765..21672f5d 100644 --- a/test/delivery-locations-resolver.test.js +++ b/test/delivery-locations-resolver.test.js @@ -1,3 +1,5 @@ +const sinon = require('sinon') + const DeliveryLocationsResolver = require('../lib/delivery-locations-resolver') const sampleItems = { @@ -149,13 +151,36 @@ function takeThisPartyPartiallyOffline () { describe('Delivery-locations-resolver', function () { before(takeThisPartyPartiallyOffline) - it('will hide "Scholar" deliveryLocation for LPA or SC only deliverable items, patron is scholar type', function () { - return DeliveryLocationsResolver.attachDeliveryLocationsAndEddRequestability([sampleItems.onsiteOnlySchomburg], 'mala').then((items) => { - expect(items[0].deliveryLocation).to.not.have.lengthOf(0) + describe('SC delivery locations', () => { + before(() => { + // Override NYPL-Core lookup for scff3 to make it requestable: + sinon.stub(DeliveryLocationsResolver, 'nyplCoreLocation').callsFake(() => { + return { + sierraDeliveryLocations: [ + { + code: 'sc', + label: 'Schomburg Center - Research and Reference Division', + locationsApiSlug: 'schomburg', + deliveryLocationTypes: ['Research'] + } + ], + requestable: true + } + }) + }) - // Confirm the known scholar rooms are not included: - scholarRooms.forEach((scholarRoom) => { - expect(items[0].deliveryLocation).to.not.include(scholarRoom) + after(() => { + DeliveryLocationsResolver.nyplCoreLocation.restore() + }) + + it('will hide "Scholar" deliveryLocation for LPA or SC only deliverable items, patron is scholar type', function () { + return DeliveryLocationsResolver.attachDeliveryLocationsAndEddRequestability([sampleItems.onsiteOnlySchomburg], 'mala').then((items) => { + expect(items[0].deliveryLocation).to.not.have.lengthOf(0) + + // Confirm the known scholar rooms are not included: + scholarRooms.forEach((scholarRoom) => { + expect(items[0].deliveryLocation).to.not.include(scholarRoom) + }) }) }) })