diff --git a/database_client/database_client.py b/database_client/database_client.py index d4e7c105..df493232 100644 --- a/database_client/database_client.py +++ b/database_client/database_client.py @@ -524,10 +524,8 @@ def get_typeahead_agencies(self, search_term: str) -> dict: @cursor_manager() def search_with_location_and_record_type( self, - state: str, + location_id: int, record_categories: Optional[list[RecordCategories]] = None, - county: Optional[str] = None, - locality: Optional[str] = None, ) -> List[dict]: """ Searches for data sources in the database. @@ -540,10 +538,8 @@ def search_with_location_and_record_type( """ optional_kwargs = {} query = DynamicQueryConstructor.create_search_query( - state=state, + location_id=location_id, record_categories=record_categories, - county=county, - locality=locality, ) self.cursor.execute(query) return self.cursor.fetchall() diff --git a/database_client/dynamic_query_constructor.py b/database_client/dynamic_query_constructor.py index ddbd8203..69673934 100644 --- a/database_client/dynamic_query_constructor.py +++ b/database_client/dynamic_query_constructor.py @@ -223,15 +223,13 @@ def generate_new_typeahead_agencies_query(search_term: str): @staticmethod def create_search_query( - state: str, + location_id: int, record_categories: Optional[list[RecordCategories]] = None, - county: Optional[str] = None, - locality: Optional[str] = None, ) -> sql.Composed: base_query = sql.SQL( """ - SELECT + SELECT DISTINCT data_sources.id, data_sources.name AS data_source_name, data_sources.description, @@ -255,14 +253,16 @@ def create_search_query( locations_expanded on agencies.location_id = locations_expanded.id INNER JOIN record_types on record_types.id = data_sources.record_type_id + LEFT JOIN + DEPENDENT_LOCATIONS DL ON DL.DEPENDENT_LOCATION_ID = LOCATIONS_EXPANDED.ID """ ) join_conditions = [] where_subclauses = [ sql.SQL( - "LOWER(locations_expanded.state_name) = LOWER({state_name})" - ).format(state_name=sql.Literal(state)), + "(locations_expanded.id = {location_id} OR DL.PARENT_LOCATION_ID = {location_id}) " + ).format(location_id=sql.Literal(location_id)), sql.SQL("data_sources.approval_status = 'approved'"), sql.SQL("data_sources.url_status NOT IN ('broken', 'none found')"), ] @@ -286,20 +286,6 @@ def create_search_query( ) ) - if county is not None: - where_subclauses.append( - sql.SQL( - "LOWER(locations_expanded.county_name) = LOWER({county_name})" - ).format(county_name=sql.Literal(county)) - ) - - if locality is not None: - where_subclauses.append( - sql.SQL( - "LOWER(locations_expanded.locality_name) = LOWER({locality})" - ).format(locality=sql.Literal(locality)) - ) - query = sql.Composed( [ base_query, diff --git a/middleware/dynamic_request_logic/post_logic.py b/middleware/dynamic_request_logic/post_logic.py index c1c5bbbd..091cfa43 100644 --- a/middleware/dynamic_request_logic/post_logic.py +++ b/middleware/dynamic_request_logic/post_logic.py @@ -56,11 +56,22 @@ def call_database_client_method(self): self.id_val = self.mp.db_client_method( self.mp.db_client, column_value_mappings=self.entry ) - except sqlalchemy.exc.IntegrityError: - FlaskResponseManager.abort( - code=HTTPStatus.CONFLICT, - message=f"{self.mp.entry_name} already exists.", - ) + except sqlalchemy.exc.IntegrityError as e: + if e.orig.sqlstate == "23505": + FlaskResponseManager.abort( + code=HTTPStatus.CONFLICT, + message=f"{self.mp.entry_name} already exists.", + ) + elif e.orig.sqlstate == "23503": + FlaskResponseManager.abort( + code=HTTPStatus.BAD_REQUEST, + message=f"{self.mp.entry_name} not found.", + ) + else: + FlaskResponseManager.abort( + code=HTTPStatus.INTERNAL_SERVER_ERROR, + message=f"Error creating {self.mp.entry_name}.", + ) def make_response(self) -> Response: return created_id_response( diff --git a/middleware/primary_resource_logic/search_logic.py b/middleware/primary_resource_logic/search_logic.py index 26e77e70..3d585b9f 100644 --- a/middleware/primary_resource_logic/search_logic.py +++ b/middleware/primary_resource_logic/search_logic.py @@ -18,7 +18,7 @@ from middleware.enums import JurisdictionSimplified, Relations, OutputFormatEnum from middleware.flask_response_manager import FlaskResponseManager from middleware.schema_and_dto_logic.primary_resource_schemas.search_schemas import ( - SearchRequests, + SearchRequestsDTO, ) from middleware.common_response_formatting import message_response from middleware.util import get_datetime_now, write_to_csv, find_root_directory @@ -100,16 +100,14 @@ def format_as_csv(ld: list[dict]) -> BytesIO: def search_wrapper( db_client: DatabaseClient, access_info: AccessInfoPrimary, - dto: SearchRequests, + dto: SearchRequestsDTO, ) -> Response: create_search_record(access_info, db_client, dto) explicit_record_categories = get_explicit_record_categories(dto.record_categories) search_results = db_client.search_with_location_and_record_type( + location_id=dto.location_id, record_categories=explicit_record_categories, - state=dto.state, # Pass modified record categories, which breaks down ALL into individual categories - county=dto.county, - locality=dto.locality, ) return send_search_results( search_results=search_results, @@ -118,13 +116,9 @@ def search_wrapper( def create_search_record(access_info, db_client, dto): - location_id = try_getting_location_id_and_raise_error_if_not_found( - db_client=db_client, - dto=dto, - ) db_client.create_search_record( user_id=access_info.get_user_id(), - location_id=location_id, + location_id=dto.location_id, # Pass originally provided record categories record_categories=dto.record_categories, ) @@ -170,7 +164,7 @@ def get_explicit_record_categories( def try_getting_location_id_and_raise_error_if_not_found( db_client: DatabaseClient, - dto: SearchRequests, + dto: SearchRequestsDTO, ) -> int: where_mappings = WhereMapping.from_dict( { @@ -228,7 +222,7 @@ def make_response(self) -> Response: def get_link_id_and_raise_error_if_not_found( - db_client: DatabaseClient, access_info: AccessInfoPrimary, dto: SearchRequests + db_client: DatabaseClient, access_info: AccessInfoPrimary, dto: SearchRequestsDTO ): location_id = try_getting_location_id_and_raise_error_if_not_found( db_client=db_client, @@ -244,18 +238,14 @@ def get_link_id_and_raise_error_if_not_found( def get_location_link_and_raise_error_if_not_found( db_client: DatabaseClient, access_info: AccessInfoPrimary, - dto: SearchRequests, + dto: SearchRequestsDTO, ): - location_id = try_getting_location_id_and_raise_error_if_not_found( - db_client=db_client, - dto=dto, - ) link_id = get_user_followed_search_link( db_client=db_client, access_info=access_info, - location_id=location_id, + location_id=dto.location_id, ) - return LocationLink(link_id=link_id, location_id=location_id) + return LocationLink(link_id=link_id, location_id=dto.location_id) class LocationLink(BaseModel): @@ -266,7 +256,7 @@ class LocationLink(BaseModel): def create_followed_search( db_client: DatabaseClient, access_info: AccessInfoPrimary, - dto: SearchRequests, + dto: SearchRequestsDTO, ) -> Response: # Get location id. If not found, not a valid location. Raise error location_link = get_location_link_and_raise_error_if_not_found( @@ -279,7 +269,7 @@ def create_followed_search( return post_entry( middleware_parameters=MiddlewareParameters( - entry_name="followed search", + entry_name="Location for followed search", relation=Relations.LINK_USER_FOLLOWED_LOCATION.value, db_client_method=DatabaseClient.create_followed_search, access_info=access_info, @@ -296,7 +286,7 @@ def create_followed_search( def delete_followed_search( db_client: DatabaseClient, access_info: AccessInfoPrimary, - dto: SearchRequests, + dto: SearchRequestsDTO, ) -> Response: # Get location id. If not found, not a valid location. Raise error location_link = get_location_link_and_raise_error_if_not_found( @@ -310,7 +300,7 @@ def delete_followed_search( return delete_entry( middleware_parameters=MiddlewareParameters( - entry_name="Followed search", + entry_name="Location for followed search", relation=Relations.LINK_USER_FOLLOWED_LOCATION.value, db_client_method=DatabaseClient.delete_followed_search, access_info=access_info, diff --git a/middleware/schema_and_dto_logic/primary_resource_schemas/search_schemas.py b/middleware/schema_and_dto_logic/primary_resource_schemas/search_schemas.py index 57153d18..a6050b6f 100644 --- a/middleware/schema_and_dto_logic/primary_resource_schemas/search_schemas.py +++ b/middleware/schema_and_dto_logic/primary_resource_schemas/search_schemas.py @@ -6,7 +6,7 @@ from middleware.enums import OutputFormatEnum from middleware.schema_and_dto_logic.schema_helpers import create_get_many_schema -from middleware.schema_and_dto_logic.util import get_json_metadata +from middleware.schema_and_dto_logic.util import get_json_metadata, get_query_metadata from utilities.common import get_enums_from_string from utilities.enums import RecordCategories, SourceMappingEnum, ParserLocation @@ -18,13 +18,9 @@ def transform_record_categories(value: str) -> Optional[list[RecordCategories]]: class SearchRequestSchema(Schema): - state = fields.Str( - required=True, - metadata={ - "description": "The state of the search.", - "source": SourceMappingEnum.QUERY_ARGS, - "location": ParserLocation.QUERY.value, - }, + location_id = fields.Int( + required=False, + metadata=get_query_metadata("The location ID of the search."), ) record_categories = fields.Str( required=False, @@ -40,22 +36,6 @@ class SearchRequestSchema(Schema): "location": ParserLocation.QUERY.value, }, ) - county = fields.Str( - required=False, - metadata={ - "description": "The county of the search. If empty, all counties for the given state will be searched.", - "source": SourceMappingEnum.QUERY_ARGS, - "location": ParserLocation.QUERY.value, - }, - ) - locality = fields.Str( - required=False, - metadata={ - "description": "The locality of the search. If empty, all localities for the given county will be searched.", - "source": SourceMappingEnum.QUERY_ARGS, - "location": ParserLocation.QUERY.value, - }, - ) output_format = fields.Enum( required=False, enum=OutputFormatEnum, @@ -68,12 +48,6 @@ class SearchRequestSchema(Schema): }, ) - @validates_schema - def validate_location_info(self, data, **kwargs): - if data.get("locality") and not data.get("county"): - raise ValidationError( - "If locality is provided, county must also be provided." - ) class SearchResultsInnerSchema(Schema): @@ -210,9 +184,7 @@ class FollowSearchResponseSchema(Schema): ) -class SearchRequests(BaseModel): - state: str +class SearchRequestsDTO(BaseModel): + location_id: int record_categories: Optional[list[RecordCategories]] = None - county: Optional[str] = None - locality: Optional[str] = None output_format: Optional[OutputFormatEnum] = None diff --git a/resources/endpoint_schema_config.py b/resources/endpoint_schema_config.py index 3db2f2ee..5e80b791 100644 --- a/resources/endpoint_schema_config.py +++ b/resources/endpoint_schema_config.py @@ -91,7 +91,7 @@ from middleware.schema_and_dto_logic.primary_resource_schemas.search_schemas import ( SearchRequestSchema, GetUserFollowedSearchesSchema, - SearchRequests, + SearchRequestsDTO, SearchResponseSchema, ) from middleware.schema_and_dto_logic.common_schemas_and_dtos import ( @@ -278,9 +278,9 @@ def schema_config_with_message_output( ) SEARCH_FOLLOW_UPDATE = EndpointSchemaConfig( input_schema=SearchRequestSchema( - exclude=["record_categories"], + exclude=["record_categories", "output_format"], ), - input_dto_class=SearchRequests, + input_dto_class=SearchRequestsDTO, ) @@ -387,7 +387,7 @@ class SchemaConfigs(Enum): SEARCH_LOCATION_AND_RECORD_TYPE_GET = EndpointSchemaConfig( input_schema=SearchRequestSchema(), primary_output_schema=SearchResponseSchema(), - input_dto_class=SearchRequests, + input_dto_class=SearchRequestsDTO, ) SEARCH_FOLLOW_GET = EndpointSchemaConfig( primary_output_schema=GetUserFollowedSearchesSchema(), diff --git a/tests/helper_scripts/helper_classes/RequestValidator.py b/tests/helper_scripts/helper_classes/RequestValidator.py index dabe653b..5dc9a058 100644 --- a/tests/helper_scripts/helper_classes/RequestValidator.py +++ b/tests/helper_scripts/helper_classes/RequestValidator.py @@ -268,18 +268,14 @@ def update_permissions( def search( self, headers: dict, - state: str, + location_id: int, record_categories: Optional[list[RecordCategories]] = None, - county: Optional[str] = None, - locality: Optional[str] = None, format: Optional[OutputFormatEnum] = OutputFormatEnum.JSON, ): endpoint_base = "/search/search-location-and-record-type" query_params = self._get_search_query_params( - county=county, - locality=locality, + location_id=location_id, record_categories=record_categories, - state=state, ) query_params.update({} if format is None else {"output_format": format.value}) endpoint = add_query_params( @@ -295,39 +291,28 @@ def search( ) @staticmethod - def _get_search_query_params(county, locality, record_categories, state): + def _get_search_query_params(record_categories, location_id: int): query_params = { - "state": state, + "location_id": location_id, } if record_categories is not None: query_params["record_categories"] = ",".join( [rc.value for rc in record_categories] ) - update_if_not_none( - dict_to_update=query_params, - secondary_dict={ - "county": county, - "locality": locality, - }, - ) return query_params def follow_search( self, headers: dict, - state: str, + location_id: int, record_categories: Optional[list[RecordCategories]] = None, - county: Optional[str] = None, - locality: Optional[str] = None, expected_json_content: Optional[dict] = None, expected_response_status: HTTPStatus = HTTPStatus.OK, ): endpoint_base = "/api/search/follow" query_params = self._get_search_query_params( - county=county, - locality=locality, + location_id=location_id, record_categories=record_categories, - state=state, ) endpoint = add_query_params( url=endpoint_base, diff --git a/tests/integration/test_search.py b/tests/integration/test_search.py index f42322ec..694de984 100644 --- a/tests/integration/test_search.py +++ b/tests/integration/test_search.py @@ -1,7 +1,9 @@ import csv +from dataclasses import dataclass from http import HTTPStatus from typing import Optional +import pytest from marshmallow import Schema from database_client.enums import LocationType @@ -31,17 +33,35 @@ TEST_COUNTY = "Allegheny" TEST_LOCALITY = "Pittsburgh" +@dataclass +class SearchTestSetup: + tdc: TestDataCreatorFlask + location_id: int + tus: TestUserSetup -def test_search_get(test_data_creator_flask: TestDataCreatorFlask): +@pytest.fixture +def search_test_setup(test_data_creator_flask: TestDataCreatorFlask): tdc = test_data_creator_flask - tus = tdc.standard_user() + return SearchTestSetup( + tdc=tdc, + location_id=tdc.db_client.get_location_id( + where_mappings={ + "state_name": TEST_STATE, + "county_name": TEST_COUNTY, + "locality_name": TEST_LOCALITY, + }), + tus=tdc.standard_user(), + ) + +def test_search_get(search_test_setup: SearchTestSetup): + sts = search_test_setup + tdc = sts.tdc + tus = sts.tus def search(record_format: Optional[OutputFormatEnum] = OutputFormatEnum.JSON): return tdc.request_validator.search( headers=tus.api_authorization_header, - state=TEST_STATE, - county=TEST_COUNTY, - locality=TEST_LOCALITY, + location_id=sts.location_id, record_categories=[RecordCategories.POLICE], format=record_format, ) @@ -102,22 +122,21 @@ def search(record_format: Optional[OutputFormatEnum] = OutputFormatEnum.JSON): def test_search_get_record_categories_all( - test_data_creator_flask: TestDataCreatorFlask, + search_test_setup: SearchTestSetup, ): """ All record categories can be provided in one of two ways: By explicitly passing an "ALL" value in the `record_categories` parameter Or by providing every non-ALL value in the `record_categories` parameter """ - tdc = test_data_creator_flask - tus = tdc.standard_user() + sts = search_test_setup + tdc = sts.tdc + tus = sts.tus def run_search(record_categories: list[RecordCategories]) -> dict: return tdc.request_validator.search( headers=tus.api_authorization_header, - state=TEST_STATE, - county=TEST_COUNTY, - locality=TEST_LOCALITY, + location_id=sts.location_id, record_categories=record_categories if len(record_categories) > 0 else None, ) @@ -137,16 +156,14 @@ def run_search(record_categories: list[RecordCategories]) -> dict: assert data_empty["count"] == data_all_explicit["count"] -def test_search_follow(test_data_creator_flask): - tdc = test_data_creator_flask - +def test_search_follow(search_test_setup: SearchTestSetup): + sts = search_test_setup + tdc = sts.tdc # Create standard user - tus_1 = tdc.standard_user() + tus_1 = sts.tus location_to_follow = { - "state": TEST_STATE, - "county": TEST_COUNTY, - "locality": TEST_LOCALITY, + "location_id": sts.location_id, } url_for_following = add_query_params( SEARCH_FOLLOW_BASE_ENDPOINT, location_to_follow @@ -210,11 +227,9 @@ def call_follow_get( # User should try to follow a nonexistent location and be denied tdc.request_validator.follow_search( headers=tus_1.jwt_authorization_header, - state=TEST_STATE, - county=TEST_COUNTY, - locality="Purtsburgh", + location_id=-1, expected_response_status=HTTPStatus.BAD_REQUEST, - expected_json_content={"message": "Location not found."}, + expected_json_content={"message": "Location for followed search not found."}, ) # User should try to follow an extant location and be granted @@ -237,7 +252,11 @@ def follow_extant_location( tus=tus_1, expected_json_content={ "metadata": {"count": 1}, - "data": [location_to_follow], + "data": [{ + "state": TEST_STATE, + "county": TEST_COUNTY, + "locality": TEST_LOCALITY, + }], "message": "Followed searches found.", }, ) @@ -254,7 +273,7 @@ def follow_extant_location( call_follow_delete( tus=tus_1, endpoint=url_for_following, - expected_json_content={"message": "Followed search deleted."}, + expected_json_content={"message": "Location for followed search deleted."}, ) # The original user, on checking their current follows, should now find no locations diff --git a/tests/integration/test_user_profile.py b/tests/integration/test_user_profile.py index 07bf2850..f221b047 100644 --- a/tests/integration/test_user_profile.py +++ b/tests/integration/test_user_profile.py @@ -45,13 +45,13 @@ def test_user_profile_get_by_id(test_data_creator_flask: TestDataCreatorFlask): # Create a recent search tdc.request_validator.search( headers=tus.api_authorization_header, - state="Pennsylvania", + location_id=tdc.db_client.get_location_id(where_mappings={"state_name": "Pennsylvania", "county_name": None, "locality_name": None}), ) # Have the user follow a search tdc.request_validator.follow_search( headers=tus.jwt_authorization_header, - state="California", + location_id=tdc.db_client.get_location_id(where_mappings={"state_name": "California", "county_name": None, "locality_name": None}), ) # Have the user create a data request diff --git a/tests/resources/test_Search.py b/tests/resources/test_Search.py deleted file mode 100644 index 4c97b5a6..00000000 --- a/tests/resources/test_Search.py +++ /dev/null @@ -1,81 +0,0 @@ -import pytest - -from database_client.database_client import DatabaseClient -from middleware.access_logic import AccessInfoPrimary -from middleware.schema_and_dto_logic.primary_resource_schemas.search_schemas import ( - SearchRequests, -) -from tests.conftest import client_with_mock_db, bypass_authentication_required -from tests.helper_scripts.constants import TEST_RESPONSE -from tests.helper_scripts.common_asserts import assert_is_test_response -from utilities.enums import RecordCategories - - -def mock_search_wrapper_all_parameters( - db_client: DatabaseClient, - access_info: AccessInfoPrimary, - dto: SearchRequests, -): - assert dto.state == "Pennsylvania" - assert dto.record_categories == [RecordCategories.POLICE] - assert dto.county == "Allegheny" - assert dto.locality == "Pittsburgh" - - return TEST_RESPONSE - - -def mock_search_wrapper_multiple_parameters( - db_client: DatabaseClient, - access_info: AccessInfoPrimary, - dto: SearchRequests, -): - assert dto.state == "Pennsylvania" - assert dto.record_categories == [RecordCategories.POLICE, RecordCategories.RESOURCE] - assert dto.county is None - assert dto.locality is None - - return TEST_RESPONSE - - -def mock_search_wrapper_minimal_parameters( - db_client: DatabaseClient, - access_info: AccessInfoPrimary, - dto: SearchRequests, -): - assert dto.state == "Pennsylvania" - assert dto.record_categories == [RecordCategories.ALL] - assert dto.county is None - assert dto.locality is None - - return TEST_RESPONSE - - -@pytest.mark.parametrize( - "url, mock_search_wrapper_function", - ( - ( - "/search/search-location-and-record-type?state=Pennsylvania&county=Allegheny&locality=Pittsburgh&record_categories=Police%20%26%20Public%20Interactions", - mock_search_wrapper_all_parameters, - ), - ( - "/search/search-location-and-record-type?state=Pennsylvania", - mock_search_wrapper_minimal_parameters, - ), - ( - "/search/search-location-and-record-type?state=Pennsylvania&record_categories=Police+%26+Public+interactions%2CAgency-published+resources", - mock_search_wrapper_multiple_parameters, - ), - ), -) -def test_search_get_parameters( - url, - mock_search_wrapper_function, - client_with_mock_db, - monkeypatch, - bypass_authentication_required, -): - - monkeypatch.setattr("resources.Search.search_wrapper", mock_search_wrapper_function) - - response = client_with_mock_db.client.get(url) - assert_is_test_response(response) diff --git a/tests/test_database_client.py b/tests/test_database_client.py index 07d21b4f..26d3cfad 100644 --- a/tests/test_database_client.py +++ b/tests/test_database_client.py @@ -621,13 +621,29 @@ def test_search_with_location_and_record_types_real_data(live_database_client): :param live_database_client: :return: """ - state_parameter = "PeNnSylvaNia" # Additionally testing for case-insensitivity + state_parameter = "Pennsylvania" # Additionally testing for case-insensitivity record_type_parameter = RecordCategories.AGENCIES - county_parameter = "ALLEGHENY" - locality_parameter = "pittsburgh" + county_parameter = "Allegheny" + locality_parameter = "Pittsburgh" + + def search(state, record_categories = None, county = None, locality = None): + location_id = live_database_client.get_location_id( + where_mappings={ + "state_name": state, + "county_name": county, + "locality_name": locality, + } + ) + if record_categories is not None: + additional_kwargs = {"record_categories": record_categories} + else: + additional_kwargs = {} + return live_database_client.search_with_location_and_record_type( + location_id=location_id, **additional_kwargs + ) SRLC = len( - live_database_client.search_with_location_and_record_type( + search( state=state_parameter, record_categories=[record_type_parameter], county=county_parameter, @@ -635,27 +651,27 @@ def test_search_with_location_and_record_types_real_data(live_database_client): ) ) S = len( - live_database_client.search_with_location_and_record_type(state=state_parameter) + search(state=state_parameter) ) SR = len( - live_database_client.search_with_location_and_record_type( + search( state=state_parameter, record_categories=[record_type_parameter] ) ) SRC = len( - live_database_client.search_with_location_and_record_type( + search( state=state_parameter, record_categories=[record_type_parameter], county=county_parameter, ) ) SCL = len( - live_database_client.search_with_location_and_record_type( + search( state=state_parameter, county=county_parameter, locality=locality_parameter ) ) SC = len( - live_database_client.search_with_location_and_record_type( + search( state=state_parameter, county=county_parameter ) ) @@ -670,7 +686,9 @@ def test_search_with_location_and_record_types_real_data(live_database_client): def test_search_with_location_and_record_types_real_data_multiple_records( live_database_client, ): - state_parameter = "Pennsylvania" + location_id = live_database_client.get_location_id( + where_mappings={"state_name": "Pennsylvania", "county_name": None, "locality_name": None} + ) record_categories = [] last_count = 0 # Exclude the ALL pseudo-category @@ -682,14 +700,14 @@ def test_search_with_location_and_record_types_real_data_multiple_records( for record_category in applicable_record_categories: record_categories.append(record_category) results = live_database_client.search_with_location_and_record_type( - state=state_parameter, record_categories=record_categories + location_id=location_id, record_categories=record_categories ) - assert len(results) > last_count + assert len(results) > last_count, f"{record_category} failed (total record_categories: {len(record_categories)})" last_count = len(results) # Finally, check that all record_types is equivalent to no record types in terms of number of results results = live_database_client.search_with_location_and_record_type( - state=state_parameter + location_id=location_id ) assert len(results) == last_count