diff --git a/.gitignore b/.gitignore index 66e4e335e7817b3622c0e3d74ea3ad807702da9a..e19b88a4ceba1e3e4a81b97058120fd5b870d26d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ __pycache__/ *.crt *.zip *.srl +*.log services/nginx/certs/sign_req_body.json services/easy_rsa/certs/pki services/easy_rsa/certs/*EasyRSA* @@ -35,4 +36,4 @@ docs/testing_with_postman/package-lock.json results helm/capif/*.lock -helm/capif/charts \ No newline at end of file +helm/capif/charts diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py index c6a6b87c6f3ce6523835b9967cb95ecff7cc4539..e81386b346f659dd45227760203d54df5246f71d 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py @@ -59,11 +59,11 @@ class Notifications(): # current_app.logger.debug(json.dumps(event_detail,cls=JSONEncoder)) event_detail={redis_event.get('key'):redis_event.get('information')} - data = EventNotification(sub["subscription_id"], events=redis_event.get('event'), event_detail=[event_detail]) + data = EventNotification(sub["subscription_id"], events=redis_event.get('event'), event_detail=event_detail) current_app.logger.debug(json.dumps(data,cls=JSONEncoder)) # self.request_post(url, data) - asyncio.run(self.send(url, json.dumps(data,cls=JSONEncoder))) + asyncio.run(self.send(url, json.loads(json.dumps(data,cls=JSONEncoder)))) except Exception as e: current_app.logger.error("An exception occurred ::" + str(e)) @@ -76,7 +76,8 @@ class Notifications(): async def send_request(self, url, data): async with aiohttp.ClientSession() as session: timeout = aiohttp.ClientTimeout(total=10) # Establecer timeout a 10 segundos - async with session.post(url, json=data, timeout=timeout) as response: + headers = {'content-type': 'application/json'} + async with session.post(url, json=data, timeout=timeout, headers=headers) as response: return await response.text() async def send(self, url, data): diff --git a/services/clean_capif_docker_services.sh b/services/clean_capif_docker_services.sh index fb8949784d4eb81d5a0b958c197765f82118d60d..1bc6d6dc1a1a341e40029d97c12af679237a9b87 100755 --- a/services/clean_capif_docker_services.sh +++ b/services/clean_capif_docker_services.sh @@ -74,4 +74,6 @@ done docker network rm capif-network +docker volume prune --all --force + echo "Clean complete." diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index 9ee68ffd904db607e4dd5f42623c373450d9662c..c4b60855a19740f15faa28ac5851b5f30d676b1f 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -5,7 +5,6 @@ Library XML Resource /opt/robot-tests/tests/resources/common/basicRequests.robot Resource ../../resources/common.resource - Suite Teardown Reset Testing Environment Test Setup Reset Testing Environment @@ -139,13 +138,12 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId ... detail=User not authorized ... cause=You are not the owner of this resource - Prueba JMS [Tags] jms-1 - # Log "Prueba 1" - # Wait For Request - # Create a log entry - # [Tags] capif_api_logging_service-1 + + # Start Mock server + Check Mock Server + Clean Mock Server # Register APF ${register_user_info}= Provider Default Registration @@ -165,8 +163,10 @@ Prueba JMS ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} # Subscribe to events - ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE - ${request_body}= Create Events Subscription events=@{events_list} notificationDestination=http://192.168.0.119:9090/testing + ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=http://192.168.0.14:9090/testing ${resp}= Post Request Capif ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions ... json=${request_body} @@ -178,13 +178,122 @@ Prueba JMS Check Response Variable Type And Values ${resp} 201 EventSubscription ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + ${results}= Create List 200 400 + + # Create Log Entry + ${request_body}= Create Log Entry + ... ${register_user_info['aef_id']} + ... ${register_user_info_invoker['api_invoker_id']} + ... ${api_ids} + ... ${api_names} + ... results=${results} + ${resp}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 InvocationLog + ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} + + Sleep 3s + + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + # Check if message follow EventNotification definition. + Check Variable ${notification_events_on_mock_server} EventNotification + # Check if mock server receive 2 events. + Length Should Be ${notification_events_on_mock_server} 2 + + # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. + ${invocation_log_base}= Copy Dictionary ${request_body} deepcopy=True + + # Store log array because each log will be notified in one Event Notification + ${invocation_log_logs}= Set Variable ${invocation_log_base['logs']} + # Remove logs array from invocationLog data + Remove From Dictionary ${invocation_log_base} logs + Length Should Be ${invocation_log_logs} 2 + + # Create 2 invocationLogs from initial invocationLog present on request + ${invocation_logs_1}= Copy Dictionary ${invocation_log_base} deepcopy=True + ${invocation_logs_2}= Copy Dictionary ${invocation_log_base} deepcopy=True + # Create a log array with only one component + ${log_1}= Create List ${invocation_log_logs[0]} + ${log_2}= Create List ${invocation_log_logs[1]} + # Setup logs array with previously created list + Set To Dictionary ${invocation_logs_1} logs=${log_1} + Set To Dictionary ${invocation_logs_2} logs=${log_2} + # Create event details for each log + ${event_details_1}= Create dictionary invocationLogs=${invocation_logs_1} + ${event_details_2}= Create dictionary invocationLogs=${invocation_logs_2} + # Create Event with Event Details from invocationLog + ${event_1}= Create Dictionary + ... subscriptionId=${subscription_id} + ... events=SERVICE_API_INVOCATION_SUCCESS + ... eventDetail=${event_details_1} + ${event_2}= Create Dictionary + ... subscriptionId=${subscription_id} + ... events=SERVICE_API_INVOCATION_FAILURE + ... eventDetail=${event_details_2} + # Check if created events follow EventNotification format defined by 3gpp + Check Variable ${event_1} EventNotification + Check Variable ${event_2} EventNotification + + List Should Contain Value ${notification_events_on_mock_server} ${event_1} + List Should Contain Value ${notification_events_on_mock_server} ${event_2} + +Prueba JMS + [Tags] jms-2 + + # Start Mock server + Check Mock Server + Clean Mock Server + + # Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Subscribe to events + ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=http://192.168.0.14:9090/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + ${results}= Create List 200 400 + # Create Log Entry ${request_body}= Create Log Entry ... ${register_user_info['aef_id']} ... ${register_user_info_invoker['api_invoker_id']} ... ${api_ids} ... ${api_names} - ... 200 + ... results=${results} ${resp}= Post Request Capif ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs ... json=${request_body} @@ -196,3 +305,67 @@ Prueba JMS Check Response Variable Type And Values ${resp} 201 InvocationLog ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} + Sleep 3s + + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + # Check if message follow EventNotification definition. + Check Variable ${notification_events_on_mock_server} EventNotification + + # Create check Events to ensure all notifications were received + ${check_events} ${check_events_length}= Create Events From invocationLogs + ... ${subscription_id} + ... ${request_body} + + # Number of events received must be equal than events expected + Length Should Be ${notification_events_on_mock_server} ${check_events_length} + # Check if events received are the same than expected + FOR ${event} IN @{check_events} + Log ${event} + List Should Contain Value ${notification_events_on_mock_server} ${event} + END + + +*** Keywords *** +Create Events From invocationLogs + [Arguments] ${subscription_id} ${invocation_log} + + ${events}= Create List + + # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. + ${invocation_log_base}= Copy Dictionary ${invocation_log} deepcopy=True + # Store log array because each log will be notified in one Event Notification + ${invocation_log_logs}= Copy List ${invocation_log_base['logs']} + # Remove logs array from invocationLog data + Remove From Dictionary ${invocation_log_base} logs + + FOR ${log} IN @{invocation_log_logs} + Log Dictionary ${log} + ${invocation_logs}= Copy Dictionary ${invocation_log_base} deepcopy=True + + # Get Event Enum for this result + ${event_enum}= Set Variable + IF ${log['result']} >= 200 and ${log['result']} < 300 + ${event_enum}= Set Variable SERVICE_API_INVOCATION_SUCCESS + ELSE + ${event_enum}= Set Variable SERVICE_API_INVOCATION_FAILURE + END + # Create a log array with only one component + ${log_list}= Create List ${log} + # Setup logs array with previously created list + Set To Dictionary ${invocation_logs} logs=${log_list} + # Create event details for each log + ${event_details}= Create dictionary invocationLogs=${invocation_logs} + # Create Event with Event Details from invocationLog and also is appended to events array + ${event}= Create Dictionary + ... subscriptionId=${subscription_id} + ... events=${event_enum} + ... eventDetail=${event_details} + Check Variable ${event} EventNotification + Append To List ${events} ${event} + END + + Log List ${events} + ${events_length}= Get Length ${events} + RETURN ${events} ${events_length} diff --git a/tests/libraries/api_logging_service/bodyRequests.py b/tests/libraries/api_logging_service/bodyRequests.py index e0e70daefb41b2de686172d41e4e25563d388da2..9ab8210951099f50cf9db7b8cd022375b9ad96e2 100644 --- a/tests/libraries/api_logging_service/bodyRequests.py +++ b/tests/libraries/api_logging_service/bodyRequests.py @@ -1,81 +1,13 @@ -def create_log_entry(aefId, apiInvokerId, apiId, apiName, result='200'): +def create_log_entry(aefId, apiInvokerId, apiId, apiName, results=['200']): data = { "aefId": aefId, "apiInvokerId": apiInvokerId, - "logs": [ - { - "apiId": apiId[0], - "apiName": apiName[0], - "apiVersion": "v1", - "resourceName": "string", - "uri": "http://resource/endpoint", - "protocol": "HTTP_1_1", - "operation": "GET", - "result": result, - "invocationTime": "2023-03-30T10:30:21.408Z", - "invocationLatency": 0, - "inputParameters": "string", - "outputParameters": "string", - "srcInterface": { - "ipv4Addr": "192.168.1.1", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "destInterface": { - "ipv4Addr": "192.168.1.23", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "fwdInterface": "string" - }, - { - "apiId": apiId[0], - "apiName": apiName[0], - "apiVersion": "v2", - "resourceName": "string", - "uri": "http://resource/endpoint", - "protocol": "HTTP_1_1", - "operation": "GET", - "result": result, - "invocationTime": "2023-03-30T10:30:21.408Z", - "invocationLatency": 0, - "inputParameters": "string", - "outputParameters": "string", - "srcInterface": { - "ipv4Addr": "192.168.1.1", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "destInterface": { - "ipv4Addr": "192.168.1.23", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "fwdInterface": "string" - } - ], + "logs": [], "supportedFeatures": "ffff" } + if len(results) > 0: + for result in results: + data['logs'].append(create_log(apiId,apiName,result)) return data def create_log_entry_bad_service(aefId, apiInvokerId, result=500): @@ -92,28 +24,24 @@ def create_log_entry_bad_service(aefId, apiInvokerId, result=500): "protocol": "HTTP_1_1", "operation": "GET", "result": result, - "invocationTime": "2023-03-30T10:30:21.408Z", + "invocationTime": "2023-03-30T10:30:21.408000+00:00", "invocationLatency": 0, "inputParameters": "string", "outputParameters": "string", "srcInterface": { "ipv4Addr": "192.168.1.1", - "fqdn": "string", "port": 65535, - "apiPrefix": "string", "securityMethods": [ "PSK", - "string" + "PKI" ] }, "destInterface": { "ipv4Addr": "192.168.1.23", - "fqdn": "string", "port": 65535, - "apiPrefix": "string", "securityMethods": [ "PSK", - "string" + "PKI" ] }, "fwdInterface": "string" @@ -131,3 +59,29 @@ def get_api_ids_and_names_from_discover_response(discover_response): api_ids.append(service_api_description['apiId']) api_names.append(service_api_description['apiName']) return api_ids, api_names + + +def create_log(apiId, apiName, result): + log= { + "apiId": apiId[0], + "apiName": apiName[0], + "apiVersion": "v1", + "resourceName": "string", + "uri": "http://resource/endpoint", + "protocol": "HTTP_1_1", + "operation": "GET", + "result": result, + "invocationTime": "2023-03-30T10:30:21.408000+00:00", + "invocationLatency": 0, + "inputParameters": "string", + "outputParameters": "string", + "srcInterface": { + "ipv4Addr": "192.168.1.1", + "port": 65535, + "securityMethods": [ + "PSK", + "PKI" + ] + } + } + return log \ No newline at end of file diff --git a/tests/libraries/common/bodyRequests.py b/tests/libraries/common/bodyRequests.py index 3f1e482d4717bc5d3ddfc298962afb8e42bc1a7e..1d4ec14f60759bfb221b686ba84df23b1386fa6c 100644 --- a/tests/libraries/common/bodyRequests.py +++ b/tests/libraries/common/bodyRequests.py @@ -15,7 +15,7 @@ def check_variable(input, data_type): if isinstance(input, list): for one in input: check_variable(one, data_type) - return True + return True if data_type == "string": if isinstance(input, str): return True diff --git a/tests/libraries/common/types.json b/tests/libraries/common/types.json index 4b683595c43c912f868856107d21ed84ad08032d..220dd0f5710796fd875ddda8d05dfb3e39a272cd 100644 --- a/tests/libraries/common/types.json +++ b/tests/libraries/common/types.json @@ -259,6 +259,67 @@ "aefIds": "string" } }, + "EventNotification": { + "mandatory_attributes": { + "subscriptionId": "string", + "events": "CAPIFEvent" + }, + "optional_attributes": { + "eventDetail": "CAPIFEventDetail" + } + }, + "CAPIFEventDetail": { + "mandatory_attributes": {}, + "optional_attributes": { + "serviceAPIDescriptions": "ServiceAPIDescription", + "apiIds": "string", + "apiInvokerIds": "string", + "accCtrlPolList": "AccessControlPolicyListExt", + "invocationLogs": "InvocationLog", + "apiTopoHide": "TopologyHiding" + } + }, + "AccessControlPolicyListExt":{ + "mandatory_attributes": { + "apiId": "string" + }, + "optional_attributes": { + "apiInvokerPolicies": "ApiInvokerPolicy" + } + }, + "TopologyHiding": { + "mandatory_attributes":{ + "apiId": "string", + "routingRules": "RoutingRule" + }, + "optional_attributes": {} + }, + "RoutingRule":{ + "mandatory_attributes": { + "aefProfile": "AefProfile" + }, + "optional_attributes": { + "ipv4AddrRanges": "Ipv4AddressRange", + "ipv6AddrRanges": "Ipv6AddressRange" + } + }, + "Ipv4AddressRange":{ + "mandatory_attributes": {}, + "optional_attributes": { + "start": "Ipv4Addr", + "stop": "Ipv4Addr" + } + }, + "Ipv4Addr": { + "regex": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$" + }, + "Ipv6AddressRange":{ + "mandatory_attributes":{ + "start": "string", + "end": "string" + }, + "optional_attributes":{} + }, "ReportingInformation": { "mandatory_attributes": {}, "optional_attributes": { diff --git a/tests/libraries/mock_server/mock_server.py b/tests/libraries/mock_server/mock_server.py index 151f09658c390b903e8005a86d81892de2297106..18d3f92e7078586263e100658844db690f79ad96 100644 --- a/tests/libraries/mock_server/mock_server.py +++ b/tests/libraries/mock_server/mock_server.py @@ -39,6 +39,7 @@ def configure_logging(app): def index(): if request.method == 'POST': app.logger.debug(request.json) + app.logger.debug(request.headers) requests_received.append(request.json) return 'Mock Server is running' diff --git a/tests/libraries/mock_server/requirements.txt b/tests/libraries/mock_server/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..abf28620ee482efbabd6ccf8a2b8d8b554563b03 --- /dev/null +++ b/tests/libraries/mock_server/requirements.txt @@ -0,0 +1 @@ +flask==3.0.3 \ No newline at end of file diff --git a/tests/requirements.txt b/tests/requirements.txt index b983217099239ea516f4ebc641f903172b0088b6..3119b5ca73095dfdc4a018dc35abaf83c5bc5ab2 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,2 +1,2 @@ # Requirements file for tests. -robotframework-archivelibrary == 0.4.2 + diff --git a/tests/resources/common.resource b/tests/resources/common.resource index c46131b17388b006b9fa7f521033a3c32874c171..4f8136e53ee7f436c48892e77151387c2955c9f5 100644 --- a/tests/resources/common.resource +++ b/tests/resources/common.resource @@ -1,5 +1,7 @@ *** Settings *** Library /opt/robot-tests/tests/libraries/helpers.py +Library Process +Library Collections Variables /opt/robot-tests/tests/libraries/environment.py Resource /opt/robot-tests/tests/resources/common/basicRequests.robot @@ -30,6 +32,9 @@ ${CAPIF_CALLBACK_PORT} 8086 ${REGISTER_ADMIN_USER} admin ${REGISTER_ADMIN_PASSWORD} password123 +${MOCK_SERVER_URL} http://192.168.0.14:9090 + + ${DISCOVER_URL} /service-apis/v1/allServiceAPIs?api-invoker-id= @@ -85,3 +90,72 @@ Remove Keys From Object Test ${TEST NAME} Currently Not Supported Log Test "${TEST NAME}" Currently not supported WARN Skip Test "${TEST NAME}" Currently not supported + +Start Mock server + Log Starting mock Server for Robot Framework. + + # Run Process pip3 install -r /opt/robot-tests/tests/libraries/mock_server/requirements.txt + ${server_process}= Start Process python3 /opt/robot-tests/tests/libraries/mock_server/mock_server.py + Log PID: ${server_process.pid} + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /testing + + ${json}= Create Dictionary events="SERVICE_API_INVOCATION_SUCCESS" subscriptionId="255545008cd3937f5554a3651bbfb9" + + ${resp}= POST On Session + ... mockserver + ... ${endpoint} + ... json=${json} + ... expected_status=any + ... verify=False + + ${result}= Terminate Process ${server_process} + +Check Mock Server + Log Checking mock Server for Robot Framework. + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /requests_list + + ${resp}= GET On Session + ... mockserver + ... ${endpoint} + ... expected_status=any + ... verify=False + + Status Should Be 200 ${resp} + +Clean Mock Server + Log Checking mock Server for Robot Framework. + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /requests_list + + ${resp}= DELETE On Session + ... mockserver + ... ${endpoint} + ... expected_status=any + ... verify=False + + Status Should Be 200 ${resp} + + +Get Mock Server Messages + Log Checking mock Server for Robot Framework. + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /requests_list + + ${resp}= GET On Session + ... mockserver + ... ${endpoint} + ... expected_status=any + ... verify=False + + Status Should Be 200 ${resp} + RETURN ${resp} \ No newline at end of file diff --git a/tools/robot/basicRequirements.txt b/tools/robot/basicRequirements.txt index 99eafd7e511dfc85fb8d4a0f0af1bdb43ed9d189..282a034af442c92c2f6120babb03df7290ece387 100644 --- a/tools/robot/basicRequirements.txt +++ b/tools/robot/basicRequirements.txt @@ -25,6 +25,7 @@ docutils==0.19 exceptiongroup==1.0.0rc9 filelock==3.8.0 flake8==3.9.2 +flask==3.0.3 h11==0.14.0 idna==3.4 iniconfig==1.1.1 @@ -69,6 +70,7 @@ rellu==0.7 requests==2.28.1 rfc3987==1.3.8 robotframework==7.0 +robotframework-archivelibrary == 0.4.2 robotframework-browser==18.3.0 robotframework-httpctrl==0.3.1 robotframework-lint==1.1 @@ -95,8 +97,8 @@ trio-websocket==0.9.2 typing-extensions==4.11.0 urllib3==1.26.12 virtualenv==20.16.5 -watchdog==0.9.0 +watchdog==4.0.0 webdrivermanager==0.10.0 wrapt==1.15.0 wsproto==1.2.0 -xlrd==2.0.1 \ No newline at end of file +xlrd==2.0.1