1: %%============================================================================== 2: %% Copyright 2020 Erlang Solutions Ltd. 3: %% 4: %% Licensed under the Apache License, Version 2.0 (the "License"); 5: %% you may not use this file except in compliance with the License. 6: %% You may obtain a copy of the License at 7: %% 8: %% http://www.apache.org/licenses/LICENSE-2.0 9: %% 10: %% Unless required by applicable law or agreed to in writing, software 11: %% distributed under the License is distributed on an "AS IS" BASIS, 12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13: %% See the License for the specific language governing permissions and 14: %% limitations under the License. 15: %%============================================================================== 16: -module(extdisco_SUITE). 17: 18: -include_lib("common_test/include/ct.hrl"). 19: -include_lib("exml/include/exml.hrl"). 20: -include_lib("eunit/include/eunit.hrl"). 21: 22: -import(distributed_helper, [mim/0, 23: rpc/4]). 24: 25: -import(domain_helper, [domain/0]). 26: 27: -define(NS_EXTDISCO, <<"urn:xmpp:extdisco:2">>). 28: 29: -compile([export_all, nowarn_export_all]). 30: 31: all() -> 32: [{group, extdisco_not_configured}, 33: {group, extdisco_configured}, 34: {group, multiple_extdisco_configured}, 35: {group, extdisco_required_elements_configured}]. 36: 37: groups() -> 38: G = [{extdisco_not_configured, [sequence], extdisco_not_configured_tests()}, 39: {extdisco_configured, [sequence], extdisco_configured_tests()}, 40: {multiple_extdisco_configured, [sequence], multiple_extdisco_configured_tests()}, 41: {extdisco_required_elements_configured, [sequence], extdisco_required_elements_configured_tests()}], 42: ct_helper:repeat_all_until_all_ok(G). 43: 44: extdisco_not_configured_tests() -> 45: [external_services_discovery_not_supported, 46: no_external_services_configured_no_services_returned]. 47: 48: extdisco_configured_tests() -> 49: tests(). 50: 51: multiple_extdisco_configured_tests() -> 52: tests(). 53: 54: tests() -> 55: [external_services_discovery_supported, 56: external_services_configured_all_returned, 57: external_services_configured_only_matching_by_type_returned, 58: external_services_configured_no_matching_services_no_returned, 59: external_services_configured_credentials_returned, 60: external_services_configured_no_matching_credentials_no_returned, 61: external_services_configured_no_matching_credentials_type_no_returned, 62: external_services_configured_incorrect_request_no_returned]. 63: 64: extdisco_required_elements_configured_tests() -> 65: [external_service_required_elements_configured]. 66: 67: init_per_suite(Config) -> 68: NewConfig = dynamic_modules:save_modules(domain(), Config), 69: escalus:init_per_suite(NewConfig). 70: 71: init_per_group(extdisco_configured, Config) -> 72: ExternalServices = [stun_service()], 73: set_external_services(ExternalServices, Config); 74: init_per_group(multiple_extdisco_configured, Config) -> 75: ExternalServices = [stun_service(), stun_service(), turn_service()], 76: set_external_services(ExternalServices, Config); 77: init_per_group(extdisco_required_elements_configured, Config) -> 78: ExternalServices = [[{type, ftp},{host, "3.3.3.3"}]], 79: set_external_services(ExternalServices, Config); 80: init_per_group(_GroupName, Config) -> 81: Config. 82: 83: init_per_testcase(external_services_discovery_not_supported = Name, Config) -> 84: NewConfig = remove_external_services(Config), 85: escalus:init_per_testcase(Name, NewConfig); 86: init_per_testcase(no_external_services_configured_no_services_returned = Name, Config) -> 87: ExternalServices = [], 88: NewConfig = set_external_services(ExternalServices, Config), 89: escalus:init_per_testcase(Name, NewConfig); 90: init_per_testcase(Name, Config) -> 91: escalus:init_per_testcase(Name, Config). 92: 93: end_per_testcase(Name, Config) when 94: Name == external_services_discovery_not_supported; 95: Name == no_external_services_configured_no_services_returned -> 96: dynamic_modules:restore_modules(Config), 97: escalus:end_per_testcase(Name, Config); 98: end_per_testcase(Name, Config) -> 99: escalus:end_per_testcase(Name, Config). 100: 101: end_per_group(_GroupName, Config) -> 102: dynamic_modules:restore_modules(Config), 103: Config. 104: 105: end_per_suite(Config) -> 106: escalus_fresh:clean(), 107: escalus:end_per_suite(Config). 108: 109: %%-------------------------------------------------------------------- 110: %% TEST CASES 111: %%-------------------------------------------------------------------- 112: 113: external_services_discovery_not_supported(Config) -> 114: % Given external service discovery is not configured 115: Test = fun(Alice) -> 116: 117: % When requesting for disco_info 118: IqGet = escalus_stanza:disco_info(domain()), 119: escalus_client:send(Alice, IqGet), 120: 121: % Then extdisco feature is not listed as supported feature 122: Result = escalus_client:wait_for_stanza(Alice), 123: escalus:assert(is_iq_result, [IqGet], Result), 124: escalus:assert(fun(Stanza) -> 125: not escalus_pred:has_feature(?NS_EXTDISCO, Stanza) 126: end, Result) 127: end, 128: escalus:fresh_story(Config, [{alice, 1}], Test). 129: 130: no_external_services_configured_no_services_returned(Config) -> 131: % Given external service discovery is configured with empty list 132: Test = fun(Alice) -> 133: 134: % When requesting for external services 135: Iq = request_external_services(domain()), 136: escalus_client:send(Alice, Iq), 137: 138: % Then services but no service element is in the iq result, 139: % which means that empty services element got returned 140: Result = escalus_client:wait_for_stanza(Alice), 141: escalus:assert(is_iq_result, [Iq], Result), 142: ?assertEqual(true, has_subelement_with_ns(Result, <<"services">>, ?NS_EXTDISCO)), 143: ?assertEqual([], get_service_element(Result)) 144: end, 145: escalus:fresh_story(Config, [{alice, 1}], Test). 146: 147: external_services_discovery_supported(Config) -> 148: % Given external service discovery is configured 149: Test = fun(Alice) -> 150: 151: % When requesting for disco_info 152: IqGet = escalus_stanza:disco_info(domain()), 153: escalus_client:send(Alice, IqGet), 154: 155: % Then extdisco feature is listed as supported feature 156: Result = escalus_client:wait_for_stanza(Alice), 157: escalus:assert(is_iq_result, [IqGet], Result), 158: escalus:assert(has_feature, [?NS_EXTDISCO], Result) 159: end, 160: escalus:fresh_story(Config, [{alice, 1}], Test). 161: 162: external_services_configured_all_returned(Config) -> 163: % Given external service discovery is configured 164: Test = fun(Alice) -> 165: 166: % When requesting for external services 167: Iq = request_external_services(domain()), 168: escalus_client:send(Alice, Iq), 169: 170: % Then list of external services with containing all 171: % supported_elements() is returned 172: Result = escalus_client:wait_for_stanza(Alice), 173: escalus:assert(is_iq_result, [Iq], Result), 174: ?assertEqual(true, has_subelement_with_ns(Result, <<"services">>, ?NS_EXTDISCO)), 175: [all_services_are_returned(Service) || Service <- get_service_element(Result)] 176: end, 177: escalus:fresh_story(Config, [{alice, 1}], Test). 178: 179: external_services_configured_only_matching_by_type_returned(Config) -> 180: % Given external service discovery is configured 181: Test = fun(Alice) -> 182: 183: % When requesting for external service of specified type 184: Type = <<"stun">>, 185: Iq = request_external_services_with_type(domain(), Type), 186: escalus_client:send(Alice, Iq), 187: 188: % Then the list of external services of the specified type is returned 189: Result = escalus_client:wait_for_stanza(Alice), 190: escalus:assert(is_iq_result, [Iq], Result), 191: ?assertEqual(true, has_subelement_with_ns(Result, <<"services">>, ?NS_EXTDISCO)), 192: [all_services_are_returned(Service, Type) || Service <- get_service_element(Result)] 193: end, 194: escalus:fresh_story(Config, [{alice, 1}], Test). 195: 196: external_services_configured_no_matching_services_no_returned(Config) -> 197: % Given external service discovery is configured 198: Test = fun(Alice) -> 199: 200: % When requesting for external service of unknown or unconfigured type 201: Type = <<"unknown_service">>, 202: Iq = request_external_services_with_type(domain(), Type), 203: escalus_client:send(Alice, Iq), 204: 205: % Then the iq_errror is returned 206: Result = escalus_client:wait_for_stanza(Alice), 207: escalus:assert(is_iq_error, [Iq], Result) 208: end, 209: escalus:fresh_story(Config, [{alice, 1}], Test). 210: 211: external_services_configured_credentials_returned(Config) -> 212: % Given external service discovery is configured with credentials 213: Test = fun(Alice) -> 214: 215: % When requesting for credentials of external service of given type 216: % and specified host 217: Type = <<"stun">>, 218: Host = <<"1.1.1.1">>, 219: Iq = request_external_services_credentials(domain(), Type, Host), 220: escalus_client:send(Alice, Iq), 221: 222: % Then the list of external services of the specified type and host 223: % is returned together with STUN/TURN login credentials 224: Result = escalus_client:wait_for_stanza(Alice), 225: escalus:assert(is_iq_result, [Iq], Result), 226: ?assertEqual(true, has_subelement_with_ns(Result, <<"credentials">>, ?NS_EXTDISCO)), 227: Services = get_service_element(Result), 228: ?assertNotEqual([], Services), 229: [all_services_are_returned(Service, Type) || Service <- Services] 230: end, 231: escalus:fresh_story(Config, [{alice, 1}], Test). 232: 233: external_services_configured_no_matching_credentials_no_returned(Config) -> 234: % Given external service discovery is configured with credentials 235: Test = fun(Alice) -> 236: 237: % When requesting for credentials of external service of unknown type 238: % and unknown host 239: Type = <<"unknown_service">>, 240: Host = <<"unknown_host">>, 241: Iq = request_external_services_credentials(domain(), Type, Host), 242: escalus_client:send(Alice, Iq), 243: 244: % Then iq_error is retured 245: Result = escalus_client:wait_for_stanza(Alice), 246: escalus:assert(is_iq_error, [Iq], Result) 247: end, 248: escalus:fresh_story(Config, [{alice, 1}], Test). 249: 250: external_services_configured_no_matching_credentials_type_no_returned(Config) -> 251: % Given external service discovery is configured with credentials 252: Test = fun(Alice) -> 253: 254: % When requesting for credentials of external service without defining 255: % the service type 256: Host = <<"stun1">>, 257: Iq = request_external_services_credentials_host_only(domain(), Host), 258: escalus_client:send(Alice, Iq), 259: 260: % Then iq_error is retured 261: Result = escalus_client:wait_for_stanza(Alice), 262: escalus:assert(is_iq_error, [Iq], Result) 263: end, 264: escalus:fresh_story(Config, [{alice, 1}], Test). 265: 266: external_services_configured_incorrect_request_no_returned(Config) -> 267: % Given external service discovery is configured 268: Test = fun(Alice) -> 269: 270: % When sending request with incorrect elements 271: Iq = request_external_services_incorrect(domain()), 272: escalus_client:send(Alice, Iq), 273: 274: % Then iq_error is returned 275: Result = escalus_client:wait_for_stanza(Alice), 276: escalus:assert(is_iq_error, [Iq], Result) 277: end, 278: escalus:fresh_story(Config, [{alice, 1}], Test). 279: 280: external_service_required_elements_configured(Config) -> 281: % Given external service discovery is configured only with required elements 282: Test = fun(Alice) -> 283: 284: % When requesting for external services 285: Iq = request_external_services(domain()), 286: escalus_client:send(Alice, Iq), 287: 288: % Then list of external services with containing all 289: % required_elements() is returned 290: Result = escalus_client:wait_for_stanza(Alice), 291: escalus:assert(is_iq_result, [Iq], Result), 292: ?assertEqual(true, has_subelement_with_ns(Result, <<"services">>, ?NS_EXTDISCO)), 293: [required_services_are_returned(Service) || Service <- get_service_element(Result)] 294: end, 295: escalus:fresh_story(Config, [{alice, 1}], Test). 296: 297: %%----------------------------------------------------------------- 298: %% Helpers 299: %%----------------------------------------------------------------- 300: 301: stun_service() -> 302: [{type, stun}, 303: {host, "1.1.1.1"}, 304: {port, 3478}, 305: {transport, "udp"}, 306: {username, "username"}, 307: {password, "secret"}]. 308: 309: turn_service() -> 310: [{type, turn}, 311: {host, "2.2.2.2"}, 312: {port, 3478}, 313: {transport, "tcp"}, 314: {username, "username"}, 315: {password, "secret"}]. 316: 317: set_external_services(Opts, Config) -> 318: Module = [{mod_extdisco, Opts}], 319: ok = dynamic_modules:ensure_modules(domain(), Module), 320: Config. 321: 322: remove_external_services(Config) -> 323: dynamic_modules:ensure_stopped(domain(), [mod_extdisco]), 324: Config. 325: 326: request_external_services(To) -> 327: escalus_stanza:iq(To, <<"get">>, 328: [#xmlel{name = <<"services">>, 329: attrs = [{<<"xmlns">>, <<"urn:xmpp:extdisco:2">>}]}]). 330: 331: request_external_services_with_type(To, Type) -> 332: escalus_stanza:iq(To, <<"get">>, 333: [#xmlel{name = <<"services">>, 334: attrs = [{<<"xmlns">>, <<"urn:xmpp:extdisco:2">>}, 335: {<<"type">>, Type}]}]). 336: 337: request_external_services_credentials(To, Type, Host) -> 338: escalus_stanza:iq(To, <<"get">>, 339: [#xmlel{name = <<"credentials">>, 340: attrs = [{<<"xmlns">>, <<"urn:xmpp:extdisco:2">>}], 341: children = [#xmlel{name = <<"service">>, 342: attrs = [{<<"host">>, Host}, 343: {<<"type">>, Type}]}]}]). 344: 345: request_external_services_credentials_host_only(To, Host) -> 346: escalus_stanza:iq(To, <<"get">>, 347: [#xmlel{name = <<"credentials">>, 348: attrs = [{<<"xmlns">>, <<"urn:xmpp:extdisco:2">>}], 349: children = [#xmlel{name = <<"service">>, 350: attrs = [{<<"host">>, Host}]}]}]). 351: 352: request_external_services_incorrect(To) -> 353: escalus_stanza:iq(To, <<"get">>, 354: [#xmlel{name = <<"incorrect">>, 355: attrs = [{<<"xmlns">>, <<"urn:xmpp:extdisco:2">>}]}]). 356: 357: get_service_element(Result) -> 358: Services = exml_query:subelement_with_ns(Result, ?NS_EXTDISCO), 359: exml_query:subelements(Services, <<"service">>). 360: 361: required_elements() -> 362: [<<"host">>, <<"type">>]. 363: 364: supported_elements() -> 365: required_elements() ++ [<<"port">>, <<"username">>, <<"password">>]. 366: 367: all_services_are_returned(Service) -> 368: [?assertEqual(true, has_subelement(Service, E)) || E <- supported_elements()]. 369: 370: required_services_are_returned(Service) -> 371: [?assertEqual(true, has_subelement(Service, E)) || E <- required_elements()]. 372: 373: all_services_are_returned(Service, Type) -> 374: ?assertEqual(true, has_attr_with_value(Service, <<"type">>, Type)), 375: all_services_are_returned(Service). 376: 377: no_services_are_returned(Service) -> 378: [?assertEqual(false, has_subelement(Service, E)) || E <- supported_elements()]. 379: 380: has_subelement(Stanza, Element) -> 381: undefined =/= exml_query:attr(Stanza, Element). 382: 383: has_attr_with_value(Stanza, Element, Value) -> 384: Value == exml_query:attr(Stanza, Element). 385: 386: has_subelement_with_ns(Stanza, Element, NS) -> 387: [] =/= exml_query:subelements_with_name_and_ns(Stanza, Element, NS).