def get_service_items(self, overrides=None): """ Fetches all of the mto service items for a given move id """ # If id was provided, get that specific one. Else get any stored one. object_id = overrides.get("id") if overrides else None move = self.get_stored(MOVE_TASK_ORDER, object_id) if move is None: logger.info("Skipping get service items no moves are stored yet") return url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint=f"/move_task_orders/{move['id']}/mto_service_items", endpoint_name="/move_task_orders/{moveId}/mto_service_items", ) with self.rest(method="GET", url=url, **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: self.add_stored(MTO_SERVICE_ITEM, resp.js) else: resp.failure("Unable to get service items.") log_response_failure(response=resp)
def get_user_info(self) -> Optional[JSONObject]: """ Gets the user info for the currently logged-in user. :return: logged-in user, or None if there is an error """ url, request_kwargs = self.request_preparer.prep_internal_request( endpoint="/users/logged_in") with self.rest(method="GET", url=url, **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: user: JSONObject = resp.js logger.info(f"ℹ️ User email: {user['email']}") return user else: resp.failure("Unable to get logged-in user.") log_response_failure(response=resp) return
def get_customer(self, overrides=None): """ Fetches a single service member's orders """ # If id was provided, get that specific one. Else get any stored one. object_id = overrides.get("id") if overrides else None order = self.get_stored(ORDER, object_id) if order is None: logger.info("Skipping get customer no order is stored yet") return url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint=f"/customer/{order['customerID']}", endpoint_name="/customer/{customerId}", ) with self.rest(method="GET", url=url, **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: self.add_stored(CUSTOMER, resp.js) else: resp.failure("Unable get customer.") log_response_failure(response=resp)
def get_moves_queue(self, overrides=None): """ Fetches a list of paginated moves """ url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint="/queues/moves?page=1&perPage=50&sort=status&order=asc", endpoint_name="/queues/moves", ) with self.rest(method="GET", url=url, **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: moves: JSONObject = resp.js self.add_stored(MOVE_TASK_ORDER, moves["queueMoves"]) # destination duty locations don't have to be in the office user's GBLOC destination_duty_location_ids = [move["destinationDutyLocation"]["id"] for move in moves["queueMoves"]] self.default_mto_ids["destinationDutyLocationID"].update(destination_duty_location_ids) else: resp.failure("Unable to get moves queue.") log_response_failure(response=resp)
def get_move(self, overrides=None): """ Fetches a single move """ # If id was provided, get that specific one. Else get any stored one. object_id = overrides.get("id") if overrides else None move = self.get_stored(MOVE_TASK_ORDER, object_id) if move is None: logger.info("Skipping get move none are stored yet") return url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint=f"/move/{move['locator']}", endpoint_name="/move/{locator}", ) with self.rest(method="GET", url=url, **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: new_mto: JSONObject = resp.js self.add_stored(MOVE_TASK_ORDER, new_mto) return new_mto else: resp.failure("Unable to get move.") log_response_failure(response=resp)
def get_orders(self, overrides=None): """ Fetches a single service member's orders """ # If id was provided, get that specific one. Else get any stored one. object_id = overrides.get("id") if overrides else None move = self.get_stored(MOVE_TASK_ORDER, object_id) if move is None: logger.info("Skipping get orders no move is stored yet") return orders_id = move.get("ordersId") if orders_id is None: move = self.get_move({"id": move["id"]}) url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint=f"/orders/{move['ordersId']}", endpoint_name="/orders/{orderId}", ) with self.rest(method="GET", url=url, **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: self.add_stored(ORDER, resp.js) else: resp.failure("Unable to get orders.") log_response_failure(response=resp)
def get_moves_queue(self, overrides=None): """ Fetches a list of paginated moves """ # Use the default queue sort for now, adding a filter to return service counseling completed # moves as well url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint="/queues/counseling?page=1&perPage=50&sort=submittedAt&order=asc&status=NEEDS SERVICE COUNSELING,SERVICE COUNSELING COMPLETED", endpoint_name="/queues/counseling", ) with self.rest(method="GET", url=url, **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: moves: JSONObject = resp.js # these are not full move models and will be those in needs counseling status self.add_stored(MOVE_TASK_ORDER, moves["queueMoves"]) else: resp.failure("Unable to get moves queue.") log_response_failure(response=resp)
def update_orders(self, overrides=None): """ Updates an existing order of a move as a TOO :return: """ object_id = overrides.get("id") if overrides else None order = self.get_stored(ORDER, object_id) order = self.get_orders() if order is None else order if order is None: logger.info("skipping update order, no moves exist yet") return move = self.get_stored(MOVE_TASK_ORDER, order["moveTaskOrderID"]) if move and move["status"] == "NEEDS SERVICE COUNSELING": logger.info("Skipping update orders as TOO, move is still in services counseling") return # require all optional fields otherwise nullable fields will be omitted payload = fake_data_generator.generate_fake_request_data( api_key=APIKey.OFFICE, path="/orders/{orderID}", method="patch", require_all=True, ) if self.default_mto_ids.get("originDutyLocationID"): payload["originDutyLocationId"] = random.choice(list(self.default_mto_ids["originDutyLocationID"])) else: payload["originDutyLocationId"] = order["originDutyLocation"]["id"] if self.default_mto_ids.get("destinationDutyLocationID"): payload["newDutyLocationId"] = random.choice(list(self.default_mto_ids["destinationDutyLocationID"])) else: payload["newDutyLocationId"] = order["destinationDutyLocation"]["id"] url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint=f"/orders/{order['id']}", endpoint_name="/orders/{orderId}", ) request_kwargs["headers"]["If-Match"] = order["eTag"] with self.rest(method="PATCH", url=url, data=json.dumps(payload), **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: new_order: JSONObject = resp.js self.add_stored(ORDER, new_order) self.add_stored(CUSTOMER, new_order["customer"]) else: resp.failure("Unable to update orders.") log_response_failure(response=resp)
def test_includes_formatted_request_in_log( self, mock_logger: MagicMock, mock_request_formatter: MagicMock, _mock_response_formatter: MagicMock) -> None: mock_response = create_autospec(RestResponseContextManager) mock_response.request = create_autospec(PreparedRequest) log_response_failure(response=mock_response) mock_logger.error.assert_called_once() assert str(mock_request_formatter.return_value ) in mock_logger.error.call_args[0][0]
def test_defaults_to_include_calling_function_name_in_log( self, mock_logger: MagicMock, _mock_request_formatter: MagicMock, _mock_response_formatter: MagicMock) -> None: mock_response = create_autospec(RestResponseContextManager) mock_response.request = create_autospec(PreparedRequest) log_response_failure(response=mock_response) mock_logger.error.assert_called_once() assert "test_defaults_to_include_calling_function_name_in_log failed" in mock_logger.error.call_args[ 0][0]
def test_can_override_task_name_in_log( self, mock_logger: MagicMock, _mock_request_formatter: MagicMock, _mock_response_formatter: MagicMock) -> None: mock_response = create_autospec(RestResponseContextManager) mock_response.request = create_autospec(PreparedRequest) task_name = "my_task" log_response_failure(response=mock_response, task_name=task_name) mock_logger.error.assert_called_once() assert f"{task_name} failed" in mock_logger.error.call_args[0][0] assert "test_can_override_task_name_in_log" not in mock_logger.error.call_args[ 0][0]
def update_orders(self, overrides=None): """ Updates an existing order of a move as a Services Counselor. :return: """ object_id = overrides.get("id") if overrides else None order = self.get_stored(ORDER, object_id) order = self.get_orders() if order is None else order if order is None: logger.info("skipping update order, no moves exist yet") return payload = fake_data_generator.generate_fake_request_data( api_key=APIKey.OFFICE, path="/counseling/orders/{orderID}", method="patch", ) payload = {key: payload[key] for key in ["issueDate", "reportByDate", "ordersType"]} if self.default_mto_ids.get("originDutyLocationID"): payload["originDutyLocationId"] = random.choice(list(self.default_mto_ids["originDutyLocationID"])) else: payload["originDutyLocationId"] = order["originDutyLocation"]["id"] if self.default_mto_ids.get("destinationDutyLocationID"): payload["newDutyLocationId"] = random.choice(list(self.default_mto_ids["destinationDutyLocationID"])) else: payload["newDutyLocationId"] = order["destinationDutyLocation"]["id"] # The request may result in validation errors if the underlying move is no longer in needs counseling status url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint=f"/counseling/orders/{order['id']}", endpoint_name="/counseling/orders/{orderId}", ) request_kwargs["headers"]["If-Match"] = order["eTag"] with self.rest(method="PATCH", url=url, data=json.dumps(payload), **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: self.add_stored(ORDER, resp.js) else: resp.failure("Unable to update orders.") log_response_failure(response=resp)
def update_allowance(self, overrides=None): """ Updates the existing entitlements of an order as a TOO :return: """ object_id = overrides.get("id") if overrides else None order = self.get_stored(ORDER, object_id) order = self.get_orders() if order is None else order if order is None: logger.info("skipping update allowance, no moves exist yet") return payload = fake_data_generator.generate_fake_request_data( api_key=APIKey.OFFICE, path="/orders/{orderID}/allowances", method="patch", ) # update allowances handler expects the orders eTag because it also updates parents order fields url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint=f"/orders/{order['id']}/allowances", endpoint_name="/orders/{orderId}/allowances", ) request_kwargs["headers"]["If-Match"] = order["eTag"] with self.rest(method="PATCH", url=url, data=json.dumps(payload), **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: new_order: JSONObject = resp.js self.add_stored(ORDER, new_order) self.add_stored(CUSTOMER, new_order["customer"]) else: resp.failure("Unable to update allowance.") log_response_failure(response=resp)
def update_customer(self, overrides=None): """ Updates the existing service member of an order as a Services Counselor. :return: """ object_id = overrides.get("id") if overrides else None customer = self.get_stored(CUSTOMER, object_id) or self.get_customer() if customer is None: logger.info("skipping update customer, no orders exist yet") return if not customer.get("current_address"): logger.info("Skipping update customer as no current address exists") return payload = fake_data_generator.generate_fake_request_data( api_key=APIKey.OFFICE, path="/customer/{customerID}", method="patch", overrides={"current_address": {"id": customer["current_address"]["id"]}}, require_all=True, ) url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint=f"/customer/{customer['id']}", endpoint_name="/customer/{customerID}", ) request_kwargs["headers"]["If-Match"] = customer["eTag"] with self.rest(method="PATCH", url=url, data=json.dumps(payload), **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: self.add_stored(CUSTOMER, resp.js) else: resp.failure("Unable to update customer.") log_response_failure(response=resp)
def get_orders(self, overrides=None): """ Fetches a single service member's orders """ # If id was provided, get that specific one. Else get any stored one. object_id = overrides.get("id") if overrides else None move = self.get_stored(MOVE_TASK_ORDER, object_id) if move is None: logger.info("Skipping get orders no move is stored yet") return orders_id = move.get("ordersId") if orders_id is None: move = self.get_move({"id": move["id"]}) url, request_kwargs = self.request_preparer.prep_ghc_request( endpoint=f"/orders/{move['ordersId']}", endpoint_name="/orders/{orderId}", ) with self.rest(method="GET", url=url, **request_kwargs) as resp: resp: RestResponseContextManager log_response_info(response=resp) if resp.status_code == HTTPStatus.OK: order: JSONObject = resp.js self.add_stored(ORDER, order) # the origin duty location is not in the queue response and we can't use the destination # because they could be outside of the office user's GBLOC self.default_mto_ids["originDutyLocationID"].add(order["originDutyLocation"]["id"]) else: resp.failure("Unable to get orders.") log_response_failure(response=resp)
def onboard_customer(self) -> None: """ Goes through the basic on-boarding flow for a customer. We're going to be re-using the same endpoint for several requests in this workflow. Because of that, we won't use the api fake data generator because we would end up having to delete a lot of the data it gave back in order to simulate the step-by-step process that on-boarding actually takes. Instead, we'll use the lower-level `MilMoveData` which is what the api fake data generate uses internally because it gives us more control. It's essentially a wrapper around faker, but we'll still use it to remain more consistent in the faker helper functions we use for different data types. Another thing to note is that because we'll be hitting a lot of endpoints in this load test, we'll pass task_name values to the logging functions whenever we hit an endpoint to make it easier to see in the logs what is happening. """ user = self.get_user_info() if user is None: return # get_user_info will log the error service_member_id = user["service_member"]["id"] milmove_faker = MilMoveData() # First let's create the profile. payload = { "id": service_member_id, "affiliation": "ARMY", "edipi": "1234567890", "rank": "E_1", } sm_url, sm_request_kwargs = self.request_preparer.prep_internal_request( endpoint=f"/service_members/{service_member_id}", endpoint_name="/service_members/{serviceMemberId}", ) with self.rest(method="PATCH", url=sm_url, data=json.dumps(payload), **sm_request_kwargs) as resp: resp: RestResponseContextManager task_name = "create_user_profile" log_response_info(response=resp, task_name=task_name) if resp.status_code != HTTPStatus.OK: resp.failure("Unable to create customer profile.") log_response_failure(response=resp, task_name=task_name) return # Now we can add name info. The url and request_kwargs will actually be the same, so we only # need to prepare the payload. suffix = random.choice([ milmove_faker.get_fake_data_for_type(data_type=DataType.SUFFIX), "" ]) payload = { "id": service_member_id, "first_name": milmove_faker.get_fake_data_for_type( data_type=DataType.FIRST_NAME), "middle_name": milmove_faker.get_fake_data_for_type( data_type=DataType.MIDDLE_NAME), "last_name": milmove_faker.get_fake_data_for_type(data_type=DataType.LAST_NAME), "suffix": suffix, } with self.rest(method="PATCH", url=sm_url, data=json.dumps(payload), **sm_request_kwargs) as resp: resp: RestResponseContextManager task_name = "add_name_info" log_response_info(response=resp, task_name=task_name) if resp.status_code != HTTPStatus.OK: resp.failure("Unable to add customer name.") log_response_failure(response=resp, task_name=task_name) return # Now we can add contact info. Again, url and request_kwargs will be the same, so only need # a payload. payload = { "id": service_member_id, "telephone": milmove_faker.get_fake_data_for_type(data_type=DataType.PHONE), "secondary_telephone": milmove_faker.get_fake_data_for_type(data_type=DataType.PHONE), "personal_email": milmove_faker.get_fake_data_for_type(data_type=DataType.EMAIL), "email_is_preferred": milmove_faker.get_fake_data_for_type(data_type=DataType.BOOLEAN), "phone_is_preferred": milmove_faker.get_fake_data_for_type(data_type=DataType.BOOLEAN), } with self.rest(method="PATCH", url=sm_url, data=json.dumps(payload), **sm_request_kwargs) as resp: resp: RestResponseContextManager task_name = "add_contact_info" log_response_info(response=resp, task_name=task_name) if resp.status_code != HTTPStatus.OK: resp.failure("Unable to add customer contact info.") log_response_failure(response=resp, task_name=task_name) return # Next we need a duty location duty_location_url, duty_location_request_kwargs = self.request_preparer.prep_internal_request( endpoint="/duty_locations?search=fort") with self.rest(method="GET", url=duty_location_url, **duty_location_request_kwargs) as resp: resp: RestResponseContextManager task_name = "search_duty_locations" log_response_info(response=resp, task_name=task_name) if resp.status_code == HTTPStatus.OK: duty_locations: JSONArray = resp.js else: resp.failure("Unable to find duty locations.") log_response_failure(response=resp, task_name=task_name) return # Don't care too much about which one for this load test, so just grabbing the first one. origin_duty_location = duty_locations[0] payload = { "id": service_member_id, "current_location_id": origin_duty_location["id"] } with self.rest(method="PATCH", url=sm_url, data=json.dumps(payload), **sm_request_kwargs) as resp: resp: RestResponseContextManager task_name = "add_current_duty_location" log_response_info(response=resp, task_name=task_name) if resp.status_code != HTTPStatus.OK: resp.failure("Unable to add current duty location.") log_response_failure(response=resp, task_name=task_name) return # Next we'll input the current mailing address. We could use a fully random one, or base it # in part on where the duty location is, which we'll do. # First, we'll get the address of the duty location. address_url, address_request_kwargs = self.request_preparer.prep_internal_request( endpoint=f"/addresses/{origin_duty_location['address_id']}", endpoint_name="/addresses/{addressId}", ) with self.rest(method="GET", url=address_url, **address_request_kwargs) as resp: resp: RestResponseContextManager task_name = "get_duty_location_address" log_response_info(response=resp, task_name=task_name) if resp.status_code == HTTPStatus.OK: duty_location_address: JSONObject = resp.js else: resp.failure("Unable to find duty location address.") log_response_failure(response=resp, task_name=task_name) return # Zip code is validated as part of on-boarding. zip_code_url, zip_code_request_kwargs = self.request_preparer.prep_internal_request( endpoint= f"/rate_engine_postal_codes/{duty_location_address['postalCode']}?postal_code_type=origin", endpoint_name="/rate_engine_postal_codes/{postal_code}", ) with self.rest(method="GET", url=zip_code_url, **zip_code_request_kwargs) as resp: resp: RestResponseContextManager task_name = "validate_zip_code" log_response_info(response=resp, task_name=task_name) if resp.status_code != HTTPStatus.OK: resp.failure("Unable to validate zip code.") log_response_failure(response=resp, task_name=task_name) return # Now we can add the current mailing address. payload = { "id": service_member_id, "residential_address": { "streetAddress1": milmove_faker.get_fake_data_for_type( data_type=DataType.STREET_ADDRESS), "city": duty_location_address["city"], "state": duty_location_address["state"], "postalCode": duty_location_address["postalCode"], }, } with self.rest(method="PATCH", url=sm_url, data=json.dumps(payload), **sm_request_kwargs) as resp: resp: RestResponseContextManager task_name = "add_current_mailing_address" log_response_info(response=resp, task_name=task_name) if resp.status_code != HTTPStatus.OK: resp.failure("Unable to add current mailing address.") log_response_failure(response=resp, task_name=task_name) return # Next is the backup mailing address # We'll use mostly the same address as the current one, just changing out the street address payload["residential_address"][ "streetAddress1"] = milmove_faker.get_fake_data_for_type( data_type=DataType.STREET_ADDRESS) with self.rest(method="PATCH", url=sm_url, data=json.dumps(payload), **sm_request_kwargs) as resp: resp: RestResponseContextManager task_name = "add_backup_mailing_address" log_response_info(response=resp, task_name=task_name) if resp.status_code != HTTPStatus.OK: resp.failure("Unable to add backup mailing address.") log_response_failure(response=resp, task_name=task_name) return # Finally, we'll add the backup contact information. backup_contact_url, backup_contact_request_kwargs = self.request_preparer.prep_internal_request( endpoint=f"/service_members/{service_member_id}/backup_contacts", endpoint_name="/service_members/{serviceMemberId}/backup_contacts", ) backup_contact_name = f"{milmove_faker.get_fake_data_for_type(data_type=DataType.FIRST_NAME)} {milmove_faker.get_fake_data_for_type(data_type=DataType.LAST_NAME)}" payload = { "name": backup_contact_name, "email": milmove_faker.get_fake_data_for_type(data_type=DataType.EMAIL), "telephone": milmove_faker.get_fake_data_for_type(data_type=DataType.PHONE), "permission": "NONE", } with self.rest(method="POST", url=backup_contact_url, data=json.dumps(payload), **backup_contact_request_kwargs) as resp: resp: RestResponseContextManager task_name = "add_backup_contact" log_response_info(response=resp, task_name=task_name) if resp.status_code != HTTPStatus.CREATED: resp.failure("Unable to add backup contact info.") log_response_failure(response=resp, task_name=task_name) return