def test_authenticates_wrangler_requests(self): """Tests that the client_id and client_secret are set for any Metadata Wrangler lookups""" mw_integration = Configuration.METADATA_WRANGLER_INTEGRATION mw_client_id = Configuration.METADATA_WRANGLER_CLIENT_ID mw_client_secret = Configuration.METADATA_WRANGLER_CLIENT_SECRET with temp_config() as config: config['integrations'][mw_integration] = { Configuration.URL: "http://localhost", mw_client_id: "abc", mw_client_secret: "def" } importer = SimplifiedOPDSLookup.from_config() eq_("abc", importer.client_id) eq_("def", importer.client_secret) # An error is raised if only one value is set. del config['integrations'][mw_integration][mw_client_secret] assert_raises(CannotLoadConfiguration, SimplifiedOPDSLookup.from_config) # The details are None if client configuration isn't set at all. del config['integrations'][mw_integration][mw_client_id] importer = SimplifiedOPDSLookup.from_config() eq_(None, importer.client_id) eq_(None, importer.client_secret) # For other integrations, the details aren't created at all. config['integrations']["Content Server"] = dict( url="http://whatevz") importer = SimplifiedOPDSLookup.from_config("Content Server") eq_(None, importer.client_id) eq_(None, importer.client_secret)
def test_delta(self): with temp_config() as config: config[Configuration.INTEGRATIONS]['OneClick'] = { 'library_id' : '1931', 'username' : 'username_123', 'password' : 'password_123', 'remote_stage' : 'qa', 'base_url' : 'www.oneclickapi.test', 'basic_token' : 'abcdef123hijklm', "ebook_loan_length" : '21', "eaudio_loan_length" : '21' } cmd_args = ["--mock"] # first, load a sample library importer = OneClickImportScript(_db=self._db, cmd_args=cmd_args) datastr, datadict = self.get_data("response_catalog_all_sample.json") importer.api.queue_response(status_code=200, content=datastr) importer.run() # set license numbers on test pool pool, made_new = LicensePool.for_foreign_id(self._db, DataSource.ONECLICK, Identifier.ONECLICK_ID, "9781615730186") eq_(False, made_new) pool.licenses_owned = 10 pool.licenses_available = 9 pool.licenses_reserved = 2 pool.patrons_in_hold_queue = 1 # now update that library with a sample delta cmd_args = ["--mock"] delta_runner = OneClickDeltaScript(_db=self._db, cmd_args=cmd_args) datastr, datadict = self.get_data("response_catalog_delta.json") delta_runner.api.queue_response(status_code=200, content=datastr) delta_runner.run() # "Tricks" did not get deleted, but did get its pools set to "nope". # "Emperor Mage: The Immortals" got new metadata. works = self._db.query(Work).all() work_titles = [work.title for work in works] expected_titles = ["Tricks", "Emperor Mage: The Immortals", "In-Flight Russian", "Road, The", "Private Patient, The", "Year of Magical Thinking, The", "Junkyard Bot: Robots Rule, Book 1, The", "Challenger Deep"] eq_(set(expected_titles), set(work_titles)) eq_("Tricks", pool.presentation_edition.title) eq_(0, pool.licenses_owned) eq_(0, pool.licenses_available) eq_(0, pool.licenses_reserved) eq_(0, pool.patrons_in_hold_queue) assert (datetime.datetime.utcnow() - pool.last_checked) < datetime.timedelta(seconds=20) # make sure we updated fields edition = Edition.for_foreign_id(self._db, DataSource.ONECLICK, Identifier.ONECLICK_ID, "9781934180723", create_if_not_exists=False) eq_("Recorded Books, Inc.", edition.publisher) # make sure there are still 8 LicensePools pools = self._db.query(LicensePool).all() eq_(8, len(pools))
def __init__(self, _db, with_token=True, *args, **kwargs): with temp_config() as config: config[Configuration.INTEGRATIONS]['Theta'] = { 'library_id': 'a', 'username': '******', 'password': '******', 'server': 'http://theta.test/', } super(MockThetaAPI, self).__init__(_db, *args, **kwargs) if with_token: self.token = "mock token" self.responses = [] self.requests = []
def test_collect_event(self): config = { Configuration.POLICIES: { Configuration.ANALYTICS_POLICY: ["mock_analytics_provider"] }, "option": "value" } with temp_config(config) as config: work = self._work(title="title", with_license_pool=True) [lp] = work.license_pools Analytics.collect_event(self._db, lp, CirculationEvent.CHECKIN, None) mock = Analytics.instance().providers[0] eq_(1, mock.count)
def __init__(self, _db, *args, **kwargs): self.responses = [] self.requests = [] with temp_config() as config: config[Configuration.INTEGRATIONS]['3M'] = { 'library_id': 'a', 'account_id': 'b', 'account_key': 'c', } super(MockThreeMAPI, self).__init__(_db, *args, base_url="http://3m.test", **kwargs)
def test_facet_groups(self): facets = Facets( Facets.COLLECTION_MAIN, Facets.AVAILABLE_ALL, Facets.ORDER_TITLE ) all_groups = list(facets.facet_groups) # By default, there are a 9 facet transitions: three groups of three. eq_(9, len(all_groups)) # available=all, collection=main, and order=title are the selected # facets. selected = sorted([x[:2] for x in all_groups if x[-1] == True]) eq_( [('available', 'all'), ('collection', 'main'), ('order', 'title')], selected ) test_facet_policy = { "enabled" : { Facets.ORDER_FACET_GROUP_NAME : [ Facets.ORDER_WORK_ID, Facets.ORDER_TITLE ], Facets.COLLECTION_FACET_GROUP_NAME : [Facets.COLLECTION_FULL], Facets.AVAILABILITY_FACET_GROUP_NAME : [Facets.AVAILABLE_ALL], }, "default" : { Facets.ORDER_FACET_GROUP_NAME : Facets.ORDER_TITLE, Facets.COLLECTION_FACET_GROUP_NAME : Facets.COLLECTION_FULL, Facets.AVAILABILITY_FACET_GROUP_NAME : Facets.AVAILABLE_ALL, } } with temp_config() as config: config['policies'] = { Configuration.FACET_POLICY : test_facet_policy } facets = Facets(None, None, Facets.ORDER_TITLE) all_groups = list(facets.facet_groups) # We have disabled almost all the facets, so the list of # facet transitions includes only two items. # # 'Sort by title' was selected, and it shows up as the selected # item in this facet group. expect = [['order', 'title', True], ['order', 'work_id', False]] eq_(expect, sorted([list(x[:2]) + [x[-1]] for x in all_groups]))
def __init__(self, _db, *args, **kwargs): self.responses = [] # The constructor will make a request for the access token, # and then a request for the collection token. self.queue_response(200, content=self.mock_access_token("bearer token")) self.queue_response( 200, content=self.mock_collection_token("collection token") ) with temp_config() as config: config[Configuration.INTEGRATIONS]['Overdrive'] = { 'client_key' : 'a', 'client_secret' : 'b', 'website_id' : 'c', 'library_id' : 'd', } super(MockOverdriveAPI, self).__init__(_db, *args, **kwargs)
def test_import(self): with temp_config() as config: config[Configuration.INTEGRATIONS]['OneClick'] = { 'library_id' : '1931', 'username' : 'username_123', 'password' : 'password_123', 'remote_stage' : 'qa', 'base_url' : 'www.oneclickapi.test', 'basic_token' : 'abcdef123hijklm', "ebook_loan_length" : '21', "eaudio_loan_length" : '21' } cmd_args = ["--mock"] importer = OneClickImportScript(_db=self._db, cmd_args=cmd_args) datastr, datadict = self.get_data("response_catalog_all_sample.json") importer.api.queue_response(status_code=200, content=datastr) importer.run() # verify that we created Works, Editions, LicensePools works = self._db.query(Work).all() work_titles = [work.title for work in works] expected_titles = ["Tricks", "Emperor Mage: The Immortals", "In-Flight Russian", "Road, The", "Private Patient, The", "Year of Magical Thinking, The", "Junkyard Bot: Robots Rule, Book 1, The", "Challenger Deep"] eq_(set(expected_titles), set(work_titles)) # make sure we created some Editions edition = Edition.for_foreign_id(self._db, DataSource.ONECLICK, Identifier.ONECLICK_ID, "9780062231727", create_if_not_exists=False) assert(edition is not None) edition = Edition.for_foreign_id(self._db, DataSource.ONECLICK, Identifier.ONECLICK_ID, "9781615730186", create_if_not_exists=False) assert(edition is not None) # make sure we created some LicensePools pool, made_new = LicensePool.for_foreign_id(self._db, DataSource.ONECLICK, Identifier.ONECLICK_ID, "9780062231727") eq_(False, made_new) pool, made_new = LicensePool.for_foreign_id(self._db, DataSource.ONECLICK, Identifier.ONECLICK_ID, "9781615730186") eq_(False, made_new) # make sure there are 8 LicensePools pools = self._db.query(LicensePool).all() eq_(8, len(pools))
def _assert_featured_works(size, expected_works=None, expected_length=None, sampled_works=None): featured_works = None featured_materialized_works = None with temp_config() as config: config[Configuration.POLICIES] = { Configuration.FEATURED_LANE_SIZE: size } featured_works = lane.featured_works( use_materialized_works=False) featured_materialized_works = lane.featured_works() expected_length = expected_length if expected_length == None: expected_length = size eq_(expected_length, len(featured_works)) eq_(expected_length, len(featured_materialized_works)) expected_works = expected_works or [] for work in expected_works: assert work in featured_works # There's also a single MaterializedWork that matches the work. [materialized_work] = filter(lambda mw: mw.works_id == work.id, featured_materialized_works) # Remove the confirmed works for the next test. featured_works.remove(work) featured_materialized_works.remove(materialized_work) sampled_works = sampled_works or [] for work in featured_works: assert work in sampled_works for work in featured_materialized_works: [sampled_work ] = filter(lambda sample: sample.id == work.works_id, sampled_works)
def test_apply(self): # Set up works that are matched by different types of collections. # A high-quality open-access work. open_access_high = self._work(with_open_access_download=True) open_access_high.quality = 0.8 open_access_high.random = 0.2 # A low-quality open-access work. open_access_low = self._work(with_open_access_download=True) open_access_low.quality = 0.2 open_access_low.random = 0.4 # A high-quality licensed work which is not currently available. (licensed_e1, licensed_p1) = self._edition(data_source_name=DataSource.OVERDRIVE, with_license_pool=True) licensed_high = self._work(presentation_edition=licensed_e1) licensed_high.license_pools.append(licensed_p1) licensed_high.quality = 0.8 licensed_p1.open_access = False licensed_p1.licenses_owned = 1 licensed_p1.licenses_available = 0 licensed_high.random = 0.3 # A low-quality licensed work which is currently available. (licensed_e2, licensed_p2) = self._edition(data_source_name=DataSource.OVERDRIVE, with_license_pool=True) licensed_p2.open_access = False licensed_low = self._work(presentation_edition=licensed_e2) licensed_low.license_pools.append(licensed_p2) licensed_low.quality = 0.2 licensed_p2.licenses_owned = 1 licensed_p2.licenses_available = 1 licensed_low.random = 0.1 qu = self._db.query(Work).join(Work.presentation_edition).join( Work.license_pools) def facetify(collection=Facets.COLLECTION_FULL, available=Facets.AVAILABLE_ALL, order=Facets.ORDER_TITLE): f = Facets(collection, available, order) return f.apply(self._db, qu) # When holds are allowed, we can find all works by asking # for everything. with temp_config() as config: config['policies'] = { Configuration.HOLD_POLICY: Configuration.HOLD_POLICY_ALLOW } everything = facetify() eq_(4, everything.count()) # If we disallow holds, we lose one book even when we ask for # everything. with temp_config() as config: config['policies'] = { Configuration.HOLD_POLICY: Configuration.HOLD_POLICY_HIDE } everything = facetify() eq_(3, everything.count()) assert licensed_high not in everything with temp_config() as config: config['policies'] = { Configuration.HOLD_POLICY: Configuration.HOLD_POLICY_ALLOW } # Even when holds are allowed, if we restrict to books # currently available we lose the unavailable book. available_now = facetify(available=Facets.AVAILABLE_NOW) eq_(3, available_now.count()) assert licensed_high not in available_now # If we restrict to open-access books we lose two books. open_access = facetify(available=Facets.AVAILABLE_OPEN_ACCESS) eq_(2, open_access.count()) assert licensed_high not in open_access assert licensed_low not in open_access # If we restrict to the main collection we lose the low-quality # open-access book. main_collection = facetify(collection=Facets.COLLECTION_MAIN) eq_(3, main_collection.count()) assert open_access_low not in main_collection # If we restrict to the featured collection we lose both # low-quality books. featured_collection = facetify( collection=Facets.COLLECTION_FEATURED) eq_(2, featured_collection.count()) assert open_access_low not in featured_collection assert licensed_low not in featured_collection title_order = facetify(order=Facets.ORDER_TITLE) eq_([ open_access_high, open_access_low, licensed_high, licensed_low ], title_order.all()) random_order = facetify(order=Facets.ORDER_RANDOM) eq_([ licensed_low, open_access_high, licensed_high, open_access_low ], random_order.all())
def test_only_show_ready_deliverable_works(self): # w1 has licenses but no available copies. It's available # unless site policy is to hide books like this. w1 = self._work(with_license_pool=True) w1.license_pools[0].open_access = False w1.license_pools[0].licenses_owned = 10 w1.license_pools[0].licenses_available = 0 # w2 has no delivery mechanisms. w2 = self._work(with_license_pool=True, with_open_access_download=False) for dm in w2.license_pools[0].delivery_mechanisms: self._db.delete(dm) # w3 is not presentation ready. w3 = self._work(with_license_pool=True) w3.presentation_ready = False # w4's only license pool is suppressed. w4 = self._work(with_open_access_download=True) w4.license_pools[0].suppressed = True # w5 has no licenses. w5 = self._work(with_license_pool=True) w5.license_pools[0].open_access = False w5.license_pools[0].licenses_owned = 0 # w6 is an open-access book, so it's available even though # licenses_owned and licenses_available are zero. w6 = self._work(with_open_access_download=True) w6.license_pools[0].open_access = True w6.license_pools[0].licenses_owned = 0 w6.license_pools[0].licenses_available = 0 # w7 is not open-access. We own licenses for it, and there are # licenses available right now. It's available. w7 = self._work(with_license_pool=True) w7.license_pools[0].open_access = False w7.license_pools[0].licenses_owned = 9 w7.license_pools[0].licenses_available = 5 # A normal query against Work/LicensePool finds all works. orig_q = self._db.query(Work).join(Work.license_pools) eq_(7, orig_q.count()) # only_show_ready_deliverable_works filters out everything but # w1 (owned licenses), w6 (open-access), and w7 (available # licenses) q = Lane.only_show_ready_deliverable_works(orig_q, Work) eq_(set([w1, w6, w7]), set(q.all())) # If we decide to show suppressed works, w4 shows up as well. q = Lane.only_show_ready_deliverable_works(orig_q, Work, show_suppressed=True) eq_(set([w1, w4, w6, w7]), set(q.all())) # Change site policy to hide books that can't be borrowed. with temp_config() as config: config['policies'] = { Configuration.HOLD_POLICY: Configuration.HOLD_POLICY_HIDE } # w1 no longer shows up, because although we own licenses, # no copies are available. # w4 is open-access but it's suppressed, so it still doesn't # show up. # w6 still shows up because it's an open-access work. # w7 shows up because we own licenses and copies are available. q = Lane.only_show_ready_deliverable_works(orig_q, Work) eq_(set([w6, w7]), set(q.all()))
def ceq(self, expect, url, cdns): cdns = cdns or {} with temp_config() as config: config[Configuration.INTEGRATIONS][ExternalIntegration.CDN] = cdns eq_(expect, cdnify(url))
def setup(self): super(TestExternalSearch, self).setup() with temp_config() as config: config[Configuration.INTEGRATIONS][Configuration.ELASTICSEARCH_INTEGRATION] = {} config[Configuration.INTEGRATIONS][Configuration.ELASTICSEARCH_INTEGRATION][Configuration.URL] = "http://localhost:9200" config[Configuration.INTEGRATIONS][Configuration.ELASTICSEARCH_INTEGRATION][Configuration.ELASTICSEARCH_INDEX_KEY] = "test_index" try: ExternalSearchIndex.__client = None self.search = ExternalSearchIndex() # Start with an empty index self.search.setup_index() except Exception as e: self.search = None print "Unable to set up elasticsearch index, search tests will be skipped." print e if self.search: works = [] self.moby_dick = self._work(title="Moby Dick", authors="Herman Melville", fiction=True) self.moby_dick.presentation_edition.subtitle = "Or, the Whale" self.moby_dick.presentation_edition.series = "Classics" self.moby_dick.summary_text = "Ishmael" self.moby_dick.presentation_edition.publisher = "Project Gutenberg" self.moby_dick.set_presentation_ready() works.append(self.moby_dick) self.moby_duck = self._work(title="Moby Duck", authors="Donovan Hohn", fiction=False) self.moby_duck.presentation_edition.subtitle = "The True Story of 28,800 Bath Toys Lost at Sea" self.moby_duck.summary_text = "A compulsively readable narrative" self.moby_duck.presentation_edition.publisher = "Penguin" self.moby_duck.set_presentation_ready() works.append(self.moby_duck) self.title_match = self._work(title="Match") self.title_match.set_presentation_ready() works.append(self.title_match) self.subtitle_match = self._work() self.subtitle_match.presentation_edition.subtitle = "Match" self.subtitle_match.set_presentation_ready() works.append(self.subtitle_match) self.summary_match = self._work() self.summary_match.summary_text = "Match" self.summary_match.set_presentation_ready() works.append(self.summary_match) self.publisher_match = self._work() self.publisher_match.presentation_edition.publisher = "Match" self.publisher_match.set_presentation_ready() works.append(self.publisher_match) self.tess = self._work(title="Tess of the d'Urbervilles") self.tess.set_presentation_ready() works.append(self.tess) self.tiffany = self._work(title="Breakfast at Tiffany's") self.tiffany.set_presentation_ready() works.append(self.tiffany) self.les_mis = self._work() self.les_mis.presentation_edition.title = u"Les Mis\u00E9rables" self.les_mis.set_presentation_ready() works.append(self.les_mis) self.lincoln = self._work(genre="Biography & Memoir", title="Abraham Lincoln") self.lincoln.set_presentation_ready() works.append(self.lincoln) self.washington = self._work(genre="Biography", title="George Washington") self.washington.set_presentation_ready() works.append(self.washington) self.lincoln_vampire = self._work(title="Abraham Lincoln: Vampire Hunter", genre="Fantasy") self.lincoln_vampire.set_presentation_ready() works.append(self.lincoln_vampire) self.children_work = self._work(title="Alice in Wonderland", audience=Classifier.AUDIENCE_CHILDREN) self.children_work.set_presentation_ready() works.append(self.children_work) self.ya_work = self._work(title="Go Ask Alice", audience=Classifier.AUDIENCE_YOUNG_ADULT) self.ya_work.set_presentation_ready() works.append(self.ya_work) self.adult_work = self._work(title="Still Alice", audience=Classifier.AUDIENCE_ADULT) self.adult_work.set_presentation_ready() works.append(self.adult_work) self.ya_romance = self._work(audience=Classifier.AUDIENCE_YOUNG_ADULT, genre="Romance") self.ya_romance.set_presentation_ready() works.append(self.ya_romance) self.no_age = self._work() self.no_age.summary_text = "President Barack Obama's election in 2008 energized the United States" self.no_age.set_presentation_ready() works.append(self.no_age) self.age_4_5 = self._work() self.age_4_5.target_age = NumericRange(4, 5, '[]') self.age_4_5.summary_text = "President Barack Obama's election in 2008 energized the United States" self.age_4_5.set_presentation_ready() works.append(self.age_4_5) self.age_5_6 = self._work(fiction=False) self.age_5_6.target_age = NumericRange(5, 6, '[]') self.age_5_6.set_presentation_ready() works.append(self.age_5_6) self.obama = self._work(genre="Biography & Memoir") self.obama.target_age = NumericRange(8, 8, '[]') self.obama.summary_text = "President Barack Obama's election in 2008 energized the United States" self.obama.set_presentation_ready() works.append(self.obama) self.dodger = self._work() self.dodger.target_age = NumericRange(8, 8, '[]') self.dodger.summary_text = "Willie finds himself running for student council president" self.dodger.set_presentation_ready() works.append(self.dodger) self.age_9_10 = self._work() self.age_9_10.target_age = NumericRange(9, 10, '[]') self.age_9_10.summary_text = "President Barack Obama's election in 2008 energized the United States" self.age_9_10.set_presentation_ready() works.append(self.age_9_10) self.age_2_10 = self._work() self.age_2_10.target_age = NumericRange(2, 10, '[]') self.age_2_10.set_presentation_ready() works.append(self.age_2_10) self.pride = self._work(title="Pride and Prejudice") self.pride.presentation_edition.medium = Edition.BOOK_MEDIUM self.pride.set_presentation_ready() works.append(self.pride) self.pride_audio = self._work(title="Pride and Prejudice") self.pride_audio.presentation_edition.medium = Edition.AUDIO_MEDIUM self.pride_audio.set_presentation_ready() works.append(self.pride_audio) self.sherlock = self._work(title="The Adventures of Sherlock Holmes") self.sherlock.presentation_edition.language = "en" self.sherlock.set_presentation_ready() works.append(self.sherlock) self.sherlock_spanish = self._work(title="Las Aventuras de Sherlock Holmes") self.sherlock_spanish.presentation_edition.language = "es" self.sherlock_spanish.set_presentation_ready() works.append(self.sherlock_spanish) self.search.bulk_update(works) time.sleep(2)