1: -module(carboncopy_SUITE). 2: 3: -compile([export_all, nowarn_export_all]). 4: -include_lib("common_test/include/ct.hrl"). 5: -include_lib("proper/include/proper.hrl"). 6: -include_lib("eunit/include/eunit.hrl"). 7: 8: -define(AE(Expected, Actual), ?assertEqual(Expected, Actual)). 9: -define(BODY, <<"And pious action">>). 10: 11: -import(mongoose_helper, [enable_carbons/1, disable_carbons/1]). 12: -import(domain_helper, [domain/0]). 13: 14: all() -> 15: [{group, one2one}, 16: {group, muc}]. 17: 18: groups() -> 19: [{one2one, [parallel], 20: [discovering_support, 21: enabling_carbons, 22: disabling_carbons, 23: avoiding_carbons, 24: non_enabled_clients_dont_get_sent_carbons, 25: non_enabled_clients_dont_get_received_carbons, 26: enabled_single_resource_doesnt_get_carbons, 27: unavailable_resources_dont_get_carbons, 28: dropped_client_doesnt_create_duplicate_carbons, 29: prop_forward_received_chat_messages, 30: prop_forward_sent_chat_messages, 31: prop_normal_routing_to_bare_jid, 32: chat_message_is_carbon_copied, 33: normal_message_with_body_is_carbon_copied, 34: normal_message_with_receipt_is_carbon_copied, 35: normal_message_with_csn_is_carbon_copied, 36: normal_message_with_chat_marker_is_carbon_copied]}, 37: {muc, [parallel], 38: [group_chat_is_not_carbon_copied, 39: local_user_to_muc_participant_is_carbon_copied, 40: muc_participant_to_local_user_is_not_carbon_copied]}]. 41: 42: %%%=================================================================== 43: %%% Overall setup/teardown 44: %%%=================================================================== 45: init_per_suite(C) -> 46: escalus:init_per_suite(C). 47: 48: end_per_suite(C) -> 49: escalus_fresh:clean(), 50: escalus:end_per_suite(C). 51: 52: init_per_group(muc, Config) -> 53: muc_helper:load_muc(), 54: Config; 55: init_per_group(_GroupName, Config) -> 56: Config. 57: 58: end_per_group(muc, Config) -> 59: muc_helper:unload_muc(), 60: Config; 61: end_per_group(_GroupName, Config) -> 62: Config. 63: 64: %%%=================================================================== 65: %%% Testcase specific setup/teardown 66: %%%=================================================================== 67: init_per_testcase(Name, C) -> 68: escalus:init_per_testcase(Name, C). 69: 70: end_per_testcase(Name, C) -> 71: escalus:end_per_testcase(Name, C). 72: 73: %%%=================================================================== 74: %%% Individual Test Cases (from groups() definition) 75: %%%=================================================================== 76: discovering_support(Config) -> 77: escalus:fresh_story( 78: Config, [{alice, 1}], 79: fun(Alice) -> 80: IqGet = escalus_stanza:disco_info(domain()), 81: escalus_client:send(Alice, IqGet), 82: Result = escalus_client:wait_for_stanza(Alice), 83: escalus:assert(is_iq_result, [IqGet], Result), 84: escalus:assert(has_feature, [<<"urn:xmpp:carbons:2">>], Result) 85: end). 86: 87: enabling_carbons(Config) -> 88: escalus:fresh_story(Config, [{alice, 1}], fun mongoose_helper:enable_carbons/1). 89: 90: disabling_carbons(Config) -> 91: escalus:fresh_story(Config, [{alice, 1}], 92: fun(Alice) -> enable_carbons(Alice), 93: disable_carbons(Alice) end). 94: 95: avoiding_carbons(Config) -> 96: escalus:fresh_story( 97: Config, [{alice, 2}, {bob, 1}], 98: fun(Alice1, Alice2, Bob) -> 99: enable_carbons([Alice1, Alice2]), 100: Msg = escalus_stanza:chat_without_carbon_to(Bob, ?BODY), 101: escalus_client:send(Alice1, Msg), 102: BobReceived = escalus_client:wait_for_stanza(Bob), 103: escalus:assert(is_chat_message, [?BODY], BobReceived), 104: ?assertEqual([], escalus_client:wait_for_stanzas(Alice2, 1, 500)), 105: ?assertEqual([], escalus_client:peek_stanzas(Alice2)) 106: end). 107: 108: non_enabled_clients_dont_get_sent_carbons(Config) -> 109: escalus:fresh_story( 110: Config, [{alice, 2}, {bob, 1}], 111: fun(Alice1, Alice2, Bob) -> 112: Msg = escalus_stanza:chat_to(Bob, ?BODY), 113: escalus_client:send(Alice1, Msg), 114: BobReceived = escalus_client:wait_for_stanza(Bob), 115: escalus:assert(is_chat_message, [?BODY], BobReceived), 116: ?assertEqual([], escalus_client:wait_for_stanzas(Alice2, 1, 500)), 117: ?assertEqual([], escalus_client:peek_stanzas(Alice2)) 118: end). 119: 120: non_enabled_clients_dont_get_received_carbons(Config) -> 121: escalus:fresh_story( 122: Config, [{alice, 2}, {bob, 1}], 123: fun(Alice1, Alice2, Bob) -> 124: Msg = escalus_stanza:chat_to(Alice1, ?BODY), 125: escalus_client:send(Bob, Msg), 126: AliceReceived = escalus_client:wait_for_stanza(Alice1), 127: escalus:assert(is_chat_message, [?BODY], AliceReceived), 128: ?assertEqual([], escalus_client:wait_for_stanzas(Alice2, 1, 500)), 129: ?assertEqual([], escalus_client:peek_stanzas(Alice2)) 130: end). 131: 132: enabled_single_resource_doesnt_get_carbons(Config) -> 133: BobsMessages = [ 134: <<"There's such a thing as dwelling">>, 135: <<"On the thought ourselves have nursed,">>, 136: <<"And with scorn and courage telling">>, 137: <<"The world to do its worst.">> 138: ], 139: escalus:fresh_story( 140: Config, [{alice, 1}, {bob, 1}], 141: fun(Alice, Bob) -> 142: enable_carbons(Alice), 143: [ escalus_client:send(Bob, escalus_stanza:chat_to(Alice, M)) 144: || M <- BobsMessages ], 145: [ escalus:assert(is_chat_message, [M], escalus_client:wait_for_stanza(Alice)) 146: || M <- BobsMessages ] 147: end). 148: 149: unavailable_resources_dont_get_carbons(Config) -> 150: escalus:fresh_story( 151: Config, [{alice, 2}, {bob, 1}], 152: fun(Alice1, Alice2, Bob) -> 153: enable_carbons([Alice1, Alice2]), 154: client_unsets_presence(Alice1), 155: escalus:assert(is_presence_with_type, [<<"unavailable">>], 156: escalus_client:wait_for_stanza(Alice2)), 157: 158: escalus_client:send(Bob, escalus_stanza:chat_to(Alice2, ?BODY)), 159: %% Alice2 receives the message, no carbons for Alice1 160: wait_for_message_with_body(Alice2, ?BODY), 161: client_sets_presence(Alice1), 162: %% still no carbons for Alice1, only presences 163: escalus_new_assert:mix_match([is_presence, is_presence], 164: escalus:wait_for_stanzas(Alice1, 2)), 165: escalus:assert(is_presence, [], 166: escalus_client:wait_for_stanza(Alice2)), 167: enable_carbons(Alice1), 168: %% Send a message with a different body and wait for it. 169: %% If we receive it, we can be sure, that our first message has 170: %% been fully processed by the routing pipeline. 171: Body2 = <<"carbonated">>, 172: escalus_client:send(Bob, escalus_stanza:chat_to(Alice2, Body2)), 173: wait_for_message_with_body(Alice2, Body2), 174: carboncopy_helper:wait_for_carbon_chat_with_body(Alice1, Body2, #{from => Bob, to => Alice2}) 175: end). 176: 177: dropped_client_doesnt_create_duplicate_carbons(Config) -> 178: escalus:fresh_story( 179: Config, [{alice, 2}, {bob, 1}], 180: fun(Alice1, Alice2, Bob) -> 181: enable_carbons([Alice1, Alice2]), 182: Msg = escalus_stanza:chat_to(Bob, ?BODY), 183: escalus_client:stop(Config, Alice2), 184: escalus:assert(is_presence_with_type, [<<"unavailable">>], 185: escalus_client:wait_for_stanza(Alice1)), 186: escalus_client:send(Alice1, Msg), 187: escalus:assert(is_chat_message, [?BODY], 188: escalus_client:wait_for_stanza(Bob)), 189: [] = escalus_client:peek_stanzas(Alice1) 190: end). 191: 192: prop_forward_received_chat_messages(Config) -> 193: run_prop 194: (forward_received, 195: ?FORALL({N, Msg}, {no_of_resources(), utterance()}, 196: true_story 197: (Config, [{alice, 1}, {bob, N}], 198: fun(Users) -> 199: all_bobs_other_resources_get_received_carbons(Users, 200: Msg) 201: end))). 202: 203: prop_forward_sent_chat_messages(Config) -> 204: run_prop 205: (forward_sent, 206: ?FORALL({N, Msg}, {no_of_resources(), utterance()}, 207: true_story 208: (Config, [{alice, 1}, {bob, N}], 209: fun(Users) -> 210: all_bobs_other_resources_get_sent_carbons(Users, 211: Msg) 212: end))). 213: 214: prop_normal_routing_to_bare_jid(Config) -> 215: run_prop 216: (normal_routing, 217: ?FORALL({N, Msg}, {no_of_resources(), utterance()}, 218: true_story 219: (Config, [{alice, 1}, {bob, N}], 220: fun(Users) -> 221: all_bobs_resources_get_message_to_bare_jid(Users, 222: Msg) 223: end))). 224: 225: chat_message_is_carbon_copied(Config) -> 226: message_is_carbon_copied(Config, fun carboncopy_helper:chat_message_with_body/1). 227: 228: normal_message_with_body_is_carbon_copied(Config) -> 229: message_is_carbon_copied(Config, fun carboncopy_helper:normal_message_with_body/1). 230: 231: normal_message_with_receipt_is_carbon_copied(Config) -> 232: message_is_carbon_copied(Config, fun carboncopy_helper:normal_message_with_receipt/1). 233: 234: normal_message_with_csn_is_carbon_copied(Config) -> 235: message_is_carbon_copied(Config, fun carboncopy_helper:normal_message_with_csn/1). 236: 237: normal_message_with_chat_marker_is_carbon_copied(Config) -> 238: message_is_carbon_copied(Config, fun carboncopy_helper:normal_message_with_chat_marker/1). 239: 240: message_is_carbon_copied(Config, StanzaFun) -> 241: escalus:fresh_story( 242: Config, [{alice, 2}, {bob, 1}], 243: fun(Alice1, Alice2, Bob) -> 244: enable_carbons([Alice1, Alice2]), 245: Body = <<"carbonated">>, 246: escalus_client:send(Bob, StanzaFun(#{to => Alice2, body => Body})), 247: AliceReceived = escalus_client:wait_for_stanza(Alice2), 248: escalus:assert(is_message, AliceReceived), 249: carboncopy_helper:wait_for_carbon_message(Alice1, #{from => Bob, to => Alice2}) 250: end). 251: 252: group_chat_is_not_carbon_copied(Config) -> 253: escalus:fresh_story(Config, [{alice, 2}, {bob, 1}], 254: fun(Alice1, Alice2, Bob) -> 255: enable_carbons([Alice1, Alice2]), 256: RoomCfg = muc_helper:start_fresh_room(Config, inbox_helper:extract_user_specs(Bob), <<"some_friendly_name">>, default), 257: muc_helper:enter_room(RoomCfg, [{Alice1, <<"cool_alice">>}, {Bob, <<"cool_bob">>}]), 258: 259: Msg = <<"Hi Room!">>, 260: muc_helper:send_to_room(RoomCfg, Bob, Msg), 261: muc_helper:verify_message_received(RoomCfg, [Alice1, Bob], <<"cool_bob">>, Msg), 262: ?assertEqual([], escalus_client:peek_stanzas(Alice2)) 263: end). 264: 265: local_user_to_muc_participant_is_carbon_copied(Config) -> 266: escalus:fresh_story(Config, [{alice, 2}, {bob, 1}], 267: fun(Alice1, Alice2, Bob) -> 268: enable_carbons([Alice1, Alice2]), 269: RoomCfg = muc_helper:start_fresh_room(Config, inbox_helper:extract_user_specs(Bob), <<"some_friendly_name">>, default), 270: muc_helper:enter_room(RoomCfg, [{Alice1, <<"cool_alice">>}, {Bob, <<"cool_bob">>}]), 271: RoomJid = proplists:get_value(room_jid, RoomCfg), 272: Body = <<"Hello!">>, 273: Stanza = escalus_stanza:chat_to(<<RoomJid/binary, "/cool_alice">>, Body), 274: 275: escalus:send(Bob, Stanza), 276: escalus:wait_for_stanza(Alice1), 277: carboncopy_helper:wait_for_carbon_chat_with_body(Alice2, Body, #{from => <<RoomJid/binary, "/cool_bob">>, to => Alice1}) 278: end). 279: 280: muc_participant_to_local_user_is_not_carbon_copied(Config) -> 281: escalus:fresh_story(Config, [{alice, 2}, {bob, 1}], 282: fun(Alice1, Alice2, Bob) -> 283: enable_carbons([Alice1, Alice2]), 284: RoomCfg = muc_helper:start_fresh_room(Config, inbox_helper:extract_user_specs(Bob), <<"some_friendly_name">>, default), 285: muc_helper:enter_room(RoomCfg, [{Alice1, <<"cool_alice">>}, {Bob, <<"cool_bob">>}]), 286: RoomJid = proplists:get_value(room_jid, RoomCfg), 287: Body = <<"Hello!">>, 288: Stanza = escalus_stanza:chat(<<RoomJid/binary, "/cool_bob">>, Alice1, Body), 289: 290: escalus:send(Bob, Stanza), 291: escalus:wait_for_stanza(Alice1), 292: ?assertEqual([], escalus_client:peek_stanzas(Alice2)) 293: end). 294: 295: %% 296: %% Test scenarios w/assertions 297: %% 298: 299: all_bobs_resources_get_message_to_bare_jid([Alice, Bob1 | Bobs], Msg) -> 300: %% All connected resources receive messages sent 301: %% to the user's bare JID without carbon wrappers. 302: enable_carbons([Bob1|Bobs]), 303: escalus_client:send( 304: Alice, escalus_stanza:chat_to(escalus_client:short_jid(Bob1), Msg)), 305: GotMsg = fun(BobsResource) -> 306: escalus:assert( 307: is_chat_message, 308: [Msg], 309: escalus_client:wait_for_stanza(BobsResource)), 310: escalus_assert:has_no_stanzas(BobsResource) 311: end, 312: lists:foreach(GotMsg, [Bob1|Bobs]). 313: 314: all_bobs_other_resources_get_received_carbons([Alice, Bob1 | Bobs], Msg) -> 315: enable_carbons([Bob1|Bobs]), 316: escalus_client:send(Alice, escalus_stanza:chat_to(Bob1, Msg)), 317: escalus_client:wait_for_stanza(Bob1), 318: GotForward = fun(BobsResource) -> 319: escalus:assert( 320: is_forwarded_received_message, 321: [escalus_client:full_jid(Alice), 322: escalus_client:full_jid(Bob1), 323: Msg], 324: escalus_client:wait_for_stanza(BobsResource)), 325: escalus_assert:has_no_stanzas(BobsResource) end, 326: lists:foreach(GotForward, Bobs). 327: 328: all_bobs_other_resources_get_sent_carbons([Alice, Bob1 | Bobs], Msg) -> 329: enable_carbons([Bob1|Bobs]), 330: escalus_client:send(Bob1, escalus_stanza:chat_to(Alice, Msg)), 331: escalus:assert(is_chat_message, [Msg], escalus_client:wait_for_stanza(Alice)), 332: GotCarbon = fun(BobsResource) -> 333: escalus:assert( 334: is_forwarded_sent_message, 335: [escalus_client:full_jid(Bob1), 336: escalus_client:full_jid(Alice), 337: Msg], 338: escalus_client:wait_for_stanza(BobsResource)), 339: escalus_assert:has_no_stanzas(BobsResource) end, 340: lists:foreach(GotCarbon, Bobs). 341: 342: %% 343: %% Internal helpers 344: %% 345: 346: %% Wrapper around escalus:story. Returns PropEr result. 347: true_story(Config, UserSpecs, TestFun) -> 348: try escalus_fresh:story_with_client_list(Config, UserSpecs, TestFun), true 349: catch E -> 350: {error, E} 351: end. 352: 353: %% Number of resources per users 354: no_of_resources() -> 1 + rand:uniform(4). 355: 356: %% A sample chat message 357: utterance() -> 358: proper_types:oneof( 359: [<<"Now, fair Hippolyta, our nuptial hour">>, 360: <<"Draws on apace; four happy days bring in">>, 361: <<"Another moon: but, O, methinks, how slow">>, 362: <<"This old moon wanes! she lingers my desires">>, 363: <<"Like to a step-dame or a dowager">>, 364: <<"Long withering out a young man revenue.">>]). 365: 366: 367: client_unsets_presence(Client) -> 368: escalus_client:send(Client, escalus_stanza:presence(<<"unavailable">>)). 369: 370: client_sets_presence(Client) -> 371: escalus_client:send(Client, escalus_stanza:presence(<<"available">>)). 372: 373: run_prop(PropName, Property) -> 374: ?AE(true, proper:quickcheck(proper:conjunction([{PropName, Property}]), 375: [verbose, long_result, {numtests, 3}])). 376: 377: wait_for_message_with_body(Alice, Body) -> 378: AliceReceived = escalus_client:wait_for_stanza(Alice), 379: escalus:assert(is_chat_message, [Body], AliceReceived).