diff --git a/tests/e2e_automation/features/APITests/create.feature b/tests/e2e_automation/features/APITests/create.feature index 9fb5155878..d806b378ef 100644 --- a/tests/e2e_automation/features/APITests/create.feature +++ b/tests/e2e_automation/features/APITests/create.feature @@ -290,7 +290,7 @@ Feature: Create the immunization event for a patient @smoke @Delete_cleanUp @supplier_name_TPP @vaccine_type_BCG @patient_id_Random - Scenario: Verify that the POST Create API will fail when exiting Unique Id and no_unique_id_uri is used in the request + Scenario: Verify that the POST Create API will fail when exiting Unique Id and unique_id_uri is used in the request Given Valid json payload is created When Trigger the post create request Then The request will be successful with the status code '201' diff --git a/tests/e2e_automation/features/APITests/delete.feature b/tests/e2e_automation/features/APITests/delete.feature index 1cd6a6d915..0651587fb4 100644 --- a/tests/e2e_automation/features/APITests/delete.feature +++ b/tests/e2e_automation/features/APITests/delete.feature @@ -48,4 +48,97 @@ Feature: Delete an immunization of a patient And The delta table will be populated with the correct data for deleted event When same delete request is triggered again Then The request will be unsuccessful with the status code '404' - And The Response JSONs should contain correct error message for Imms_id 'not_found' \ No newline at end of file + And The Response JSONs should contain correct error message for Imms_id 'not_found' + + + @vaccine_type_HEPB @patient_id_Random @supplier_name_TPP + Scenario: Verify that the create request will be reinstated successfully after the record is soft deleted + Given I have created a valid vaccination record + When Send a delete for Immunization event created + Then The request will be successful with the status code '204' + And IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record + When Trigger another post create request with same unique_id and unique_id_uri + Then The request will be successful with the status code '201' + And The location key and Etag in header will contain the previous Immunization Id and version will be incremented by 1 + And IMMS event and delta tables, along with the MNS event, will be populated with correct created data for the reinstated record + When Send a delete for Immunization event created + Then The request will be successful with the status code '204' + And IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record + + + @vaccine_type_6IN1 @patient_id_Random @supplier_name_TPP + Scenario: Verify that the update request is reinstated successfully after the record is soft deleted + Given I have created a valid vaccination record + When Send a delete for Immunization event created + Then The request will be successful with the status code '204' + And IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record + When Trigger update request with same unique_id and unique_id_uri for the deleted record + Then The request will be successful with the status code '200' + And The Etag in header will contain the correct version which will be incremented by 1 + And IMMS event and delta tables, along with the MNS event, will be populated with correct updated data for the reinstated record + When Send a delete for Immunization event created + Then The request will be successful with the status code '204' + And IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record + + @vaccine_type_HEPB @patient_id_Random @supplier_name_TPP + Scenario: Verify that the identifier search request will have empty response for deleted record + Given I have created a valid vaccination record + When Send a delete for Immunization event created + Then The request will be successful with the status code '204' + And IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record + When I send a search request with Post method using identifier parameter for the record + Then The request will be successful with the status code '200' + #And No immunization event is returned in the response - defect need fixing for this VED-1263 + + @vaccine_type_HEPB @patient_id_Random @supplier_name_TPP + Scenario: Verify that the search request will have empty response for deleted record + Given I have created a valid vaccination record + When Send a delete for Immunization event created + Then The request will be successful with the status code '204' + And IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record + When Send a search request with post method using patient.identifier and target-disease for Immunization event deleted + Then The request will be successful with the status code '200' + And No immunization event is returned in the response + + @Delete_cleanUp @vaccine_type_HEPB @patient_id_Random @supplier_name_TPP + Scenario: Verify that the search request will be successful for reinstated record with create operation + Given I have created a valid vaccination record + When Send a delete for Immunization event created + Then The request will be successful with the status code '204' + And IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record + When Trigger another post create request with same unique_id and unique_id_uri + Then The request will be successful with the status code '201' + And The location key and Etag in header will contain the previous Immunization Id and version will be incremented by 1 + And IMMS event and delta tables, along with the MNS event, will be populated with correct created data for the reinstated record + When I send a search request with Post method using identifier parameter for the record + Then The request will be successful with the status code '200' + And reinstated record is returned in the response with correct created data + + + @Delete_cleanUp @vaccine_type_6IN1 @patient_id_Random @supplier_name_TPP + Scenario: Verify that the search request will be successful for reinstated record with update operation + Given I have created a valid vaccination record + When Send a delete for Immunization event created + Then The request will be successful with the status code '204' + And IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record + When Trigger update request with same unique_id and unique_id_uri for the deleted record + Then The request will be successful with the status code '200' + And The Etag in header will contain the correct version which will be incremented by 1 + And IMMS event and delta tables, along with the MNS event, will be populated with correct updated data for the reinstated record + When I send a search request with Post method using identifier parameter for the record + Then The request will be successful with the status code '200' + And reinstated record is returned in the response with correct created data + + @Delete_cleanUp @vaccine_type_HEPB @patient_id_Random @supplier_name_TPP + Scenario: Verify that the create request will be unsuccessfully for already reinstated record + Given I have created a valid vaccination record + When Send a delete for Immunization event created + Then The request will be successful with the status code '204' + And IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record + When Trigger another post create request with same unique_id and unique_id_uri + Then The request will be successful with the status code '201' + And The location key and Etag in header will contain the previous Immunization Id and version will be incremented by 1 + And IMMS event and delta tables, along with the MNS event, will be populated with correct created data for the reinstated record + When Trigger another post create request with same unique_id and unique_id_uri + Then The request will be unsuccessful with the status code '422' + And The Response JSONs should contain correct error message for 'duplicate' \ No newline at end of file diff --git a/tests/e2e_automation/features/APITests/steps/common_steps.py b/tests/e2e_automation/features/APITests/steps/common_steps.py index 46ac772854..15686e125d 100644 --- a/tests/e2e_automation/features/APITests/steps/common_steps.py +++ b/tests/e2e_automation/features/APITests/steps/common_steps.py @@ -4,13 +4,14 @@ import uuid from datetime import UTC, datetime, timedelta from urllib.parse import parse_qs -from venv import logger import pytest_check as check from pytest_bdd import given, parsers, then, when from src.dynamoDB.dynamo_db_helper import ( fetch_immunization_events_detail, + fetch_immunization_int_delta_detail_by_immsID, parse_imms_int_imms_event_response, + validate_imms_delta_record_with_created_event, ) from src.objectModels.api_immunization_builder import ( build_site_route, @@ -38,7 +39,7 @@ get_update_url_header, ) from utilities.date_helper import is_valid_date, normalize_utc_suffix -from utilities.enums import Operation +from utilities.enums import ActionFlag, Operation from utilities.http_requests_session import http_requests_session from utilities.sqs_message_halder import read_message from utilities.vaccination_constants import ROUTE_MAP, SITE_MAP @@ -168,8 +169,6 @@ def The_request_will_have_status_code(context, statusCode): @then("The location key and Etag in header will contain the Immunization Id and version") def validateCreateLocation(context): - location = context.response.headers["location"] - eTag = context.response.headers["E-Tag"] body = get_response_body_for_display(context.response) assert "location" in context.response.headers, ( f"Location header is missing in the response with Status code: {context.response.status_code}. Response: {body}" @@ -177,6 +176,9 @@ def validateCreateLocation(context): assert "E-Tag" in context.response.headers, ( f"E-Tag header is missing in the response with Status code: {context.response.status_code}. Response: {body}" ) + location = context.response.headers["location"] + eTag = context.response.headers["E-Tag"] + context.ImmsID = location.split("/")[-1] context.eTag = eTag.strip('"') print(f"\n Immunization ID is {context.ImmsID} and Etag is {context.eTag} \n") @@ -254,7 +256,7 @@ def validateCreateHeader(context): @then(parsers.parse("The imms event table will be populated with the correct data for '{operation}' event")) -def validate_imms_event_table_by_operation(context, operation: Operation): +def validate_imms_event_table_by_operation(context, operation: Operation, reinstated=False): create_obj = context.create_object table_query_response = fetch_immunization_events_detail(context.aws_profile_name, context.ImmsID, context.S3_env) assert "Item" in table_query_response, f"Item not found in response for ImmsID: {context.ImmsID}" @@ -265,8 +267,7 @@ def validate_imms_event_table_by_operation(context, operation: Operation): try: resource = json.loads(resource_json_str) - except (TypeError, json.JSONDecodeError) as e: - logger.error(f"Failed to parse Resource from item: {e}") + except (TypeError, json.JSONDecodeError): raise AssertionError("Failed to parse Resource from response item.") assert resource is not None, "Resource is None in the response" @@ -275,7 +276,7 @@ def validate_imms_event_table_by_operation(context, operation: Operation): assert int(context.expected_version) == int(context.eTag), ( f"Expected Version: {context.expected_version}, Found: {context.eTag}" ) - + actualDeletedAt = item.get("DeletedAt") fields_to_compare = [ ("Operation", Operation[operation].value, item.get("Operation")), ( @@ -299,6 +300,22 @@ def validate_imms_event_table_by_operation(context, operation: Operation): for name, expected, actual in fields_to_compare: check.is_true(expected == actual, f"Expected {name}: {expected}, Actual {actual}") + if Operation[operation].value == "DELETE": + check.is_true( + actualDeletedAt is not None and actualDeletedAt > 0, + f"Expected DeletedAt to be a Unix timestamp, got {actualDeletedAt}", + ) + elif reinstated: + check.is_true( + actualDeletedAt == "reinstated", + f"Expected DeletedAt: None for reinstated record, got {actualDeletedAt}", + ) + else: + check.is_true( + actualDeletedAt is None, + f"Expected DeletedAt: None, Actual {actualDeletedAt}", + ) + validate_to_compare_request_and_response(context, create_obj, created_event, True) @@ -401,6 +418,40 @@ def validate_mns_event_not_triggered_for_updated_event(context): mns_event_will_not_be_triggered_for_the_event(context) +@when("Trigger another post create request with same unique_id and unique_id_uri") +def trigger_post_create_with_same_unique_id(context): + context.immunization_object.contained[1].address[0].city = "Updated City" + context.immunization_object.contained[1].address[0].state = "Updated State" + Trigger_the_post_create_request(context) + + +@then("The delta table will be populated with the correct data for updated event") +def validate_delta_table_for_updated_event(context): + create_obj = context.create_object + items = fetch_immunization_int_delta_detail_by_immsID( + context.aws_profile_name, + context.ImmsID, + context.S3_env, + context.expected_version, + ) + assert items, f"Items not found in response for ImmsID: {context.ImmsID}" + delta_items = [i for i in items if i.get("Operation") == Operation.updated.value] + assert delta_items, f"No item found for ImmsID: {context.ImmsID}" + latest_delta_record = max(delta_items, key=lambda x: x.get("SequenceNumber", -1)) + validate_imms_delta_record_with_created_event( + context, + create_obj, + latest_delta_record, + Operation.updated.value, + ActionFlag.updated.value, + ) + + +@then("MNS event will be triggered with correct data for Updated event") +def validate_mns_event_triggered_for_updated_event(context): + mns_event_will_be_triggered_with_correct_data(context=context, action="UPDATE") + + def trigger_the_updated_request(context): context.expected_version = int(context.expected_version) + 1 context.create_object = context.update_object @@ -518,22 +569,25 @@ def validate_sqs_message(context, message_body, action): def mns_event_will_be_triggered_with_correct_data_for_deleted_event(context): - if context.patient.identifier[0].value is None: - message_body = read_message( - context, - queue_type="notification", - wait_time_seconds=5, - max_total_wait_seconds=20, - ) - print( - "No MNS delete event is created as expected since NHS number is not present in the original immunization event" - ) - assert message_body is None, "Not expected a message but queue returned a message" + if context.mns_validation_required.strip().lower() == "true": + if context.patient.identifier[0].value is None: + message_body = read_message( + context, + queue_type="notification", + wait_time_seconds=5, + max_total_wait_seconds=20, + ) + print( + "No MNS delete event is created as expected since NHS number is not present in the original immunization event" + ) + assert message_body is None, "Not expected a message but queue returned a message" + else: + message_body = read_message(context, queue_type="notification") + print(f"Read deleted message from SQS: {message_body}") + assert message_body is not None, "Expected a delete message but queue returned empty" + validate_sqs_message(context, message_body, "DELETE") else: - message_body = read_message(context, queue_type="notification") - print(f"Read deleted message from SQS: {message_body}") - assert message_body is not None, "Expected a delete message but queue returned empty" - validate_sqs_message(context, message_body, "DELETE") + print("MNS validation not required, skipping MNS event verification for deleted event.") def mns_event_will_be_triggered_with_correct_data(context, action): @@ -553,3 +607,10 @@ def mns_event_will_be_triggered_with_correct_data(context, action): print( f"MNS event validation is skipped since mns_validation_required is set to {context.mns_validation_required}" ) + + +def trigger_update_request_with_same_unique_id_and_uri_for_deleted_record(context): + get_update_url_header(context, str(context.expected_version)) + context.update_object = copy.deepcopy(context.immunization_object) + context.update_object = convert_to_update(context.update_object, context.ImmsID) + trigger_the_updated_request(context) diff --git a/tests/e2e_automation/features/APITests/steps/test_create_steps.py b/tests/e2e_automation/features/APITests/steps/test_create_steps.py index bebf4ece8f..dc74b7d489 100644 --- a/tests/e2e_automation/features/APITests/steps/test_create_steps.py +++ b/tests/e2e_automation/features/APITests/steps/test_create_steps.py @@ -1,10 +1,9 @@ import json import random from datetime import UTC, datetime, timedelta -from venv import logger import pytest_check as check -from pytest_bdd import given, parsers, scenarios, then, when +from pytest_bdd import given, parsers, scenarios, then from src.dynamoDB.dynamo_db_helper import ( fetch_immunization_events_detail, fetch_immunization_int_delta_detail_by_immsID, @@ -34,7 +33,7 @@ VACCINE_CODE_MAP, ) -from .common_steps import Trigger_the_post_create_request, valid_json_payload_is_created +from .common_steps import valid_json_payload_is_created scenarios("APITests/create.feature") @@ -242,8 +241,7 @@ def validate_imms_event_table(context): try: resource = json.loads(resource_json_str) - except (TypeError, json.JSONDecodeError) as e: - logger.error(f"Failed to parse Resource from item: {e}") + except (TypeError, json.JSONDecodeError): raise AssertionError("Failed to parse Resource from response item.") assert resource is not None, "Resource is None in the response" @@ -347,8 +345,8 @@ def validate_procedure_term_correct_coding_in_delta_table(context): @then("The delta table will use the first valid SNOMED site and route coding") def validate_delta_table_uses_first_valid_snomed_site_route_coding(context): actual_terms = get_all_term_text(context) - expected_site_term = context.create_object.site.coding[1].extension[0].valueString - expected_route_term = context.create_object.route.coding[1].extension[0].valueString + expected_site_term = context.create_object.site.text + expected_route_term = context.create_object.route.text assert actual_terms["site_term"] == expected_site_term, ( f"Expected site of vaccination term '{expected_site_term}', but got '{actual_terms['site_term']}'" ) @@ -449,8 +447,3 @@ def create_request_with_invalid_gender(context, gender): def create_request_with_empty_nam(context): valid_json_payload_is_created(context) context.immunization_object.contained[1].name = None - - -@when("Trigger another post create request with same unique_id and unique_id_uri") -def trigger_post_create_with_same_unique_id(context): - Trigger_the_post_create_request(context) diff --git a/tests/e2e_automation/features/APITests/steps/test_delete_steps.py b/tests/e2e_automation/features/APITests/steps/test_delete_steps.py index f9deed5f27..8c1a294664 100644 --- a/tests/e2e_automation/features/APITests/steps/test_delete_steps.py +++ b/tests/e2e_automation/features/APITests/steps/test_delete_steps.py @@ -1,11 +1,13 @@ -import logging +import copy import uuid from pytest_bdd import parsers, scenarios, then, when +from pytest_check.context_manager import check from src.dynamoDB.dynamo_db_helper import ( fetch_immunization_int_delta_detail_by_immsID, validate_imms_delta_record_with_created_event, ) +from src.objectModels.api_immunization_builder import convert_to_update from utilities.api_fhir_immunization_helper import ( find_entry_by_Imms_id, parse_FHIR_immunization_response, @@ -16,14 +18,21 @@ from .common_steps import ( The_request_will_have_status_code, + mns_event_will_be_triggered_with_correct_data_for_deleted_event, send_delete_for_immunization_event_created, + trigger_update_request_with_same_unique_id_and_uri_for_deleted_record, valid_token_is_generated, + validate_delta_table_for_updated_event, + validate_imms_event_table_by_operation, + validate_mns_event_triggered_for_updated_event, +) +from .test_search_steps import ( + send_search_post_request_with_identifier_header, + send_search_post_with_comma_separated_target_disease, + trigger_search_request, + validate_correct_immunization_event, + validate_empty_immunization_event, ) -from .test_search_steps import trigger_search_request - -logging.basicConfig(filename="debugLog.log", level=logging.INFO) -logger = logging.getLogger(__name__) - scenarios("APITests/delete.feature") @@ -48,12 +57,10 @@ def validate_imms_delta_table_by_deleted_ImmsID(context): items = fetch_immunization_int_delta_detail_by_immsID(context.aws_profile_name, context.ImmsID, context.S3_env, 2) assert items, f"Items not found in response for ImmsID: {context.ImmsID}" - # Find the latest item where operation is DELETE deleted_items = [i for i in items if i.get("Operation") == Operation.deleted.value] assert deleted_items, f"No deleted item found for ImmsID: {context.ImmsID}" - # Assuming each item has a 'timestamp' field to determine the latest - latest_delta_record = max(deleted_items, key=lambda x: x.get("timestamp", 0)) + latest_delta_record = max(deleted_items, key=lambda x: x.get("SequenceNumber", -1)) validate_imms_delta_record_with_created_event( context, @@ -92,3 +99,102 @@ def validate_deleted_immunization_event_not_present_using_post(context): assert context.created_event is None, ( f"Immunization event with ID {context.ImmsID} should not be present in the search response after deletion." ) + + +@then( + "The location key and Etag in header will contain the previous Immunization Id and version will be incremented by 1" +) +def validate_location_key_and_etag_in_header(context): + assert "location" in context.response.headers, ( + f"Location header is missing in the response with Status code: {context.response.status_code}. Response: {context.response.text}" + ) + assert "E-Tag" in context.response.headers, ( + f"E-Tag header is missing in the response with Status code: {context.response.status_code}. Response: {context.response.text}" + ) + print(f"\n Immunization ID is {context.ImmsID} and Etag is {context.eTag} \n") + location = context.response.headers["location"] + eTag = context.response.headers["E-Tag"] + context.expected_version += 1 + actualLocation = location.split("/")[-1] + check.is_true( + context.ImmsID == actualLocation, + f"Expected imms id sholud be : {context.ImmsID}, Found: {actualLocation}", + ) + check.is_true( + str(context.expected_version) == eTag.strip('"'), + f"Expected version should be : {context.expected_version}, Found: {eTag}", + ) + context.eTag = eTag.strip('"') + + +@then("The Etag in header will contain the correct version which will be incremented by 1") +def validate_etag_in_header(context): + eTag = context.response.headers["E-Tag"] + assert "E-Tag" in context.response.headers, ( + f"E-Tag header is missing in the response with Status code: {context.response.status_code}. Response: {context.response.text}" + ) + print(f"\n Etag is {context.eTag} \n") + check.is_true( + str(context.expected_version) == eTag.strip('"'), + f"Expected version should be : {context.expected_version}, Found: {eTag}", + ) + context.eTag = eTag.strip('"') + + +@then( + "IMMS event and delta tables, along with the MNS event, will be populated with correct updated data for the reinstated record" +) +@then( + "IMMS event and delta tables, along with the MNS event, will be populated with correct created data for the reinstated record" +) +def validate_delta_table_for_create_event_for_reinstated_record(context): + context.update_object = copy.deepcopy(context.immunization_object) + context.update_object = convert_to_update(context.update_object, context.ImmsID) + validate_imms_event_table_by_operation(context, "updated", reinstated=True) + validate_delta_table_for_updated_event(context) + validate_mns_event_triggered_for_updated_event(context) + + +@then("IMMS event and delta tables will be populated with correct updated data for the reinstated record") +def validate_delta_table_for_updated_event_for_reinstated_record(context): + validate_imms_event_table_by_operation(context, "updated", reinstated=True) + validate_delta_table_for_updated_event(context) + + +@when("Trigger update request with same unique_id and unique_id_uri for the deleted record") +def trigger_post_create_request_with_same_unique_id_and_uri(context): + context.immunization_object.contained[1].address[0].city = "Updated City" + context.immunization_object.contained[1].address[0].state = "Updated State" + context.immunization_object.contained[1].address[0].postalCode = "X99 3ZA" + trigger_update_request_with_same_unique_id_and_uri_for_deleted_record(context) + + +@then( + "IMMS event and delta tables, along with the MNS event, will be populated with correct data for the deleted record" +) +def validate_imms_event_delta_and_mns_for_deleted_record(context): + validate_imms_event_table_by_operation(context, "deleted") + validate_imms_delta_table_by_deleted_ImmsID(context) + mns_event_will_be_triggered_with_correct_data_for_deleted_event(context) + + +@when("I send a search request with Post method using identifier parameter for the record") +def trigger_search_request_for_deleted_record(context): + send_search_post_request_with_identifier_header(context) + + +@then("No immunization event is returned in the response") +def validate_no_immunization_event_returned_in_response(context): + validate_empty_immunization_event(context, identifier_search=False) + + +@then("reinstated record is returned in the response with correct created data") +def validate_reinstated_record_returned_in_response_with_correct_data(context): + validate_correct_immunization_event(context) + + +@when( + "Send a search request with post method using patient.identifier and target-disease for Immunization event deleted" +) +def trigger_search_request_with_patient_identifier_and_target_disease(context): + send_search_post_with_comma_separated_target_disease(context, "Post") diff --git a/tests/e2e_automation/features/APITests/steps/test_read_steps.py b/tests/e2e_automation/features/APITests/steps/test_read_steps.py index 73c34e49f3..ffc5ff3b30 100644 --- a/tests/e2e_automation/features/APITests/steps/test_read_steps.py +++ b/tests/e2e_automation/features/APITests/steps/test_read_steps.py @@ -1,4 +1,3 @@ -import logging import uuid from pytest_bdd import scenarios, then, when @@ -9,9 +8,6 @@ from utilities.api_get_header import get_read_url_header from utilities.http_requests_session import http_requests_session -logging.basicConfig(filename="debugLog.log", level=logging.INFO) -logger = logging.getLogger(__name__) - scenarios("APITests/read.feature") diff --git a/tests/e2e_automation/features/APITests/steps/test_search_steps.py b/tests/e2e_automation/features/APITests/steps/test_search_steps.py index 74a07c85c5..24cb4fdae5 100644 --- a/tests/e2e_automation/features/APITests/steps/test_search_steps.py +++ b/tests/e2e_automation/features/APITests/steps/test_search_steps.py @@ -467,7 +467,7 @@ def validate_correct_immunization_event_with_elements(context): @then("Empty immunization event is returned in the response") -def validate_empty_immunization_event(context): +def validate_empty_immunization_event(context, identifier_search=True): response = context.response.json() assert response.get("resourceType") == "Bundle", "resourceType should be 'Bundle'" assert response.get("type") == "searchset", "type should be 'searchset'" @@ -475,9 +475,14 @@ def validate_empty_immunization_event(context): link = response.get("link", [{}])[0] link_url = link.get("url") assert link_url is not None, " link[0].url is missing" - assert link_url == f"{context.baseUrl}/Immunization?identifier=None", ( - f"link[0].url should be '{context.baseUrl}/Immunization?identifier=None', got '{link_url}'" - ) + if identifier_search: + assert link_url == f"{context.baseUrl}/Immunization?identifier=None", ( + f"link[0].url should be '{context.baseUrl}/Immunization?identifier=None', got '{link_url}'" + ) + else: + assert link_url.startswith(context.baseUrl), ( + f"link[0].url should start with '{context.baseUrl}', got '{link_url}'" + ) assert response.get("total") == 0, "total should be 0" diff --git a/tests/e2e_automation/features/APITests/steps/test_update_steps.py b/tests/e2e_automation/features/APITests/steps/test_update_steps.py index a757d5c5f1..cfa82fd3ea 100644 --- a/tests/e2e_automation/features/APITests/steps/test_update_steps.py +++ b/tests/e2e_automation/features/APITests/steps/test_update_steps.py @@ -2,10 +2,6 @@ import uuid from pytest_bdd import parsers, scenarios, then, when -from src.dynamoDB.dynamo_db_helper import ( - fetch_immunization_int_delta_detail_by_immsID, - validate_imms_delta_record_with_created_event, -) from src.objectModels.api_immunization_builder import convert_to_update from utilities.api_fhir_immunization_helper import ( parse_error_response, @@ -13,10 +9,8 @@ ) from utilities.api_get_header import get_update_url_header from utilities.date_helper import generate_date -from utilities.enums import ActionFlag, Operation from .common_steps import ( - mns_event_will_be_triggered_with_correct_data, send_update_for_immunization_event, trigger_the_updated_request, valid_json_payload_is_created, @@ -32,28 +26,6 @@ def send_update_for_immunization_event_by_supplier(context, Supplier): send_update_for_immunization_event(context) -@then("The delta table will be populated with the correct data for updated event") -def validate_delta_table_for_updated_event(context): - create_obj = context.create_object - items = fetch_immunization_int_delta_detail_by_immsID( - context.aws_profile_name, - context.ImmsID, - context.S3_env, - context.expected_version, - ) - assert items, f"Items not found in response for ImmsID: {context.ImmsID}" - delta_items = [i for i in items if i.get("Operation") == Operation.updated.value] - assert delta_items, f"No item found for ImmsID: {context.ImmsID}" - latest_delta_record = max(delta_items, key=lambda x: x.get("SequenceNumber", -1)) - validate_imms_delta_record_with_created_event( - context, - create_obj, - latest_delta_record, - Operation.updated.value, - ActionFlag.updated.value, - ) - - @when( parsers.parse("Send a update for Immunization event created with occurrenceDateTime being updated to '{DateText}'") ) @@ -120,8 +92,3 @@ def validateForbiddenAccess(context, errorName): error_response = parse_error_response(context.response.json()) validate_error_response(error_response, errorName, version=context.version) print(f"\n Error Response - \n {error_response}") - - -@then("MNS event will be triggered with correct data for Updated event") -def validate_mns_event_triggered_for_updated_event(context): - mns_event_will_be_triggered_with_correct_data(context=context, action="UPDATE") diff --git a/tests/e2e_automation/features/batchTests/Steps/batch_common_steps.py b/tests/e2e_automation/features/batchTests/Steps/batch_common_steps.py index 9cdbec51bb..45e829ada0 100644 --- a/tests/e2e_automation/features/batchTests/Steps/batch_common_steps.py +++ b/tests/e2e_automation/features/batchTests/Steps/batch_common_steps.py @@ -270,6 +270,7 @@ def validate_imms_delta_table_for_dpsfull_records(context): ) +@then("The delta table will be populated with the correct data for reinstated record") @then("The delta table will be populated with the correct data for all updated records in batch file") def validate_imms_delta_table_for_updated_records(context): if context.delta_cache is None: @@ -289,14 +290,63 @@ def validate_imms_delta_table_for_deleted_records(context): "The imms event table will be populated with the correct data for '{operation}' event for records in batch file" ) ) -def validate_imms_event_table_for_all_records_in_batch_file(context, operation: Operation): +def validate_imms_event_table_for_given_operation_event(context, operation): + validate_imms_event_table_for_all_records_in_batch_file(context, operation) + + +@then("The imms event table will be populated with the correct data for reinstated record in batch file") +def validate_imms_event_table_for_reinstated_event(context): + validate_imms_event_table_for_all_records_in_batch_file(context, "updated", reinstated=True) + + +@then("all rejected records are listed in the csv bus ack file and no imms id is generated") +def all_record_are_rejected_for_given_field_name(context): + file_rows = read_and_validate_csv_bus_ack_file_content(context) + all_valid = validate_bus_ack_file_for_error(context, file_rows) + assert all_valid, "One or more records failed validation checks" + + +@then(parsers.parse("MNS event will be triggered with correct data for all '{event_type}' events where NHS is not null")) +def mns_event_will_be_triggered_with_correct_data_for_created_events_in_batch_file(context, event_type): + if context.mns_validation_required.strip().lower() != "true": + print( + f"MNS event validation is skipped since mns_validation_required is set to {context.mns_validation_required}" + ) + return + + action = event_type.upper() if event_type.upper() in ["CREATE", "UPDATE"] else "CREATE" + + df = context.vaccine_df.dropna(subset=["IMMS_ID"]).copy() + df["IMMS_ID_CLEAN"] = df["IMMS_ID"].astype(str).str.replace("Immunization#", "", regex=False) + + valid_rows = list(df.itertuples(index=False)) + + if not valid_rows: + print("No valid NHS rows found — skipping MNS validation.") + return + + mns_event_will_be_triggered_for_batch_record(context=context, action=action, valid_rows=valid_rows) + + +@then("Api updated event will trigger MNS event with correct data") +def mns_event_will_be_triggered_with_correct_data_for_api_updated_events(context): + mns_event_will_be_triggered_with_correct_data(context=context, action="UPDATE") + + +def normalize(value): + return "" if pd.isna(value) or value == "" else value + + +def validate_imms_event_table_for_all_records_in_batch_file(context, operation: Operation, reinstated=False): mapping = ActionMap[operation.lower()] df = context.vaccine_df[context.vaccine_df["ACTION_FLAG"].str.lower() == mapping.action_flag.value.lower()] df["UNIQUE_ID_COMBINED"] = df["UNIQUE_ID_URI"].astype(str) + "#" + df["UNIQUE_ID"].astype(str) valid_rows = df[df["UNIQUE_ID_COMBINED"].notnull() & (df["UNIQUE_ID_COMBINED"] != "nan#nan")] - for idx, row in valid_rows.iterrows(): + unique_rows = valid_rows.drop_duplicates(subset=["UNIQUE_ID_COMBINED"]) + + for idx, row in unique_rows.iterrows(): unique_id_combined = row["UNIQUE_ID_COMBINED"] batch_record = {k: normalize(v) for k, v in row.to_dict().items()} @@ -339,48 +389,28 @@ def validate_imms_event_table_for_all_records_in_batch_file(context, operation: ("Version", int(context.expected_version), int(item.get("Version"))), ] + actualDeletedAt = item.get("DeletedAt") + for name, expected, actual in fields_to_compare: check.is_true(expected == actual, f"Expected {name}: {expected}, Actual {actual}") - validate_to_compare_batch_record_with_event_table_record(context, batch_record, created_event) - - -@then("all rejected records are listed in the csv bus ack file and no imms id is generated") -def all_record_are_rejected_for_given_field_name(context): - file_rows = read_and_validate_csv_bus_ack_file_content(context) - all_valid = validate_bus_ack_file_for_error(context, file_rows) - assert all_valid, "One or more records failed validation checks" - - -@then(parsers.parse("MNS event will be triggered with correct data for all '{event_type}' events where NHS is not null")) -def mns_event_will_be_triggered_with_correct_data_for_created_events_in_batch_file(context, event_type): - if context.mns_validation_required.strip().lower() != "true": - print( - f"MNS event validation is skipped since mns_validation_required is set to {context.mns_validation_required}" - ) - return - - action = event_type.upper() if event_type.upper() in ["CREATE", "UPDATE"] else "CREATE" - - df = context.vaccine_df.dropna(subset=["IMMS_ID"]).copy() - df["IMMS_ID_CLEAN"] = df["IMMS_ID"].astype(str).str.replace("Immunization#", "", regex=False) - - valid_rows = list(df.itertuples(index=False)) - - if not valid_rows: - print("No valid NHS rows found — skipping MNS validation.") - return - - mns_event_will_be_triggered_for_batch_record(context=context, action=action, valid_rows=valid_rows) - - -@then("Api updated event will trigger MNS event with correct data") -def mns_event_will_be_triggered_with_correct_data_for_api_updated_events(context): - mns_event_will_be_triggered_with_correct_data(context=context, action="UPDATE") - + if Operation[operation].value == "DELETE": + check.is_true( + actualDeletedAt is not None and actualDeletedAt > 0, + f"Expected DeletedAt to be a Unix timestamp, got {actualDeletedAt}", + ) + elif reinstated: + check.is_true( + actualDeletedAt == "reinstated", + f"Expected DeletedAt: None for reinstated record, got {actualDeletedAt}", + ) + else: + check.is_true( + actualDeletedAt is None, + f"Expected DeletedAt: None, Actual {actualDeletedAt}", + ) -def normalize(value): - return "" if pd.isna(value) or value == "" else value + validate_to_compare_batch_record_with_event_table_record(context, batch_record, created_event) def create_batch_file(context, file_ext: str = "csv", fileName: str = None, delimiter: str = "|"): @@ -450,19 +480,18 @@ def preload_delta_data(context): context.delta_cache[clean_id] = {"rows": group, "delta_items": delta_items} -def validate_imms_delta_table_for_newly_created_records_in_batch_file(context): +def validate_imms_delta_table_for_newly_created_records_in_batch_file(context, expected_number_of_items=1): for clean_id, data in context.delta_cache.items(): rows = data["rows"] delta_items = data["delta_items"] create_items = [i for i in delta_items if i.get("Operation") == "CREATE"] - check.is_true( - len(create_items) == 1, - f"Expected exactly 1 CREATE record for IMMS_ID {clean_id}, found {len(create_items)}", + assert len(create_items) == expected_number_of_items, ( + f"Expected exactly {expected_number_of_items} CREATE record(s) for IMMS_ID {clean_id}, found {len(create_items)}" ) - create_item = create_items[0] + create_item = max(create_items, key=lambda x: x.get("SequenceNumber", -1)) for _, row in rows[rows["ACTION_FLAG"] == "NEW"].iterrows(): batch_record = {k: normalize(v) for k, v in row.to_dict().items()} @@ -497,23 +526,23 @@ def validate_imms_delta_table_for_updated_records_in_batch_file(context): ) -def validate_imms_delta_table_for_deleted_records_in_batch_file(context): +def validate_imms_delta_table_for_deleted_records_in_batch_file(context, expected_number_of_items=1): for clean_id, data in context.delta_cache.items(): rows = data["rows"] delta_items = data["delta_items"] - delete_item = next((i for i in delta_items if i.get("Operation") == "DELETE"), None) + delete_items = [i for i in delta_items if i.get("Operation") == "DELETE"] - check.is_true(delete_item, f"No DELETE record for IMMS_ID {clean_id}") + delete_item = max(delete_items, key=lambda x: x.get("SequenceNumber", -1)) delete_rows = rows[rows["ACTION_FLAG"] == "DELETE"] - check.is_true( - len(delete_rows) == 1, - f"Expected exactly 1 DELETE row in batch file for IMMS_ID {clean_id}, found {len(delete_rows)}", + assert len(delete_rows) == expected_number_of_items, ( + f"Expected exactly {expected_number_of_items} DELETE row(s) in batch file for IMMS_ID {clean_id}, found {len(delete_rows)}" ) row = delete_rows.iloc[0] + batch_record = {k: normalize(v) for k, v in row.to_dict().items()} validate_imms_delta_record_with_batch_record( diff --git a/tests/e2e_automation/features/batchTests/Steps/test_delete_batch_steps.py b/tests/e2e_automation/features/batchTests/Steps/test_delete_batch_steps.py index 96006e7510..755294909c 100644 --- a/tests/e2e_automation/features/batchTests/Steps/test_delete_batch_steps.py +++ b/tests/e2e_automation/features/batchTests/Steps/test_delete_batch_steps.py @@ -82,3 +82,20 @@ def validate_imms_event_table_for_delete_event(context): @then("The delta table will have delete entry with no change to record detail") def validate_delta_table_for_delete_event(context): validate_imms_delta_table_by_deleted_ImmsID(context) + + +@given( + "batch file is created for below data as full dataset and each record delete and the followed with create/update action flag" +) +@ignore_if_local_run +def valid_batch_file_is_created_with_delete_action_flag_and_create_update_action_flag(datatable, context): + build_dataFrame_using_datatable(datatable, context) + df_new = context.vaccine_df.copy() + df_update = df_new.copy() + df_update["ACTION_FLAG"] = "DELETE" + df_reinstated = df_new.copy() + df_reinstated.iloc[0, df_reinstated.columns.get_loc("ACTION_FLAG")] = "NEW" + df_reinstated.iloc[1, df_reinstated.columns.get_loc("ACTION_FLAG")] = "UPDATE" + context.vaccine_df = pd.concat([df_new, df_update, df_reinstated], ignore_index=True) + create_batch_file(context) + context.expected_version = 2 diff --git a/tests/e2e_automation/features/batchTests/Steps/test_update_batch_steps.py b/tests/e2e_automation/features/batchTests/Steps/test_update_batch_steps.py index 8ab8e4c423..fe2fb8a532 100644 --- a/tests/e2e_automation/features/batchTests/Steps/test_update_batch_steps.py +++ b/tests/e2e_automation/features/batchTests/Steps/test_update_batch_steps.py @@ -18,6 +18,7 @@ mns_event_will_be_triggered_with_correct_data, send_update_for_immunization_event, valid_json_payload_is_created, + validate_delta_table_for_updated_event, validate_etag_in_header, validate_imms_event_table_by_operation, validateCreateLocation, @@ -26,9 +27,6 @@ from features.APITests.steps.test_create_steps import ( validate_imms_delta_table_by_ImmsID, ) -from features.APITests.steps.test_update_steps import ( - validate_delta_table_for_updated_event, -) from .batch_common_steps import ( build_batch_row_from_api_object, diff --git a/tests/e2e_automation/features/batchTests/delete_batch.feature b/tests/e2e_automation/features/batchTests/delete_batch.feature index 36d150a558..c9a00b1abb 100644 --- a/tests/e2e_automation/features/batchTests/delete_batch.feature +++ b/tests/e2e_automation/features/batchTests/delete_batch.feature @@ -53,3 +53,22 @@ Feature: Create the immunization event for a patient through batch file and upda And Json bus ack will only contain file metadata and no failure record entry And The imms event table status will be updated to delete and no change to record detail And The delta table will have delete entry with no change to record detail + + + @vaccine_type_RSV @patient_id_Random @supplier_name_RAVS + Scenario: Verify that the imms event can be reinstated after soft delete through create and/or update Action Flag + Given batch file is created for below data as full dataset and each record delete and the followed with create/update action flag + | patient_id | unique_id | + | Random | reinstate_with_create | + | Random | reinstate_with_update | + When batch file is uploaded in s3 bucket + Then file will be moved to destination bucket and inf ack file will be created + And inf ack file has success status for processed batch file + And bus ack files will be created + And CSV bus ack will not have any entry of successfully processed records + And Json bus ack will only contain file metadata and no failure record entry + And Audit table will have correct status, queue name and record count for the processed batch file + And The imms event table will be populated with the correct data for reinstated record in batch file + And The delta table will be populated with the correct data for all created records in batch file + And The delta table will be populated with the correct data for reinstated record + And The delta table will be populated with the correct data for all deleted records in batch file \ No newline at end of file diff --git a/tests/e2e_automation/features/conftest.py b/tests/e2e_automation/features/conftest.py index eb6166cc9b..500b20ea5c 100644 --- a/tests/e2e_automation/features/conftest.py +++ b/tests/e2e_automation/features/conftest.py @@ -173,10 +173,7 @@ def pytest_bdd_after_scenario(request, feature, scenario): ) if context.response.status_code == 204: - if context.mns_validation_required.strip().lower() == "true": - mns_event_will_be_triggered_with_correct_data_for_deleted_event(context) - else: - print("MNS validation not required, skipping MNS event verification for deleted event.") + mns_event_will_be_triggered_with_correct_data_for_deleted_event(context) else: print( f"DELETE request failed with status code {context.response.status_code} for ImmsID {context.ImmsID}. " diff --git a/tests/e2e_automation/utilities/aws_token.py b/tests/e2e_automation/utilities/aws_token.py index 513308a00f..480a9e54fa 100644 --- a/tests/e2e_automation/utilities/aws_token.py +++ b/tests/e2e_automation/utilities/aws_token.py @@ -1,12 +1,8 @@ -import logging import os import subprocess import boto3 -logging.basicConfig(filename="debugLog.log", level=logging.INFO) -logger = logging.getLogger(__name__) - def set_aws_session_token(): try: diff --git a/tests/e2e_automation/utilities/context.py b/tests/e2e_automation/utilities/context.py index 790135e648..ac92bc130f 100644 --- a/tests/e2e_automation/utilities/context.py +++ b/tests/e2e_automation/utilities/context.py @@ -50,3 +50,4 @@ def __init__(self): self.aws_account_id = None self.gp_code = None self.mns_validation_required = False + self.patient_age = 0