Beispiel #1
0
    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)
Beispiel #2
0
    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))
Beispiel #3
0
 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)
Beispiel #6
0
    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)
Beispiel #8
0
    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))
Beispiel #9
0
        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)
Beispiel #10
0
    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())
Beispiel #11
0
    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()))
Beispiel #12
0
 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)