From d6f1eac600b79c8b488978381f6d97e8fa3eb96b Mon Sep 17 00:00:00 2001 From: garciay Date: Fri, 24 May 2019 07:14:27 -0700 Subject: [PATCH] Add RLOC tests --- ccsrc/Protocols/Http/http_codec.cc | 15 +-- ccsrc/Protocols/Json/json_codec.cc | 13 ++- etc/AtsMec/AtsMec_LocationAPI.cfg | 5 +- ttcn/AtsMec/AtsMec_LocationAPI_TestCases.ttcn | 2 +- ...AtsMec_RadioNodeLocationAPI_TestCases.ttcn | 99 +++++++++++++++++++ ttcn/AtsMec/AtsMec_TestControl.ttcn | 4 + .../LocationAPI/ttcn/LocationAPI_Pixits.ttcn | 10 +- .../ttcn/LocationAPI_Templates.ttcn | 62 ++++++++---- .../ttcn/LocationAPI_TypesAndValues.ttcn | 16 ++- ttcn/TestCodec/TestCodec_External.ttcn | 6 +- .../LibItsHttp_JsonMessageBodyTypes.ttcn | 1 + .../LibItsHttp_JsonTemplates.ttcn | 14 ++- 12 files changed, 190 insertions(+), 57 deletions(-) create mode 100644 ttcn/AtsMec/AtsMec_RadioNodeLocationAPI_TestCases.ttcn diff --git a/ccsrc/Protocols/Http/http_codec.cc b/ccsrc/Protocols/Http/http_codec.cc index b40c040..1a8a232 100644 --- a/ccsrc/Protocols/Http/http_codec.cc +++ b/ccsrc/Protocols/Http/http_codec.cc @@ -239,7 +239,7 @@ int http_codec::encode_response (const LibItsHttp__TypesAndValues::Response& p_r } p_encoding_buffer.put_cs("\r\n"); - // Encode headers excepeted the Content-Length + // Encode headers excepted the Content-Length const LibItsHttp__TypesAndValues::HeaderLines& headers = p_response.header(); std::string content_type; for (int i = 0; i < headers.size_of(); i++) { @@ -504,16 +504,7 @@ int http_codec::decode_body(TTCN_Buffer& decoding_buffer, LibItsHttp__MessageBod OCTETSTRING s(decoding_buffer.get_len() - decoding_buffer.get_pos(), decoding_buffer.get_data() + decoding_buffer.get_pos()); loggers::get_instance().log_msg("http_codec::decode_body: raw body=", s); -#if defined(GEMALTO_FIX) // Temporary fix to be removed - // GEMALTO Encode in hex string - if ((s.lengthof() & 0x00000001) == 0x00000001) { - s = int2oct(0, 1) + s; - } - s = str2oct(CHARSTRING(s.lengthof(), (const char*)(static_cast(s)))); - loggers::get_instance().log_msg("http_codec::decode_body: Convert string to binary: ", s); -#endif - - // Align the payload length with the specified Content-Lenght value + // Align the payload length with the specified Content-Length value loggers::get_instance().log("http_codec::decode_body: _dc.length=%d - body length=%d", _dc.length, s.lengthof()); OCTETSTRING body; if (_dc.length != 0) { @@ -524,7 +515,7 @@ int http_codec::decode_body(TTCN_Buffer& decoding_buffer, LibItsHttp__MessageBod body = OCTETSTRING(_dc.length, p); } } else { - loggers::get_instance().warning("http_codec::decode_body: No Conten-Length header, process all remaining bytes"); + loggers::get_instance().warning("http_codec::decode_body: No Content-Length header, process all remaining bytes"); body = s; } loggers::get_instance().log_msg("http_codec::decode_body: Aligned body=", body); diff --git a/ccsrc/Protocols/Json/json_codec.cc b/ccsrc/Protocols/Json/json_codec.cc index 2b16bfa..e8c0e4f 100644 --- a/ccsrc/Protocols/Json/json_codec.cc +++ b/ccsrc/Protocols/Json/json_codec.cc @@ -55,10 +55,15 @@ int json_codec::decode (const OCTETSTRING& p_data, LibItsHttp__JsonMessageBodyTy TTCN_Buffer decoding_buffer(OCTETSTRING(str.length(), (const unsigned char*)str.c_str())); if (it->second.find("\"userList\"") != std::string::npos) { // Be carefull to the order - // TODO To be refined, find("\"userList\"") is not optimal - LocationAPI__TypesAndValues::UserList user_list; - user_list.decode(LocationAPI__TypesAndValues::UserList_descr_, decoding_buffer, TTCN_EncDec::CT_JSON); - msg.userList() = user_list; + // TODO To be refined, find("\"userList\"") is not optimal + LocationAPI__TypesAndValues::UserList user_list; + user_list.decode(LocationAPI__TypesAndValues::UserList_descr_, decoding_buffer, TTCN_EncDec::CT_JSON); + msg.userList() = user_list; + } else if (it->second.find("\"accessPointList\"") != std::string::npos) { // Be carefull to the order + // TODO To be refined, find("\"accessPointList\"") is not optimal + LocationAPI__TypesAndValues::AccessPointList access_point_list; + access_point_list.decode(LocationAPI__TypesAndValues::AccessPointList_descr_, decoding_buffer, TTCN_EncDec::CT_JSON); + msg.accessPointList() = access_point_list; } else if (it->second.find("\"userInfo\"") != std::string::npos) { LocationAPI__TypesAndValues::UserInfo user_info; user_info.decode(LocationAPI__TypesAndValues::UserInfo_descr_, decoding_buffer, TTCN_EncDec::CT_JSON); diff --git a/etc/AtsMec/AtsMec_LocationAPI.cfg b/etc/AtsMec/AtsMec_LocationAPI.cfg index 3e79305..8c7d9a3 100644 --- a/etc/AtsMec/AtsMec_LocationAPI.cfg +++ b/etc/AtsMec/AtsMec_LocationAPI.cfg @@ -7,7 +7,8 @@ LibCommon_Time.PX_TAC := 30.0 LibCommon_Sync.PX_TSYNC_TIME_LIMIT := 30.0; LibCommon_Sync.PX_TSHUT_DOWN_TIME_LIMIT := 30.0; -LibItsHttp_Pics.PICS_HEADER_HOST := "192.168.1.140" +#LibItsHttp_Pics.PICS_HEADER_HOST := "192.168.1.140" +LibItsHttp_Pics.PICS_HEADER_HOST := "172.16.3.114" #LibItsHttp_Pics.PICS_HEADER_HOST := "172.28.4.87" LibItsHttp_Pics.PICS_HEADER_CONTENT_TYPE := "application/json" @@ -31,7 +32,7 @@ LogEventTypes:= Yes [TESTPORT_PARAMETERS] # In this section you can specify parameters that are passed to Test Ports. -system.httpPort.params := "HTTP(codecs=json:json_codec)/TCP(debug=1,server=192.168.1.140,port=8081,use_ssl=0)" +system.httpPort.params := "HTTP(codecs=json:json_codec)/TCP(debug=1,server=172.16.3.114,port=8081,use_ssl=0)" [DEFINE] # In this section you can create macro definitions, diff --git a/ttcn/AtsMec/AtsMec_LocationAPI_TestCases.ttcn b/ttcn/AtsMec/AtsMec_LocationAPI_TestCases.ttcn index 6340a23..d157c5a 100644 --- a/ttcn/AtsMec/AtsMec_LocationAPI_TestCases.ttcn +++ b/ttcn/AtsMec/AtsMec_LocationAPI_TestCases.ttcn @@ -100,4 +100,4 @@ module AtsMec_LocationAPI_TestCases { } // End of group me_app_role -} // End of module AtsMec_TestCases +} // End of module AtsMec_LocationAPI_TestCases diff --git a/ttcn/AtsMec/AtsMec_RadioNodeLocationAPI_TestCases.ttcn b/ttcn/AtsMec/AtsMec_RadioNodeLocationAPI_TestCases.ttcn new file mode 100644 index 0000000..68a14aa --- /dev/null +++ b/ttcn/AtsMec/AtsMec_RadioNodeLocationAPI_TestCases.ttcn @@ -0,0 +1,99 @@ +/** + * @author ETSI / STF569 + * @version $URL:$ + * $ID:$ + * @desc This module provides the MEC test cases. + * @copyright ETSI Copyright Notification + * No part may be reproduced except as authorized by written permission. + * The copyright and the foregoing restriction extend to reproduction in all media. + * All rights reserved. + * @see ETSI GS MEC 003, Draft ETSI GS MEC 013 V2.0.3 (2018-10) + */ +module AtsMec_RadioNodeLocationAPI_TestCases { + + // Libcommon + import from LibCommon_Time all; + import from LibCommon_VerdictControl all; + import from LibCommon_Sync all; + + // LibHttp + import from LibItsHttp_TypesAndValues all; + import from LibItsHttp_Functions all; + import from LibItsHttp_Templates all; + import from LibItsHttp_JsonTemplates all; + import from LibItsHttp_TestSystem all; + + // LibMec_LocationAPI + import from LocationAPI_TypesAndValues all; + import from LocationAPI_Templates all; + import from LocationAPI_Pics all; + import from LocationAPI_Pixits all; + + // LibMec + import from LibMec_Functions all; + import from LibMec_Pics all; + import from LibMec_Pixits all; + + group me_app_role { + + /** + * @desc Check that the IUT responds with the list of radio nodes currently associated with the MEC host and the location of each radio node when queried by a MEC Application + * @see https://forge.etsi.org/gitlab/mec/MEC-tests/blob/master/Test%20Purposes/Plat/Mp1/RadioNode/PlatRadioNodeLocation.tplan2 + * @see https://forge.etsi.org/gitlab/mec/gs013-location-api/blob/master/LocationAPI.yaml#/definitions/AccessPointList + */ + testcase TC_MEC_PLAT_MP1_RLOC_BV_001() runs on HttpComponent system HttpTestAdapter { + // Local variables + var HeaderLines v_headers; + var HttpMessage v_response; + + // Test control + if (not(PICS_PLAT_IUT) or not(PICS_LOCATION_API_SUPPORTED)) { + log("*** " & testcasename() & ": PICS_PLAT_IUT and PICS_LOCATION_API_SUPPORTED required for executing the TC ***"); + setverdict(inconc); + stop; + } + + // Test component configuration + f_cf_01_http_up(); + + // Test adapter configuration + + // Preamble + f_init_default_headers_list(-, -, v_headers); + httpPort.send( + m_http_request( + m_http_request_get( + PICS_ME_APP_Q_ZONE_ID_URI & "/" & oct2char(unichar2oct(PX_ZONE_ID, "UTF-8")) & "/accessPoints", + v_headers + ) + ) + ); + f_selfOrClientSyncAndVerdict(c_prDone, e_success); + + // Test Body + tc_ac.start; + alt { + [] httpPort.receive( + mw_http_response( + mw_http_response_ok( + mw_http_message_body_json( + mw_body_json_access_point_list( + mw_access_point_list( + PX_ZONE_ID + )))))) -> value v_response { + log("*** " & testcasename() & ": PASS: IUT successfully responds with an AccessPoint list containing a ZoneId ***"); + f_selfOrClientSyncAndVerdict(c_tbDone, e_success); + } + [] tc_ac.timeout { + log("*** " & testcasename() & ": INCONC: Expected message not received ***"); + f_selfOrClientSyncAndVerdict(c_tbDone, e_timeout); + } + } // End of 'alt' statement + + // Postamble + f_cf_01_http_down(); + } // End of testcase TC_MEC_PLAT_MP1_RLOC_BV_001 + + } // End of group me_app_role + +} // End of module AtsMec_RadioNodeLocationAPI_TestCases diff --git a/ttcn/AtsMec/AtsMec_TestControl.ttcn b/ttcn/AtsMec/AtsMec_TestControl.ttcn index 88d4603..9d52184 100644 --- a/ttcn/AtsMec/AtsMec_TestControl.ttcn +++ b/ttcn/AtsMec/AtsMec_TestControl.ttcn @@ -14,6 +14,7 @@ module AtsMec_TestControl { import from AtsMec_LocationAPI_TestCases all; import from AtsMec_UEidentityAPI_TestCases all; import from AtsMec_UEinformation_TestCases all; + import from AtsMec_RadioNodeLocationAPI_TestCases all; import from AtsMec_RnisAPI_TestCases all; control { @@ -30,6 +31,9 @@ module AtsMec_TestControl { if (PICS_LOCATION_API_SUPPORTED) { execute(TC_MEC_PLAT_MP1_INF_BV_001()); } + if (PICS_LOCATION_API_SUPPORTED) { + execute(TC_MEC_PLAT_MP1_RLOC_BV_001()); + } } if (PICS_RNIS_IUT) { diff --git a/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_Pixits.ttcn b/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_Pixits.ttcn index 2d324df..54f15a2 100644 --- a/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_Pixits.ttcn +++ b/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_Pixits.ttcn @@ -6,17 +6,17 @@ module LocationAPI_Pixits { // LibMec/LocationAPI import from LocationAPI_TypesAndValues all; - modulepar universal charstring PX_USER := "acr:192.0.0.1"; + modulepar Address PX_USER := "acr:192.0.0.1"; - modulepar universal charstring PX_ACCESS_POINT_ID := "001010000000000000000000000000001"; + modulepar AccessPointId PX_ACCESS_POINT_ID := "001010000000000000000000000000001"; - modulepar universal charstring PX_ZONE_ID := "zone01"; + modulepar ZoneId PX_ZONE_ID := "zone01"; - modulepar universal charstring PX_RESOURCE_URL := "http://example.com/exampleAPI/location/v2/users?address:acr:192.0.0.1"; + modulepar ResourceURL PX_RESOURCE_URL := "http://example.com/exampleAPI/location/v2/users?address:acr:192.0.0.1"; modulepar TimeStamp PX_TIME_STAMP := { seconds := 1483231138, nanoSeconds := 0 }; - modulepar UInt32 PX_NB_ACCESS_POINTS := 3; + modulepar NumberOfAccessPoints PX_NB_ACCESS_POINTS := 3; modulepar UInt32 PX_NB_UNSERVICABLEL_ACCESS_POINTS := 1; diff --git a/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_Templates.ttcn b/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_Templates.ttcn index 99e87ee..956c9b6 100644 --- a/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_Templates.ttcn +++ b/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_Templates.ttcn @@ -8,10 +8,10 @@ module LocationAPI_Templates { import from LocationAPI_Pixits all; template (value) UserInfo m_user_info( - in universal charstring p_address := PX_USER, - in universal charstring p_access_point_id := PX_ACCESS_POINT_ID, - in universal charstring p_zone_id := PX_ZONE_ID, - in universal charstring p_resource_url := PX_RESOURCE_URL + in Address p_address := PX_USER, + in AccessPointId p_access_point_id := PX_ACCESS_POINT_ID, + in ZoneId p_zone_id := PX_ZONE_ID, + in ResourceURL p_resource_url := PX_RESOURCE_URL ) := { address_ := p_address, accessPointId := p_access_point_id, @@ -23,9 +23,9 @@ module LocationAPI_Templates { } // End of template m_user_info template (present) UserInfo mw_user_info( - template (present) universal charstring p_address := ?, - template (present) universal charstring p_access_point_id := ?, - template (present) universal charstring p_zone_id := ?, + template (present) Address p_address := ?, + template (present) AccessPointId p_access_point_id := ?, + template (present) ZoneId p_zone_id := ?, template (present) universal charstring p_resource_url := ? ) := { address_ := p_address, @@ -39,7 +39,7 @@ module LocationAPI_Templates { template (omit) UserList m_user_list( in template (omit) UserInfos p_userInfo := omit, - in universal charstring p_resource_url + in ResourceURL p_resource_url ) := { user := p_userInfo, resourceURL := p_resource_url @@ -47,7 +47,7 @@ module LocationAPI_Templates { template UserList mw_user_list( template UserInfos p_userInfo := *, - template (present) universal charstring p_resource_url := ? + template (present) ResourceURL p_resource_url := ? ) := { user := p_userInfo, resourceURL := p_resource_url @@ -56,7 +56,7 @@ module LocationAPI_Templates { template (value) LocationInfo m_location_info( in float p_latitude, in float p_longitude, - in integer p_accuracy + in UInt32 p_accuracy ) := { latitude := p_latitude, longitude := p_longitude, @@ -67,7 +67,7 @@ module LocationAPI_Templates { template (present) LocationInfo mw_location_info( template (present) float p_latitude := ?, template (present) float p_longitude := ?, - template (present) integer p_accuracy := ? + template (present) UInt32 p_accuracy := ? ) := { latitude := p_latitude, longitude := p_longitude, @@ -76,11 +76,11 @@ module LocationAPI_Templates { } // End of template mw_location_info template (value) ZoneInfo m_zone_info( - in universal charstring p_zone_id := PX_ZONE_ID, - in UInt32 p_number_of_access_points := PX_NB_ACCESS_POINTS, - in UInt32 p_number_of_unservicable_access_points := PX_NB_UNSERVICABLEL_ACCESS_POINTS, - in UInt32 p_number_of_users := PX_NB_USERS, - in universal charstring p_resource_url := PX_RESOURCE_URL + in ZoneId p_zone_id := PX_ZONE_ID, + in NumberOfAccessPoints p_number_of_access_points := PX_NB_ACCESS_POINTS, + in NumberOfUnserviceableAccessPoints p_number_of_unservicable_access_points := PX_NB_UNSERVICABLEL_ACCESS_POINTS, + in NumberOfUsers p_number_of_users := PX_NB_USERS, + in ResourceURL p_resource_url := PX_RESOURCE_URL ) := { zoneId := p_zone_id, numberOfAccessPoints := p_number_of_access_points, @@ -90,11 +90,11 @@ module LocationAPI_Templates { } // End of template m_zone_info template (present) ZoneInfo mw_zone_info( - template (present) universal charstring p_zone_id := ?, - template (present) UInt32 p_number_of_access_points := ?, - template (present) UInt32 p_number_of_unservicable_access_points := ?, - template (present) UInt32 p_number_of_users := ?, - template (present) universal charstring p_resource_url := ? + template (present) ZoneId p_zone_id := ?, + template (present) NumberOfAccessPoints p_number_of_access_points := ?, + template (present) NumberOfUnserviceableAccessPoints p_number_of_unservicable_access_points := ?, + template (present) NumberOfUsers p_number_of_users := ?, + template (present) ResourceURL p_resource_url := ? ) := { zoneId := p_zone_id, numberOfAccessPoints := p_number_of_access_points, @@ -103,4 +103,24 @@ module LocationAPI_Templates { resourceURL := p_resource_url } // End of template mw_zone_info + template (omit) AccessPointList m_access_point_list( + in ZoneId p_zoneId := PX_ZONE_ID, + in ResourceURL p_resourceURL, + in template (omit) AccessPointInfos p_accessPoint := omit + ) := { + zoneId := p_zoneId, + accessPoint := p_accessPoint, + resourceURL := p_resourceURL + } // End of template m_access_point_list + + template AccessPointList mw_access_point_list( + template (present) ZoneId p_zoneId := ?, + template (present) ResourceURL p_resourceURL := ?, + template AccessPointInfos p_accessPoint := * + ) := { + zoneId := p_zoneId, + accessPoint := p_accessPoint, + resourceURL := p_resourceURL + } // End of template mw_access_point_list + } // End of module LocationAPI_Templates diff --git a/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_TypesAndValues.ttcn b/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_TypesAndValues.ttcn index 54ba279..d1c81b8 100644 --- a/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_TypesAndValues.ttcn +++ b/ttcn/LibMec/LocationAPI/ttcn/LocationAPI_TypesAndValues.ttcn @@ -35,7 +35,7 @@ module LocationAPI_TypesAndValues { float latitude, float longitude, float altitude optional, - integer accuracy + UInt32 accuracy } // End of type LocationInfo /** @@ -80,12 +80,14 @@ module LocationAPI_TypesAndValues { */ type record AccessPointInfo { AccessPointId accessPointId, + LocationInfo locationInfo optional, ConnectionType connectionType, OperationStatus operationStatus, - UInt32 numberOfUsers, + NumberOfUsers numberOfUsers, + InterestRealm interestRealm, ResourceURL resourceURL } - + type record of AccessPointInfo AccessPointInfos; /** * @desc A type containing list of access points. @@ -95,7 +97,7 @@ module LocationAPI_TypesAndValues { */ type record AccessPointList { ZoneId zoneId, - AccessPointInfo accessPoint optional, + AccessPointInfos accessPoint optional, ResourceURL resourceURL } @@ -370,12 +372,6 @@ REST_NetAPI_Common]. NumberOfUsersAPThreshold numberOfUsersAPThreshold optional, OperationStatus operationStatus optional } - - type record of AccessPointInfo AccessPointInfos - type record AccessPointInfoList { - AccessPointInfos AccessPointInfo optional, - ResourceURL resourceURL - } } with { encode "JSON" diff --git a/ttcn/TestCodec/TestCodec_External.ttcn b/ttcn/TestCodec/TestCodec_External.ttcn index fb0a34f..7dfd0c1 100644 --- a/ttcn/TestCodec/TestCodec_External.ttcn +++ b/ttcn/TestCodec/TestCodec_External.ttcn @@ -38,7 +38,11 @@ module TestCodec_External { } // End of testcase tc_encode_LocationInfo testcase tc_encode_UserInfo() runs on TCType system TCType { - var UserInfo v_location_info := valueof(m_user_info("acr:10.0.0.1", "001010000000000000000000000000001", "zone01", "http://example.com/exampleAPI/location/v2/users?address=acr%3A10.0.0.1", TimeStamp:{ 1483231138, 0 })); + var UserInfo v_location_info := valueof(m_user_info( + "acr:10.0.0.1", + "001010000000000000000000000000001", + "zone01", + "http://example.com/exampleAPI/location/v2/users?address=acr%3A10.0.0.1")); var UserInfo v_location_info_result; var universal charstring v_expected_result := "{\"address\":\"acr:10.0.0.1\",\"accessPointId\":\"001010000000000000000000000000001\",\"zoneId\":\"zone01\",\"resourceURL\":\"http://example.com/exampleAPI/location/v2/users?address=acr%3A10.0.0.1\",\"timeStamp\":{\"seconds\":1483231138,\"nanoSeconds\":0}}"; var universal charstring v_enc_msg; diff --git a/ttcn/patch_lib_http/LibItsHttp_JsonMessageBodyTypes.ttcn b/ttcn/patch_lib_http/LibItsHttp_JsonMessageBodyTypes.ttcn index d75cc1a..a880443 100644 --- a/ttcn/patch_lib_http/LibItsHttp_JsonMessageBodyTypes.ttcn +++ b/ttcn/patch_lib_http/LibItsHttp_JsonMessageBodyTypes.ttcn @@ -18,6 +18,7 @@ module LibItsHttp_JsonMessageBodyTypes { UserInfo userInfo, UserList userList, ZoneInfo zoneInfo, + AccessPointList accessPointList, UEidentityAPI_TypesAndValues.ProblemDetails problemDetails_ue_identity, UeIdentityTagInfo ueIdentityTagInfo, CellChangeSubscription cellChangeSubscription, diff --git a/ttcn/patch_lib_http/LibItsHttp_JsonTemplates.ttcn b/ttcn/patch_lib_http/LibItsHttp_JsonTemplates.ttcn index 41fd994..b116380 100644 --- a/ttcn/patch_lib_http/LibItsHttp_JsonTemplates.ttcn +++ b/ttcn/patch_lib_http/LibItsHttp_JsonTemplates.ttcn @@ -72,10 +72,22 @@ module LibItsHttp_JsonTemplates { template (present) JsonBody mw_body_json_zone_info( template (present) ZoneInfo p_zone_info := ? - ) := { + ) := { zoneInfo := p_zone_info } // End of template mw_body_json_zone_info + template (value) JsonBody m_body_json_access_point_list( + in template (value) AccessPointList p_access_point_list + ) := { + accessPointList := p_access_point_list + } // End of template m_body_json_access_point_list + + template (present) JsonBody mw_body_json_access_point_list( + template (present) AccessPointList p_access_point_list := ? + ) := { + accessPointList := p_access_point_list + } // End of template mw_body_json_access_point_list + } // End of group locarion_api group ue_identity_api { -- GitLab