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