1: %%============================================================================== 2: %% Copyright 2014 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: 17: -module(component_SUITE). 18: -compile([export_all, nowarn_export_all]). 19: 20: -include_lib("escalus/include/escalus.hrl"). 21: -include_lib("common_test/include/ct.hrl"). 22: -include_lib("exml/include/exml.hrl"). 23: -include_lib("exml/include/exml_stream.hrl"). 24: -include_lib("eunit/include/eunit.hrl"). 25: 26: -import(distributed_helper, [add_node_to_cluster/1, 27: mim/0, 28: remove_node_from_cluster/1, 29: require_rpc_nodes/1, 30: start_node/2, 31: stop_node/2]). 32: 33: -import(component_helper, [connect_component/1, 34: connect_component/2, 35: disconnect_component/2, 36: disconnect_components/2, 37: connect_component_subdomain/1, 38: spec/2, 39: common/1, 40: common/2, 41: name/1]). 42: 43: %%-------------------------------------------------------------------- 44: %% Suite configuration 45: %%-------------------------------------------------------------------- 46: 47: all() -> 48: [ 49: {group, xep0114_tcp}, 50: {group, xep0114_ws}, 51: {group, subdomain}, 52: {group, hidden_components}, 53: {group, distributed} 54: ]. 55: 56: groups() -> 57: G = [{xep0114_tcp, [], xep0114_tests()}, 58: {xep0114_ws, [], xep0114_tests()}, 59: {subdomain, [], [register_subdomain]}, 60: {hidden_components, [], [disco_with_hidden_component]}, 61: {distributed, [], [register_in_cluster, 62: register_same_on_both 63: %clear_on_node_down TODO: Breaks cover 64: ]}], 65: ct_helper:repeat_all_until_all_ok(G). 66: 67: suite() -> 68: require_rpc_nodes([mim]) ++ escalus:suite(). 69: 70: xep0114_tests() -> 71: [register_one_component, 72: dirty_disconnect, 73: register_two_components, 74: try_registering_with_wrong_password, 75: try_registering_component_twice, 76: try_registering_existing_host, 77: disco_components, 78: kick_old_component_on_conflict 79: ]. 80: 81: %%-------------------------------------------------------------------- 82: %% Init & teardown 83: %%-------------------------------------------------------------------- 84: 85: init_per_suite(Config) -> 86: Config1 = escalus:init_per_suite(Config), 87: ejabberd_node_utils:init(Config1). 88: 89: end_per_suite(Config) -> 90: escalus:end_per_suite(Config). 91: 92: init_per_group(xep0114_tcp, Config) -> 93: Config1 = get_components(common(Config), Config), 94: escalus:create_users(Config1, escalus:get_users([alice, bob])); 95: init_per_group(xep0114_ws, Config) -> 96: WSOpts = [{transport, escalus_ws}, 97: {wspath, <<"/ws-xmpp">>}, 98: {wslegacy, true} | common(Config, ct:get_config({hosts, mim, cowboy_port}))], 99: Config1 = get_components(WSOpts, Config), 100: escalus:create_users(Config1, escalus:get_users([alice, bob])); 101: init_per_group(subdomain, Config) -> 102: Config1 = get_components(common(Config), Config), 103: add_domain(Config1), 104: escalus:create_users(Config1, escalus:get_users([alice, astrid])); 105: init_per_group(hidden_components, Config) -> 106: Config1 = get_components(common(Config), Config), 107: escalus:create_users(Config1, escalus:get_users([alice, bob])); 108: init_per_group(distributed, Config) -> 109: Config1 = get_components(common(Config), Config), 110: Config2 = add_node_to_cluster(Config1), 111: escalus:create_users(Config2, escalus:get_users([alice, clusterguy])); 112: init_per_group(_GroupName, Config) -> 113: escalus:create_users(Config, escalus:get_users([alice, bob])). 114: 115: end_per_group(subdomain, Config) -> 116: escalus:delete_users(Config, escalus:get_users([alice, astrid])), 117: restore_domain(Config); 118: end_per_group(distributed, Config) -> 119: escalus:delete_users(Config, escalus:get_users([alice, clusterguy])), 120: remove_node_from_cluster(Config); 121: end_per_group(_GroupName, Config) -> 122: escalus:delete_users(Config, escalus:get_users([alice, bob])). 123: 124: init_per_testcase(CaseName, Config) -> 125: escalus:init_per_testcase(CaseName, Config). 126: 127: end_per_testcase(CaseName, Config) -> 128: escalus:end_per_testcase(CaseName, Config). 129: 130: 131: %%-------------------------------------------------------------------- 132: %% Tests 133: %%-------------------------------------------------------------------- 134: dirty_disconnect(Config) -> 135: %% Given one connected component, kill the connection and reconnect 136: CompOpts = ?config(component1, Config), 137: {Component, Addr, _} = connect_component(CompOpts), 138: disconnect_component(Component, Addr), 139: {Component1, Addr, _} = connect_component(CompOpts), 140: disconnect_component(Component1, Addr). 141: 142: register_one_component(Config) -> 143: %% Given one connected component 144: CompOpts = ?config(component1, Config), 145: {Component, ComponentAddr, _} = connect_component(CompOpts), 146: verify_component(Config, Component, ComponentAddr), 147: disconnect_component(Component, ComponentAddr). 148: 149: verify_component(Config, Component, ComponentAddr) -> 150: escalus:story(Config, [{alice, 1}], fun(Alice) -> 151: %% When Alice sends a message to the component 152: Msg1 = escalus_stanza:chat_to(ComponentAddr, <<"Hi!">>), 153: escalus:send(Alice, Msg1), 154: %% Then component receives it 155: Reply1 = escalus:wait_for_stanza(Component), 156: escalus:assert(is_chat_message, [<<"Hi!">>], Reply1), 157: 158: %% When component sends a reply 159: Msg2 = escalus_stanza:chat_to(Alice, <<"Oh hi!">>), 160: escalus:send(Component, escalus_stanza:from(Msg2, ComponentAddr)), 161: 162: %% Then Alice receives it 163: Reply2 = escalus:wait_for_stanza(Alice), 164: escalus:assert(is_chat_message, [<<"Oh hi!">>], Reply2), 165: escalus:assert(is_stanza_from, [ComponentAddr], Reply2) 166: end). 167: 168: register_two_components(Config) -> 169: %% Given two connected components 170: CompOpts1 = ?config(component1, Config), 171: CompOpts2 = ?config(component2, Config), 172: {Comp1, CompAddr1, _} = connect_component(CompOpts1), 173: {Comp2, CompAddr2, _} = connect_component(CompOpts2), 174: 175: escalus:story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 176: %% When Alice sends a message to the first component 177: Msg1 = escalus_stanza:chat_to(Alice, <<"abc">>), 178: escalus:send(Comp1, escalus_stanza:from(Msg1, CompAddr1)), 179: %% Then component receives it 180: Reply1 = escalus:wait_for_stanza(Alice), 181: escalus:assert(is_chat_message, [<<"abc">>], Reply1), 182: escalus:assert(is_stanza_from, [CompAddr1], Reply1), 183: 184: %% When Bob sends a message to the second component 185: Msg2 = escalus_stanza:chat_to(Bob, <<"def">>), 186: escalus:send(Comp2, escalus_stanza:from(Msg2, CompAddr2)), 187: %% Then it also receives it 188: Reply2 = escalus:wait_for_stanza(Bob), 189: escalus:assert(is_chat_message, [<<"def">>], Reply2), 190: escalus:assert(is_stanza_from, [CompAddr2], Reply2), 191: 192: %% When the second component sends a reply to Bob 193: Msg3 = escalus_stanza:chat_to(CompAddr2, <<"ghi">>), 194: escalus:send(Bob, Msg3), 195: %% Then he receives it 196: Reply3 = escalus:wait_for_stanza(Comp2), 197: escalus:assert(is_chat_message, [<<"ghi">>], Reply3), 198: 199: %% WHen the first component sends a reply to Alice 200: Msg4 = escalus_stanza:chat_to(CompAddr1, <<"jkl">>), 201: escalus:send(Alice, Msg4), 202: %% Then she receives it 203: Reply4 = escalus:wait_for_stanza(Comp1), 204: escalus:assert(is_chat_message, [<<"jkl">>], Reply4) 205: end), 206: 207: disconnect_component(Comp1, CompAddr1), 208: disconnect_component(Comp2, CompAddr2). 209: 210: try_registering_with_wrong_password(Config) -> 211: %% Given a component with a wrong password 212: CompOpts1 = ?config(component1, Config), 213: CompOpts2 = lists:keyreplace(password, 1, CompOpts1, 214: {password, <<"wrong_one">>}), 215: try 216: %% When trying to connect it 217: {Comp, Addr, _} = connect_component(CompOpts2), 218: disconnect_component(Comp, Addr), 219: ct:fail("component connected successfully with wrong password") 220: catch {stream_error, _E} -> 221: %% Then it should fail to do so 222: ok 223: end. 224: 225: try_registering_component_twice(Config) -> 226: %% Given two components with the same name 227: CompOpts1 = ?config(component1, Config), 228: {Comp1, Addr, _} = connect_component(CompOpts1), 229: 230: try 231: %% When trying to connect the second one 232: {Comp2, Addr, _} = connect_component(CompOpts1), 233: disconnect_component(Comp2, Addr), 234: ct:fail("second component connected successfully") 235: catch {stream_error, _} -> 236: %% Then it should fail to do so 237: ok 238: end, 239: 240: disconnect_component(Comp1, Addr). 241: 242: try_registering_existing_host(Config) -> 243: %% Given a external vjud component 244: Component = ?config(vjud_component, Config), 245: 246: try 247: %% When trying to connect it to the server 248: {Comp, Addr, _} = connect_component(Component), 249: disconnect_component(Comp, Addr), 250: ct:fail("vjud component connected successfully") 251: catch {stream_error, _} -> 252: %% Then it should fail since vjud service already exists on the server 253: ok 254: end. 255: 256: %% When conflict_behaviour is kick_old, then: 257: %% - stop old connections by sending stream:error with reason "conflict" 258: kick_old_component_on_conflict(Config) -> 259: CompOpts1 = spec(kicking_component, Config), 260: {Comp1, Addr, _} = connect_component(CompOpts1), 261: 262: %% When trying to connect the second one 263: {Comp2, Addr, _} = connect_component(CompOpts1), 264: 265: %% First connection is disconnected 266: Stanza = escalus:wait_for_stanza(Comp1), 267: escalus:assert(is_stream_error, [<<"conflict">>, <<"">>], Stanza), 268: 269: %% New connection is usable 270: verify_component(Config, Comp2, Addr), 271: 272: disconnect_component(Comp2, Addr). 273: 274: disco_components(Config) -> 275: %% Given two connected components 276: CompOpts1 = ?config(component1, Config), 277: CompOpts2 = ?config(component2, Config), 278: {Comp1, Addr1, _} = connect_component(CompOpts1), 279: {Comp2, Addr2, _} = connect_component(CompOpts2), 280: 281: escalus:story(Config, [{alice, 1}], fun(Alice) -> 282: %% When server asked for the disco features 283: Server = escalus_client:server(Alice), 284: Disco = escalus_stanza:service_discovery(Server), 285: escalus:send(Alice, Disco), 286: 287: %% Then it contains hosts of 2 components 288: DiscoReply = escalus:wait_for_stanza(Alice), 289: escalus:assert(has_service, [Addr1], DiscoReply), 290: escalus:assert(has_service, [Addr2], DiscoReply) 291: end), 292: 293: disconnect_component(Comp1, Addr1), 294: disconnect_component(Comp2, Addr2). 295: 296: %% Verifies that a component connected to the "hidden components" endpoint 297: %% is not discoverable. 298: %% Assumes mod_disco with `{users_can_see_hidden_services, false}` option 299: disco_with_hidden_component(Config) -> 300: %% Given two connected components 301: CompOpts1 = ?config(component1, Config), 302: HCompOpts = spec(hidden_component, Config), 303: {Comp1, Addr1, _} = connect_component(CompOpts1), 304: {HComp, HAddr, _} = connect_component(HCompOpts), 305: 306: escalus:story(Config, [{alice, 1}], fun(Alice) -> 307: %% When server asked for the disco features 308: Server = escalus_client:server(Alice), 309: Disco = escalus_stanza:service_discovery(Server), 310: escalus:send(Alice, Disco), 311: 312: %% Then it contains hosts of component1 and hidden_component is missing 313: DiscoReply = escalus:wait_for_stanza(Alice), 314: escalus:assert(has_service, [Addr1], DiscoReply), 315: escalus:assert(fun(Stanza) -> 316: not escalus_pred:has_service(HAddr, Stanza) 317: end, DiscoReply) 318: end), 319: 320: disconnect_component(Comp1, Addr1), 321: disconnect_component(HComp, HAddr). 322: 323: register_subdomain(Config) -> 324: %% Given one connected component 325: CompOpts1 = ?config(component1, Config), 326: {Comp, Addr, Name} = connect_component_subdomain(CompOpts1), 327: 328: escalus:story(Config, [{alice, 1}, {astrid, 1}], fun(Alice, Astrid) -> 329: %% When Alice asks for service discovery on the server 330: Server1 = escalus_client:server(Alice), 331: Disco1 = escalus_stanza:service_discovery(Server1), 332: escalus:send(Alice, Disco1), 333: 334: %% Then it contains the registered route 335: DiscoReply1 = escalus:wait_for_stanza(Alice), 336: ComponentHost1 = <<Name/binary, ".", Server1/binary>>, 337: escalus:assert(has_service, [ComponentHost1], DiscoReply1), 338: 339: %% When Astrid ask for service discovery on her server 340: Server2 = escalus_client:server(Astrid), 341: false = (Server1 =:= Server2), 342: Disco2 = escalus_stanza:service_discovery(Server2), 343: escalus:send(Astrid, Disco2), 344: 345: %% Then it also contains the registered route 346: DiscoReply2 = escalus:wait_for_stanza(Astrid), 347: ComponentHost2 = <<Name/binary, ".", Server2/binary>>, 348: escalus:assert(has_service, [ComponentHost2], DiscoReply2) 349: 350: end), 351: 352: disconnect_component(Comp, Addr). 353: 354: 355: register_in_cluster(Config) -> 356: %% Given one component connected to the cluster 357: CompOpts1 = ?config(component1, Config), 358: Component1 = connect_component(CompOpts1), 359: {Comp1, Addr1, _} = Component1, 360: CompOpts2 = ?config(component2, Config), 361: Component2 = connect_component(CompOpts2), 362: {Comp2, Addr2, _} = Component2, 363: CompOpts_on_2 = spec(component_on_2, Config), 364: Component_on_2 = connect_component(CompOpts_on_2), 365: {Comp_on_2, Addr_on_2, _} = Component_on_2, 366: 367: escalus:story(Config, [{alice, 1}, {clusterguy, 1}], fun(Alice, ClusterGuy) -> 368: do_chat_with_component(Alice, ClusterGuy, Component1), 369: do_chat_with_component(Alice, ClusterGuy, Component2), 370: do_chat_with_component(Alice, ClusterGuy, Component_on_2) 371: end), 372: 373: disconnect_component(Comp1, Addr1), 374: disconnect_component(Comp2, Addr2), 375: disconnect_component(Comp_on_2, Addr_on_2), 376: ok. 377: 378: clear_on_node_down(Config) -> 379: CompOpts = ?config(component1, Config), 380: ?assertMatch({_, _, _}, connect_component(CompOpts)), 381: ?assertThrow({stream_error, _}, connect_component(CompOpts)), 382: 383: stop_node(mim(), Config), 384: start_node(mim(), Config), 385: 386: {Comp, Addr, _} = connect_component(CompOpts), 387: disconnect_component(Comp, Addr). 388: 389: do_chat_with_component(Alice, ClusterGuy, Component1) -> 390: {Comp, Addr, Name} = Component1, 391: 392: %% When Alice sends a message to the component 393: Msg1 = escalus_stanza:chat_to(Addr, <<"Hi!">>), 394: escalus:send(Alice, Msg1), 395: %% Then component receives it 396: Reply1 = escalus:wait_for_stanza(Comp), 397: escalus:assert(is_chat_message, [<<"Hi!">>], Reply1), 398: 399: %% When components sends a reply 400: Msg2 = escalus_stanza:chat_to(Alice, <<"Oh hi!">>), 401: escalus:send(Comp, escalus_stanza:from(Msg2, Addr)), 402: 403: %% Then Alice receives it 404: Reply2 = escalus:wait_for_stanza(Alice), 405: escalus:assert(is_chat_message, [<<"Oh hi!">>], Reply2), 406: escalus:assert(is_stanza_from, [Addr], Reply2), 407: 408: %% When ClusterGuy (connected to the other node than component) 409: %% sends a message 410: Msg3 = escalus_stanza:chat_to(Addr, <<"Hello!">>), 411: escalus:send(ClusterGuy, Msg3), 412: %% Then component receives it 413: Reply3 = escalus:wait_for_stanza(Comp), 414: escalus:assert(is_chat_message, [<<"Hello!">>], Reply3), 415: 416: %% When components sends a reply 417: Msg4 = escalus_stanza:chat_to(ClusterGuy, <<"Hola!">>), 418: escalus:send(Comp, escalus_stanza:from(Msg4, Addr)), 419: 420: %% Then ClusterGuy receives it 421: Reply4 = escalus:wait_for_stanza(ClusterGuy), 422: escalus:assert(is_chat_message, [<<"Hola!">>], Reply4), 423: escalus:assert(is_stanza_from, [Addr], Reply4), 424: 425: %% When Alice asks for the disco features 426: Server1 = escalus_client:server(Alice), 427: Disco1 = escalus_stanza:service_discovery(Server1), 428: escalus:send(Alice, Disco1), 429: 430: %% Then it contains host of the service 431: DiscoReply1 = escalus:wait_for_stanza(Alice), 432: escalus:assert(has_service, [Addr], DiscoReply1), 433: 434: %% When ClusterGuy asks for the disco features on her server 435: Server2 = escalus_client:server(ClusterGuy), 436: Disco2 = escalus_stanza:service_discovery(Server2), 437: escalus:send(ClusterGuy, Disco2), 438: 439: %% Then it also contains the service (with the other address though) 440: DiscoReply2 = escalus:wait_for_stanza(ClusterGuy), 441: DistributedAddr = <<Name/binary, ".", Server2/binary>>, 442: escalus:assert(has_service, [DistributedAddr], DiscoReply2). 443: 444: 445: register_same_on_both(Config) -> 446: %% Given two components with the same name 447: %% but not on the same host 448: %% we should be able to register 449: %% and we get two components having the same name and address 450: CompOpts2 = ?config(component2, Config), 451: Component2 = connect_component(CompOpts2), 452: {Comp2, Addr, Name} = Component2, 453: CompOpts_d = spec(component_duplicate, Config), 454: Component_d = connect_component(CompOpts_d), 455: {Comp_d, Addr, Name} = Component_d, 456: 457: escalus:story(Config, [{alice, 1}, {clusterguy, 1}], fun(Alice, ClusterGuy) -> 458: %% When Alice sends a message to the component 459: Msg1 = escalus_stanza:chat_to(Addr, <<"Hi!">>), 460: escalus:send(Alice, Msg1), 461: %% Then component receives it (on the same node) 462: Reply1 = escalus:wait_for_stanza(Comp2), 463: escalus:assert(is_chat_message, [<<"Hi!">>], Reply1), 464: 465: %% When components sends a reply 466: Msg2 = escalus_stanza:chat_to(Alice, <<"Oh hi!">>), 467: escalus:send(Comp2, escalus_stanza:from(Msg2, Addr)), 468: 469: %% Then Alice receives it 470: Reply2 = escalus:wait_for_stanza(Alice), 471: escalus:assert(is_chat_message, [<<"Oh hi!">>], Reply2), 472: escalus:assert(is_stanza_from, [Addr], Reply2), 473: 474: %% When ClusterGuy (connected to the other node than component) 475: %% sends a message 476: Msg3 = escalus_stanza:chat_to(Addr, <<"Hello!">>), 477: escalus:send(ClusterGuy, Msg3), 478: %% Then component on his node receives it 479: Reply3 = escalus:wait_for_stanza(Comp_d), 480: escalus:assert(is_chat_message, [<<"Hello!">>], Reply3), 481: 482: %% When components sends a reply 483: Msg4 = escalus_stanza:chat_to(ClusterGuy, <<"Hola!">>), 484: escalus:send(Comp_d, escalus_stanza:from(Msg4, Addr)), 485: 486: %% Then ClusterGuy receives it 487: Reply4 = escalus:wait_for_stanza(ClusterGuy), 488: escalus:assert(is_chat_message, [<<"Hola!">>], Reply4), 489: escalus:assert(is_stanza_from, [Addr], Reply4), 490: 491: %% When Alice asks for the disco features 492: Server1 = escalus_client:server(Alice), 493: Disco1 = escalus_stanza:service_discovery(Server1), 494: escalus:send(Alice, Disco1), 495: 496: %% Then it contains host of the service 497: DiscoReply1 = escalus:wait_for_stanza(Alice), 498: escalus:assert(has_service, [Addr], DiscoReply1), 499: 500: %% When ClusterGuy asks for the disco features on her server 501: Server2 = escalus_client:server(ClusterGuy), 502: Disco2 = escalus_stanza:service_discovery(Server2), 503: escalus:send(ClusterGuy, Disco2), 504: 505: %% Then it also contains the same service 506: DiscoReply2 = escalus:wait_for_stanza(ClusterGuy), 507: escalus:assert(has_service, [Addr], DiscoReply2) 508: 509: end), 510: disconnect_components([Comp2, Comp_d], Addr), 511: ok. 512: 513: %%-------------------------------------------------------------------- 514: %% Helpers 515: %%-------------------------------------------------------------------- 516: 517: get_components(Opts, Config) -> 518: Components = [component1, component2, vjud_component], 519: [ {C, Opts ++ spec(C, Config)} || C <- Components ] ++ Config. 520: 521: add_domain(Config) -> 522: Hosts = {hosts, "\"localhost\", \"sogndal\""}, 523: ejabberd_node_utils:backup_config_file(Config), 524: ejabberd_node_utils:modify_config_file([Hosts], Config), 525: ejabberd_node_utils:restart_application(mongooseim), 526: ok. 527: 528: restore_domain(Config) -> 529: ejabberd_node_utils:restore_config_file(Config), 530: ejabberd_node_utils:restart_application(mongooseim), 531: Config. 532: 533: 534: %%-------------------------------------------------------------------- 535: %% Stanzas 536: %%-------------------------------------------------------------------- 537: 538: 539: cluster_users() -> 540: AllUsers = ct:get_config(escalus_users), 541: [proplists:lookup(alice, AllUsers), proplists:lookup(clusterguy, AllUsers)].