def test_all_then_first(self, mock_search): """Verify that calling first on a ResourceMatcher that has already been called with .all() doesn't fail.""" # Set up mock response. mock_search.return_value = { "pagination": { "totalItems": 2 }, "list": [{ "id": 1, "name": "test1" }, { "id": 2, "name": "test2" }], } # Run a search and try pulling records. results = matching.ResourceMatcher(models.Contributor, items_per_page=10) all_results = results.all() first_result = results.first() # Check expected results. for result in all_results: assert isinstance(result, models.Contributor) assert isinstance(first_result, models.Contributor)
def test_all(self, mock_search): # Set up mock response. mock_search.return_value = { "pagination": { "totalItems": 2 }, "list": [{ "id": 1, "name": "test1" }, { "id": 2, "name": "test2" }], } # Run Search. results = matching.ResourceMatcher(models.Contributor, items_per_page=10).all() results = list(results) # Check results are expected type. for result in results: assert isinstance(result, models.Contributor) # Check result list is expected length. assert len(results) == 2
def match(cls, **kwargs) -> matching.ResourceMatcher: """Find a resource using passed keyword arguments. Note: If called without arguments, returns all records in the DA . """ # Check that we no invalid search terms were passed. for key in kwargs: if key not in cls.__fields__.keys(): raise exceptions.InvalidSearchFieldError # Prepare the "term" search field. # If we've got both a name and a value, join them. if kwargs.get("name") and kwargs.get("value"): kwargs["term"] = " ".join( [kwargs.pop("name"), kwargs.pop("value")]) # Otherwise, treat the one that exists as the term. elif kwargs.get("name"): kwargs["term"] = kwargs.pop("name") elif kwargs.get("value"): kwargs["term"] = kwargs.pop("value") return matching.ResourceMatcher(cls, **kwargs)
def test_repr(self, mock_get_by_id): # Run match mock_get_by_id.return_value = {"list": [{"id": 1}]} test_match = matching.ResourceMatcher(models.Resource, id=1) assert ( str(test_match) == "ResourceMatcher(model=<class 'digitalarchive.models.Resource'>, query={'id': 1}, count=1)" )
def test_match_id_field(self, mock_get_by_id): """Test Search API called with proper params.""" # Run match mock_get_by_id.return_value = {"list": [{"id": 1}]} test_match = matching.ResourceMatcher(models.Resource, id=1) # Check search called with proper params. mock_get_by_id.assert_called_once() # Inspect list for proper data match_results = list(test_match.list) assert len(match_results) == 1 assert isinstance(match_results[0], models.Resource)
def test_match_record_by_id(self, mock_api_get): # pylint: disable=protected-access # instantiate matcher and reset mock, them run just the method. test_matcher = matching.ResourceMatcher(models.Publisher, id=1) mock_api_get.reset_mock() # Explicitly call record ID method test_response = test_matcher._record_by_id() # Check api called with correct params. mock_api_get.assert_called_with(endpoint=models.Publisher.endpoint, resource_id=1) # check response is correct format assert test_response["list"][0] is mock_api_get()
def test_match_by_keyword_single_page(self, mock_search): # Set up mock response. mock_search.return_value = { "pagination": { "totalItems": 1 }, "list": [unittest.mock.MagicMock()], } matching.ResourceMatcher(models.Publisher, name="test_name") mock_search.assert_called_with( model=models.Publisher.endpoint, params={ "name": "test_name", "itemsPerPage": 200 }, )
def test_match_by_keyword_multi_page(self, mock_search, mock_get_all): # Set up mock response. mock_search.return_value = { "pagination": { "totalItems": 2 }, "list": [unittest.mock.MagicMock(), unittest.mock.MagicMock()], } # Run Search. matching.ResourceMatcher(models.Publisher, items_per_page=1, name="test_name") # Verify generator function called. mock_get_all.assert_called_with(mock_search())
def test_get_all_search_results(self, mock_search): # Set up mock response. results_page_1 = { "pagination": { "page": 1, "totalPages": 2, "totalItems": 2 }, "list": [{ "id": 1, "name": "test1", "slug": "testslug" }], } results_page_2 = { "pagination": { "page": 2, "totalPages": 2, "totalItems": 2 }, "list": [{ "id": 2, "name": "test2", "slug": "testslug" }], } mock_search.side_effect = [ results_page_1, results_page_1, results_page_2 ] test_matcher = matching.ResourceMatcher(models.Collection, items_per_page=1, name="test_name") mock_search.reset_mock() mock_search.side_effect = [results_page_1, results_page_2] # Trigger function under test. results = list(test_matcher.list) # Check result is as intended assert len(results) == 2 for result in results: assert isinstance(result, models.Collection)
def test_subject_pagination(self, mock_api_search): mock_api_search.return_value = { "pagination": { "totalItems": 2 }, "list": [unittest.mock.MagicMock(), unittest.mock.MagicMock()], } test_match = matching.ResourceMatcher(models.Subject, name="test_name", items_per_page=1) # Confirm the list attribute is properly generated. try: test_list = test_match.list except AttributeError: self.fail()
def test_first(self, mock_get_by_id): # Run match mock_get_by_id.return_value = {"list": [{"id": 1}]} test_match = matching.ResourceMatcher(models.Resource, id=1).first() assert isinstance(test_match, models.Resource)
def match(cls, **kwargs) -> matching.ResourceMatcher: """ Search for a Document by keyword, or fetch one by ID. Matching on the Document model runs a full-text search using keywords passed via the title and description keywords. Results can also be limited by dates or by related records, as described below. Note: Title and description keywords are not searched for individually. All non-date or child record searches are concatenated to single querystring. Note: Collection and other related record searches use `INNER JOIN` logic when passed multiple related resources. **Allowed search fields:** Args: title (:obj:`str`, optional): Title search keywords. description (:obj:`str`, optional): Title search keywords. start_date (:class:`datetime.date`, optional): Return only Documents with a `doc_date` after the passed `start_date`. end_date (:class:`datetime.date`, optional): Return only Documents with a `doc_date` before the passed `end_date`. collections (:obj:`list` of :class:`digitalarchive.models.Collection`, optional): Restrict results to Documents contained in all of the passed Collections. publishers (:obj:`list` of :class:`digitalarchive.models.Publisher`, optional): Restrict results to Documents published by all of the passed Publishers. repositories (:obj:`list` of :class:`digitalarchive.models.Repository`, optional) Restrict results to Documents contained in all of the passed Repositories. coverages (:obj:`list` of :class:`digitalarchive.models.Coverage`, optional) Restrict results to Documents relating to all of the passed geographical Coverages. subjects (:obj:`list` of :class:`digitalarchive.models.Subject`) Restrict results to Documents tagged with all of the passed subjects contributors (:obj:`list of :class:`digitalarchive.models.Contributor`) Restrict results to Documents whose authors include all of the passed contributors. donors (list(:class:`digitalarchive.models.Donor`)) Restrict results to Documents who were obtained or translated with support from all of the passed donors. languages (:class:`digitalarchive.models.Language` or str) Restrict results to Documents by language of original document. If passing a string, you must pass an ISO 639-2/B language code. translation (:class:`digitalarchive.models.Translation`) Restrict results to Documents for which there is a translation available in the passed Language. theme (:class:`digitalarchive.models.Theme`) Restrict results to Documents belonging to the passed Theme. Returns: An instance of (:class:`digitalarchive.matching.ResourceMatcher`) containing any records responsive to the search. """ # Limit search to only Documents (this excludes Collections from search result). kwargs["model"] = "Record" # Check that search keywords are valid. allowed_search_fields = [ *cls.__fields__.keys(), "start_date", "end_date", "themes", "model", ] for key in kwargs: if key not in allowed_search_fields: logging.error( f"[!] {key} is not a valid search term for {cls}. Valid terms: {allowed_search_fields}" ) raise exceptions.InvalidSearchFieldError # Process date searches if they are present. if any(key in kwargs.keys() for key in ["start_date", "end_date"]): kwargs = Document._process_date_searches(kwargs) # Process language searches if they are present. if "languages" in kwargs.keys(): kwargs = Document._process_language_search(kwargs) # Process any related model searches. if any(key in kwargs.keys() for key in [ "collections", "publishers", "repositories", "original_coverages", "subjects", "contributors", "donors", "languages", "translations", "themes", ]): kwargs = Document._process_related_model_searches(kwargs) # Prepare the 'q' fulltext search field. keywords = [] for field in ["name", "title", "description", "slug", "q"]: if kwargs.get(field) is not None: keywords.append(kwargs.pop(field)) kwargs["q"] = " ".join(keywords) # Reformat fields that accept lists. This makes the queries inner joins rather than union all. for field in [ "donor", "subject", "contributor", "coverage", "collection" ]: if field in kwargs.keys(): kwargs[f"{field}[]"] = kwargs.pop(field) # Run the match. return matching.ResourceMatcher(cls, **kwargs)