diff --git a/apps/utils/locust_tests/common/db.py b/apps/utils/locust_tests/common/db.py index 1c7e4a40c4..43df7c5b2d 100644 --- a/apps/utils/locust_tests/common/db.py +++ b/apps/utils/locust_tests/common/db.py @@ -247,112 +247,6 @@ def get_contract_ids(uri: str, table_sample_pct: Optional[float] = None) -> List return [contract for contract in unfiltered_contracts if contract["id"]] -def get_pac_hashed_mbis(uri: str) -> List: - """ - Return a list of unique hashed MBIs that represent a diverse set of FISS and MCS - claims over a range of claim statuses. - - We anticipate that fields will have a mixture of blank vs non-blank values based on the status codes received. - - By selecting MBIs that are related to claims with varying status codes, we can get a good mixture of claim data - elements, better testing our FHIR transformers' ability to correctly render them. - """ - per_status_max = int(LIMIT / 40) # Based on ~40 distinct status values between FISS/MCS - - sub_select_day_age = 30 - - """ - selects FISS records only from the last N days - """ - fiss_sub_query = ( - "select * from rda.fiss_claims where last_updated > current_date - interval" - f" '{sub_select_day_age}' day and mbi_id is not null " - ) - - """ - Selects FISS rows partitioned by status - """ - fiss_partition_sub_query = ( - "select fiss.*, ROW_NUMBER() " - " over (partition by fiss.curr_status order by fiss.last_updated desc) " - f"from ({fiss_sub_query}) as fiss " - ) - - """ - Selects the mbi ids from N of each FISS status - """ - fiss_mbi_sub_query = ( - "select fiss_partition.mbi_id " - f"from ({fiss_partition_sub_query}) fiss_partition " - f"where fiss_partition.row_number <= {per_status_max} " - ) - - """ - selects MCS records only from the last N days - """ - mcs_sub_query = ( - "select * from rda.mcs_claims where last_updated > current_date - interval" - f" '{sub_select_day_age}' day and mbi_id is not null " - ) - - """ - Selects MCS rows partitioned by status - """ - mcs_partition_sub_query = ( - "select mcs.*, ROW_NUMBER() " - " over (partition by mcs.idr_status_code order by mcs.last_updated desc) " - f"from ({mcs_sub_query}) as mcs " - ) - - """ - Selects the mbi ids from N of each MCS status - """ - mcs_mbi_sub_query = ( - "select mcs_partition.mbi_id " - f"from ({mcs_partition_sub_query}) mcs_partition " - f"where mcs_partition.row_number <= {per_status_max} " - ) - - """ - Selects the distinct mbis from both fiss and mcs subqueries - """ - distinct_type_status_mbis = ( - "select distinct type_status.mbi_id " - f"from ({fiss_mbi_sub_query} union {mcs_mbi_sub_query}) as type_status " - ) - - mbi_query = ( - # Get up to N distinct MBI hashes - "select mbi.hash " - "from ( " - # Subquery sorts by source to weight 'filler' MBIs last - " select union_select.mbi_id " - " from ( " - # Select up to N of the newest claims for each distinct FISS and MCS status value - " select src.mbi_id, 1 as source_order " - f" from ({distinct_type_status_mbis}) src " - " union " - # Select whatever MBIs as filler - " select distinct mbi.mbi_id, 2 as source_order " - " from ( " - " select recent_mbis.* " - " from rda.mbi_cache as recent_mbis " - f" where recent_mbis.mbi_id not in ({distinct_type_status_mbis}) " - " order by recent_mbis.last_updated desc " - " ) as mbi " - f" limit {LIMIT} " - " ) as union_select " - " order by union_select.source_order " - ") sources " - "left join rda.mbi_cache as mbi on mbi.mbi_id = sources.mbi_id " - f"limit {LIMIT}" - ) - - # intentionally reversing the query results, as the important mbis to test - # will be at the beginning of the result set and BFDUserBase will pop items - # off of the end of the list - return [str(r[0]) for r in reversed(_execute(uri, mbi_query))] - def get_pac_mbis(uri: str) -> List: """ @@ -461,20 +355,6 @@ def get_pac_mbis(uri: str) -> List: return [str(r[0]) for r in reversed(_execute(uri, mbi_query))] -def get_pac_hashed_mbis_smoketest(uri: str) -> list[str]: - """Gets the top LIMIT MBI hashes from the rda table's MBI cache for use with the PACA smoketests - - Args: - uri (str): The database connection string - - Returns: - list[str]: A list of MBI hashes - """ - smoketest_mbi_query = f"select hash from rda.mbi_cache limit {LIMIT}" - - return [str(r[0]) for r in _execute(uri, smoketest_mbi_query)] - - def get_pac_mbis_smoketest(uri: str) -> list[str]: """Gets the top LIMIT MBI from the rda table's MBI cache for use with the PACA smoketests diff --git a/apps/utils/locust_tests/high_volume_suite_post.py b/apps/utils/locust_tests/high_volume_suite_post.py new file mode 100644 index 0000000000..0786dfa8d2 --- /dev/null +++ b/apps/utils/locust_tests/high_volume_suite_post.py @@ -0,0 +1,485 @@ +"""High Volume Load test suite for BFD Server endpoints.""" +import inspect +import random +import sys +from typing import ( + Callable, + Collection, + Dict, + List, + Optional, + Protocol, + Set, + Type, + TypeVar, +) + +from locust import TaskSet, User, events, tag, task +from locust.env import Environment + +from common import data, db +from common.bfd_user_base import BFDUserBase +from common.locust_utils import is_distributed, is_locust_master +from common.user_init_aware_load_shape import UserInitAwareLoadShape + +TaskT = TypeVar("TaskT", Callable[..., None], Type["TaskSet"]) +MASTER_BENE_IDS: Collection[str] = [] +MASTER_CONTRACT_DATA: Collection[Dict[str, str]] = [] +MASTER_MBIS: Collection[str] = [] +TAGS: Set[str] = set() +EXCLUDE_TAGS: Set[str] = set() + + +@events.test_start.add_listener +def _(environment: Environment, **kwargs): + if ( + is_distributed(environment) + and is_locust_master(environment) + or not environment.parsed_options + ): + return + + # See https://docs.locust.io/en/stable/extending-locust.html#test-data-management + # for Locust's documentation on the test data management pattern used here + global MASTER_BENE_IDS + MASTER_BENE_IDS = data.load_from_parsed_opts( + environment.parsed_options, + db.get_bene_ids, + use_table_sample=True, + data_type_name="bene_ids", + ) + + global TAGS + TAGS = ( + set(environment.parsed_options.locust_tags.split()) + if hasattr(environment.parsed_options, "locust_tags") + else set() + ) + + global EXCLUDE_TAGS + EXCLUDE_TAGS = ( + set(environment.parsed_options.locust_exclude_tags.split()) + if hasattr(environment.parsed_options, "locust_exclude_tags") + else set() + ) + + global MASTER_CONTRACT_DATA + MASTER_CONTRACT_DATA = data.load_from_parsed_opts( + environment.parsed_options, + db.get_contract_ids, + use_table_sample=True, + data_type_name="contract_data", + ) + + global MASTER_MBIS + MASTER_MBIS = data.load_from_parsed_opts( + environment.parsed_options, + db.get_mbis, + use_table_sample=True, + data_type_name="mbis", + ) + + +class TestLoadShape(UserInitAwareLoadShape): + pass + + +class TaskHolder(Protocol[TaskT]): + tasks: List[TaskT] + + +class HighVolumeTaskSet(TaskSet): + @property + def user(self) -> "HighVolumeUser": + # This forces the type of self.user for all derived TaskSets to narrow to HighVolumeUser, + # thus giving correct type hinting for all of HighVolumeUser's properties. + return self._high_volume_user + + def __init__(self, parent: User) -> None: + if not isinstance(parent, HighVolumeUser): + raise ValueError( + f"User is {type(self.user).__name__}; expected {type(HighVolumeUser).__name__}" + ) + self._high_volume_user: HighVolumeUser = parent + super().__init__(parent) + + +EOB_TAG = "eob" + + +@tag(EOB_TAG) +@task +class EobTaskSet(HighVolumeTaskSet): + @tag("eob_test_id_count_type_pde_v1", "v1") + @task + def eob_test_id_count_type_pde_v1(self): + """Explanation of Benefit search by ID, type PDE, paginated""" + self.user.run_task_by_parameters( + base_path="/v1/fhir/ExplanationOfBenefit/_search", + body={ + "patient": self.user.bene_ids.pop(), + "_count": "50", + "_types": "PDE", + }, + name="/v1/fhir/ExplanationOfBenefit/_search search by id / type = PDE / count = 50", + ) + + @tag("eob_test_id_last_updated_count_v1", "v1") + @task + def eob_test_id_last_updated_count_v1(self): + """Explanation of Benefit search by ID, last updated, paginated""" + self.user.run_task_by_parameters( + base_path="/v1/fhir/ExplanationOfBenefit/_search", + body={ + "patient": self.user.bene_ids.pop(), + "_count": "100", + "_lastUpdated": f"gt{self.user.last_updated}", + }, + name="/v1/fhir/ExplanationOfBenefit/_search search by id / lastUpdated / count = 100", + ) + + @tag("eob_test_id_include_tax_number_last_updated_v1", "v1") + @task + def eob_test_id_include_tax_number_last_updated_v1(self): + """Explanation of Benefit search by ID, Last Updated, Include Tax Numbers""" + self.user.run_task_by_parameters( + base_path="/v1/fhir/ExplanationOfBenefit/_search", + body={ + "patient": self.user.bene_ids.pop(), + "_lastUpdated": f"gt{self.user.last_updated}", + "_IncludeTaxNumbers": "true", + }, + name="/v1/fhir/ExplanationOfBenefit/_search search by id / lastUpdated / includeTaxNumbers", + ) + + @tag("eob_test_id_last_updated_v1", "v1") + @task + def eob_test_id_last_updated_v1(self): + """Explanation of Benefit search by ID, Last Updated""" + self.user.run_task_by_parameters( + base_path="/v1/fhir/ExplanationOfBenefit/_search", + body={ + "patient": self.user.bene_ids.pop(), + "_lastUpdated": f"gt{self.user.last_updated}", + }, + name="/v1/fhir/ExplanationOfBenefit/_search search by id / lastUpdated", + ) + + @tag("eob_test_id_v1", "v1") + @task + def eob_test_id_v1(self): + """Explanation of Benefit search by ID""" + self.user.run_task_by_parameters( + base_path="/v1/fhir/ExplanationOfBenefit/_search", + body={ + "patient": self.user.bene_ids.pop(), + }, + name="/v1/fhir/ExplanationOfBenefit/_search search by id", + ) + + @tag("eob_test_id", "v2") + @task + def eob_test_id(self): + """Explanation of Benefit search by ID""" + self.user.run_task_by_parameters( + base_path="/v2/fhir/ExplanationOfBenefit/_search", + body={ + "patient": self.user.bene_ids.pop(), + }, + name="/v2/fhir/ExplanationOfBenefit/_search search by id", + ) + + @tag("eob_test_id_count", "v2") + @task + def eob_test_id_count(self): + """Explanation of Benefit search by ID, Paginated""" + self.user.run_task_by_parameters( + base_path="/v2/fhir/ExplanationOfBenefit/_search", + body={ + "patient": self.user.bene_ids.pop(), + "_count": "10", + }, + name="/v2/fhir/ExplanationOfBenefit/_search search by id / count=10", + ) + + @tag("eob_test_id_include_tax_number_last_updated", "v2") + @task + def eob_test_id_include_tax_number_last_updated(self): + """Explanation of Benefit search by ID, Last Updated, Include Tax Numbers""" + self.user.run_task_by_parameters( + base_path="/v2/fhir/ExplanationOfBenefit/_search", + body={ + "_lastUpdated": f"gt{self.user.last_updated}", + "patient": self.user.bene_ids.pop(), + "_IncludeTaxNumbers": "true", + }, + name="/v2/fhir/ExplanationOfBenefit/_search search by id / lastUpdated / includeTaxNumbers", + ) + +PATIENT_TAG = "patient" + + +@tag(PATIENT_TAG) +@task +class PatientTaskSet(HighVolumeTaskSet): + @tag("patient_test_coverage_contract_v1", "v1") + @task + def patient_test_coverage_contract_v1(self): + """Patient search by coverage contract (all pages)""" + + contract = self.user.contract_data.pop() + self.user.run_task_by_parameters( + base_path="/v1/fhir/Patient/_search", + body={ + "_id": self.user.bene_ids.pop(), + "_has:Coverage.extension": f'https://bluebutton.cms.gov/resources/variables/ptdcntrct01|{contract["id"]}', + "_has:Coverage.rfrncyr": f'https://bluebutton.cms.gov/resources/variables/rfrnc_yr|{contract["year"]}', + "_count": "25", + }, + name="/v1/fhir/Patient search by coverage contract (all pages)", + ) + + + @tag("patient_test_mbi_v1", "v1") + @task + def patient_test_mbi_v1(self): + """Patient search by ID, Last Updated, include MBI, include Address""" + + self.user.run_task_by_parameters( + base_path="/v1/fhir/Patient/_search", + body={ + "_id": self.user.bene_ids.pop(), + "_IncludeIdentifiers": "mbi", + }, + name="/v1/fhir/Patient search by mbi", + ) + + @tag("patient_test_id_last_updated_include_mbi_include_address_v1", "v1") + @task + def patient_test_id_last_updated_include_mbi_include_address_v1(self): + """Patient search by ID, Last Updated, include MBI, include Address""" + self.user.run_task_by_parameters( + base_path="/v1/fhir/Patient/_search", + body={ + "_id": self.user.bene_ids.pop(), + "_lastUpdated": f"gt{self.user.last_updated}", + "_IncludeIdentifiers": "mbi", + "_IncludeTaxNumbers": "true", + }, + name="/v1/fhir/Patient/_search search by id / (2 weeks) / includeTaxNumbers / mbi", + ) + + @tag("patient_test_id_v1", "v1") + @task + def patient_test_id_v1(self): + """Patient search by ID""" + + self.user.run_task_by_parameters( + base_path="/v1/fhir/Patient/_search", + body={ + "_id": self.user.bene_ids.pop(), + "_lastUpdated": f"gt{self.user.last_updated}", + }, + name="/v1/fhir/Patient/id" + ) + + @tag("patient_test_coverage_contract", "v2") + @task + def patient_test_coverage_contract(self): + """Patient search by Coverage Contract, paginated""" + + contract = self.user.contract_data.pop() + self.user.run_task_by_parameters( + base_path="/v2/fhir/Patient/_search", + body={ + "_id": self.user.bene_ids.pop(), + "_has:Coverage.extension": f'https://bluebutton.cms.gov/resources/variables/ptdcntrct01|{contract["id"]}', + "_has:Coverage.rfrncyr": f'https://bluebutton.cms.gov/resources/variables/rfrnc_yr|{contract["year"]}', + "_count": "25", + }, + name="/v2/fhir/Patient search by coverage contract (all pages)", + ) + + + @tag("patient_test_mbi", "v2") + @task + def patient_test_mbi(self): + """Patient search by MBI, include identifiers""" + + self.user.run_task_by_parameters( + base_path="/v2/fhir/Patient/_search", + body={ + "_id": self.user.bene_ids.pop(), + "_has:Coverage.extension": self.user.contract_data.pop()["id"], + "_IncludeIdentifiers": "mbi", + }, + name="/v2/fhir/Patient_search search by mbi new try", + ) + @tag("patient_test_id_include_mbi_last_updated", "v2") + @task + def patient_test_id_include_mbi_last_updated(self): + """Patient search by ID with last updated, include MBI""" + self.user.run_task_by_parameters( + base_path="/v2/fhir/Patient/_search", + body={ + "_id": self.user.bene_ids.pop(), + "_IncludeIdentifiers": "mbi", + "_lastUpdated": f"gt{self.user.last_updated}", + }, + name="/v2/fhir/Patient/_search search by id / _IncludeIdentifiers=mbi / (2 weeks)", + ) + + @tag("patient_test_id", "v2") + @task + def patient_test_id(self): + """Patient search by ID""" + self.user.run_task_by_parameters( + base_path="/v2/fhir/Patient/_search", + body={ + "_id": self.user.bene_ids.pop(), + }, + name="/v2/fhir/Patient/_search search by id", + ) + + +class HighVolumeUser(BFDUserBase): + """High volume load test suite for V2 BFD Server endpoints. + + The tests in this suite generate a large volume of traffic to endpoints that are hit most + frequently during a peak load event. + """ + + # Do we terminate the tests when a test runs out of data and paginated URLs? + END_ON_NO_DATA = False + + @staticmethod + def filter_tasks_by_tags( + task_holder: Type[TaskHolder], + tags: Set[str], + exclude_tags: Set[str], + checked: Optional[Dict[TaskT, bool]] = None, + ): + """ + Recursively remove any tasks/TaskSets from a TaskSet/User that + shouldn't be executed according to the tag options + :param task_holder: the TaskSet or User with tasks + :param tags: The set of tasks by @tag to include in the final list + :param exclude_tags: The set of tasks by @tag to exclude from the final list + :param checked: The running score of tasks which have or have not been processed + :return: A list of filtered tasks to execute + """ + filtered_tasks = [] + if checked is None: + checked = {} + for task in task_holder.tasks: + if task in checked: + if checked[task]: + filtered_tasks.append(task) + continue + passing = True + if hasattr(task, "tasks"): + HighVolumeUser.filter_tasks_by_tags(task, tags, exclude_tags, checked) + passing = len(task.tasks) > 0 + else: + if len(tags) > 0: + passing &= ( + "locust_tag_set" in dir(task) + and len(task.locust_tag_set.intersection(tags)) > 0 + ) + if len(exclude_tags) > 0: + passing &= ( + "locust_tag_set" not in dir(task) + or len(task.locust_tag_set.intersection(exclude_tags)) == 0 + ) + + if passing: + filtered_tasks.append(task) + checked[task] = passing + + return filtered_tasks + + @staticmethod + def get_tasks(tags: Set[str], exclude_tags: Set[str]): + """ + Returns the list of runnable tasks for the given user, filterable by a list of tags or exclude_tags. + Returns all runnable tasks if neither tags or exclude_tags contain items. + + :param tags: The list of tags to filter tasks by + :param exclude_tags: This list of tags to exclude tasks by + :return: A list of tasks to run + """ + + # Filter out the class members without a tasks attribute + class_members = inspect.getmembers(sys.modules[__name__], inspect.isclass) + potential_tasks = list( + filter( + lambda potential_task: hasattr(potential_task[1], "tasks") + and issubclass(potential_task[1], HighVolumeTaskSet), + class_members, + ) + ) + + # Filter each task holder's tasks by the given tags and exclude_tags + tasks = [] + for task_holder in list(map(lambda task_set: task_set[1], potential_tasks)): + tasks.extend(HighVolumeUser.filter_tasks_by_tags(task_holder, tags, exclude_tags)) + return tasks + + def get_runnable_tasks(self, tags: Set[str], exclude_tags: Set[str]): + """ + Helper method to be called via the HighVolumerUser constructor. + Required due to python <= 3.9 not allowing direct calls to static methods. + Returns the list of runnable tasks. + + :param tags: The list of tags to filter tasks by + :param exclude_tags: This list of tags to exclude tasks by + :return: A list of tasks to run + """ + return HighVolumeUser.get_tasks(tags, exclude_tags) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.bene_ids = list(MASTER_BENE_IDS) + self.contract_data = list(MASTER_CONTRACT_DATA) + self.mbis = list(MASTER_MBIS) + + # Shuffle the data to ensure each User isn't making requests with the same data in the same + # order + random.shuffle(self.bene_ids) + random.shuffle(self.contract_data) + random.shuffle(self.mbis) + + # As of 01/20/2023 there is an unresolved locust issue [1] with the --tags/--exclude-tags command line options. + # Therefore, we have implemented custom arguments (--locust-tags/--locust-exclude-tags) to programmatically + # filter tasks by the given @tag(s) at runtime. + # [1] https://github.com/locustio/locust/issues/1689 + + # Looking at this, it's not obvious why we're dynamically generating a _class_ (not an + # instance) with its "tasks" _attribute_ (not field) set to the filtered list of tasks we + # want to run. Why not just pass the list of tasks directly, as Locust supports setting + # "tasks" to a List of Callables? A few reasons: + # 1. Locust does not support passing an _instance_ of a TaskSet as "tasks", as Locust + # expects to instantiate the TaskSet itself when the User is ran (passing the User to the + # TaskSet's __init__, as "parent"). If we want to pass a TaskSet where its "tasks" are a + # _subset_ of all the tasks on the TaskSet, we need to generate a class with a "tasks" + # attribute explicitly set. Otherwise, Locust will take all Callables tagged with "task" + # from the TaskSet + # 2. If "tasks" is a list of function refs/Callables, Locust will pass the _User_ class as + # the first arg to each task Callable. Each Callable, in this context, is a method of a + # HighVolumeTaskSet, and so each Callable expects "self" to be an instance of said + # HighVolumeTaskSet. However, in this case, "self" is actually an instance of + # HighVolumeUser, and so this contract is broken and the benefit provided by static type + # analysis is invalidated. Passing a dynamically generated Class deriving from + # HighVolumeTaskSet with its "tasks" attribute set to the list of task function + # references solves this "self" invalidation by ensuring "self" is _always_ an instance + # of HighVolumeTaskSet in this context when Locust executes a given task Callable + self.tasks = [ + type( + "HVUFilteredTaskSet", + (HighVolumeTaskSet,), + {"tasks": self.get_runnable_tasks(TAGS, EXCLUDE_TAGS)}, + ) + ] + + # Override the value for last_updated with a static value + self.last_updated = "2022-06-29" diff --git a/apps/utils/locust_tests/v1/regression_suite_post.py b/apps/utils/locust_tests/v1/regression_suite_post.py index 296a457aac..60483eb262 100644 --- a/apps/utils/locust_tests/v1/regression_suite_post.py +++ b/apps/utils/locust_tests/v1/regression_suite_post.py @@ -133,6 +133,22 @@ def eob_test_id(self): name="/v1/fhir/ExplanationOfBenefit/_search search by id", ) + @tag("patient", "patient_test_coverage_contract") + @task + def patient_test_coverage_contract(self): + """Patient search by coverage contract (all pages)""" + contract = next(self.contract_data) + self.run_task_by_parameters( + base_path="/v1/fhir/Patient/_search", + body={ + "_id": next(self.bene_ids), + "_has:Coverage.extension": f'https://bluebutton.cms.gov/resources/variables/ptdcntrct01|{contract["id"]}', + "_has:Coverage.rfrncyr": f'https://bluebutton.cms.gov/resources/variables/rfrnc_yr|{contract["year"]}', + "_count": "25", + }, + name="/v1/fhir/Patient search by coverage contract (all pages)" + ) + @tag("patient", "patient_test_mbi") @task def patient_test_mbi(self): diff --git a/apps/utils/locust_tests/v2/partially_adjudicated.py b/apps/utils/locust_tests/v2/partially_adjudicated.py deleted file mode 100644 index f7f83eb7a7..0000000000 --- a/apps/utils/locust_tests/v2/partially_adjudicated.py +++ /dev/null @@ -1,121 +0,0 @@ -import random -from typing import Collection - -from locust import events, tag, task -from locust.env import Environment - -from common import data, db -from common.bfd_user_base import BFDUserBase -from common.locust_utils import is_distributed, is_locust_master -from common.user_init_aware_load_shape import UserInitAwareLoadShape - -master_pac_mbis: Collection[str] = [] - - -@events.test_start.add_listener -def _(environment: Environment, **kwargs): - if ( - is_distributed(environment) - and is_locust_master(environment) - or not environment.parsed_options - ): - # Don't bother loading data for the master runner, it doesn't run a test - return - - # See https://docs.locust.io/en/stable/extending-locust.html#test-data-management - # for Locust's documentation on the test data management pattern used here - global master_pac_mbis - master_pac_mbis = data.load_from_parsed_opts( - environment.parsed_options, - db.get_pac_hashed_mbis, - use_table_sample=False, - data_type_name="pac_mbis", - ) - - -class TestLoadShape(UserInitAwareLoadShape): - pass - - -class PACUser(BFDUserBase): - """ - Tests for Partially Adjudicated Claims endpoints to test their performance - - The MBI list is randomly shuffled to get better sampling in sequential testing. - """ - - SERVICE_DATE = {"service-date": "gt2020-01-05"} - LAST_UPDATED = {"_lastUpdated": "gt2020-05-05"} - SERVICE_DATE_LAST_UPDATED = dict(SERVICE_DATE, **LAST_UPDATED) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.hashed_mbis = list(master_pac_mbis) - - def __get(self, resource, name, parameters=None): - params = {} if parameters is None else parameters - params["mbi"] = random.choice(self.hashed_mbis) - - self.run_task_by_parameters(base_path=f"/v2/fhir/{resource}", params=params, name=name) - - @tag("claim") - @task - def get_claim(self): - """Get single Claim""" - self.__get("Claim", "claim") - - @tag("claim", "service-date") - @task - def get_claim_with_service_date(self): - """Get single Claim with service date""" - self.__get("Claim", "claimServiceDate", self.SERVICE_DATE) - - @tag("claim", "last-updated") - @task - def get_claim_with_last_updated(self): - """Get single Claim with last updated""" - self.__get("Claim", "claimLastUpdated", self.LAST_UPDATED) - - @tag("claim", "service-date", "last-updated") - @task - def get_claim_with_service_date_and_last_updated(self): - """Get single Claim with last updated and service date""" - self.__get("Claim", "claimServiceDateLastUpdated", self.SERVICE_DATE_LAST_UPDATED) - - @tag("claim", "exclude-samsa") - @task - def get_claim_with_exclude_samsa(self): - self.__get("Claim", "excludeSAMSA", True) - - @tag("claim-response") - @task - def get_claim_response(self): - """Get single ClaimResponse""" - self.__get("ClaimResponse", "claimResponse") - - @tag("claim-response", "service-date") - @task - def get_claim_response_with_service_date(self): - """Get single ClaimResponse with service date""" - self.__get("ClaimResponse", "claimResponseServiceDate", self.SERVICE_DATE) - - @tag("claim-response", "last-updated") - @task - def get_claim_response_with_last_updated(self): - """Get single ClaimResponse with last updated""" - self.__get("ClaimResponse", "claimResponseLastUpdated", self.LAST_UPDATED) - - @tag("claim-response", "service-date", "last-updated") - @task - def get_claim_response_with_service_date_and_last_updated(self): - """Get single ClaimResponse with last updated and service date""" - self.__get( - "ClaimResponse", - "claimResponseServiceDateLastUpdated", - self.SERVICE_DATE_LAST_UPDATED, - ) - - @tag("claim", "exclude-samsa") - @task - def get_claim_response_with_exclude_samsa(self): - self.__get("ClaimResponse", "excludeSAMSA", True) diff --git a/apps/utils/locust_tests/v2/partially_adjudicated_smoketest.py b/apps/utils/locust_tests/v2/partially_adjudicated_smoketest.py deleted file mode 100644 index f625ea4d8c..0000000000 --- a/apps/utils/locust_tests/v2/partially_adjudicated_smoketest.py +++ /dev/null @@ -1,69 +0,0 @@ -import itertools -from typing import Collection - -from locust import events, tag, task -from locust.env import Environment - -from common import data, db -from common.bfd_user_base import BFDUserBase -from common.locust_utils import is_distributed, is_locust_master -from common.user_init_aware_load_shape import UserInitAwareLoadShape - -master_pac_mbis: Collection[str] = [] - - -@events.test_start.add_listener -def _(environment: Environment, **kwargs): - if ( - is_distributed(environment) - and is_locust_master(environment) - or not environment.parsed_options - ): - # Don't bother loading data for the master runner, it doesn't run a test - return - - # See https://docs.locust.io/en/stable/extending-locust.html#test-data-management - # for Locust's documentation on the test data management pattern used here - global master_pac_mbis - master_pac_mbis = data.load_from_parsed_opts( - environment.parsed_options, - db.get_pac_hashed_mbis_smoketest, - use_table_sample=False, - data_type_name="pac_mbis", - ) - - -class TestLoadShape(UserInitAwareLoadShape): - pass - - -class PACSmokeUser(BFDUserBase): - - """ - Tests for Partially Adjudicated Claims endpoints to error check the transformers - - Copies are made of the MBI list so that both the claim and claimResponse object is checked - for every MBI. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.hashed_mbis = list(master_pac_mbis) - - def __get(self, resource, name, parameters=None): - params = {} if parameters is None else parameters - params["mbi"] = itertools.cycle(self.hashed_mbis) - - self.run_task_by_parameters(base_path=f"/v2/fhir/{resource}", params=params, name=name) - - @tag("claim") - @task - def get_claim(self): - """Get single Claim""" - self.__get("Claim", "claim") - - @tag("claim-response") - @task - def get_claim_response(self): - """Get single ClaimResponse""" - self.__get("ClaimResponse", "claimResponse") diff --git a/apps/utils/locust_tests/v2/regression_suite_post.py b/apps/utils/locust_tests/v2/regression_suite_post.py index 670f4d731c..8403d7bbb2 100644 --- a/apps/utils/locust_tests/v2/regression_suite_post.py +++ b/apps/utils/locust_tests/v2/regression_suite_post.py @@ -125,6 +125,37 @@ def eob_test_id(self): name="/v2/fhir/ExplanationOfBenefit/_search search by id", ) + @tag("patient", "patient_test_coverage_contract") + @task + def patient_test_coverage_contract(self): + """Patient search by Coverage Contract, paginated""" + + contract = next(self.contract_data) + self.run_task_by_parameters( + base_path="/v2/fhir/Patient/_search", + body={ + "_id": next(self.bene_ids), + "_has:Coverage.extension": f'https://bluebutton.cms.gov/resources/variables/ptdcntrct01|{contract["id"]}', + "_has:Coverage.rfrncyr": f'https://bluebutton.cms.gov/resources/variables/rfrnc_yr|{contract["year"]}', + "_count": "25", + }, + name="/v2/fhir/Patient search by coverage contract (all pages)", + ) + + @tag("patient", "patient_test_mbi") + @task + def patient_test_mbi(self): + """Patient search by MBI, include identifiers""" + + self.run_task_by_parameters( + base_path="/v2/fhir/Patient/_search", + body={ + "identifier": f"https://bluebutton.cms.gov/resources/identifier/mbi-hash|{next(self.mbis)}", + "_IncludeIdentifiers": "mbi", + }, + name="/v2/fhir/Patient search by mbi / includeIdentifiers = mbi", + ) + @tag("patient", "patient_test_id_include_mbi") @task def patient_test_id_include_mbi(self):