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, all}].
   16: 
   17: groups() ->
   18:     G = [{all, [parallel],
   19:           [discovering_support,
   20:            enabling_carbons,
   21:            disabling_carbons,
   22:            avoiding_carbons,
   23:            non_enabled_clients_dont_get_sent_carbons,
   24:            non_enabled_clients_dont_get_received_carbons,
   25:            enabled_single_resource_doesnt_get_carbons,
   26:            unavailable_resources_dont_get_carbons,
   27:            dropped_client_doesnt_create_duplicate_carbons,
   28:            prop_forward_received_chat_messages,
   29:            prop_forward_sent_chat_messages,
   30:            prop_normal_routing_to_bare_jid
   31:           ]}],
   32:     ct_helper:repeat_all_until_all_ok(G).
   33: 
   34: %%%===================================================================
   35: %%% Overall setup/teardown
   36: %%%===================================================================
   37: init_per_suite(C) ->
   38:     escalus:init_per_suite(C).
   39: 
   40: end_per_suite(C) ->
   41:     escalus_fresh:clean(),
   42:     escalus:end_per_suite(C).
   43: 
   44: %%%===================================================================
   45: %%% Testcase specific setup/teardown
   46: %%%===================================================================
   47: init_per_testcase(Name, C) ->
   48:     escalus:init_per_testcase(Name, C).
   49: 
   50: end_per_testcase(Name, C) ->
   51:     escalus:end_per_testcase(Name, C).
   52: 
   53: %%%===================================================================
   54: %%% Individual Test Cases (from groups() definition)
   55: %%%===================================================================
   56: discovering_support(Config) ->
   57:     escalus:fresh_story(
   58:       Config, [{alice, 1}],
   59:       fun(Alice) ->
   60:               IqGet = escalus_stanza:disco_info(domain()),
   61:               escalus_client:send(Alice, IqGet),
   62:               Result = escalus_client:wait_for_stanza(Alice),
   63:               escalus:assert(is_iq_result, [IqGet], Result),
   64:               escalus:assert(has_feature, [<<"urn:xmpp:carbons:2">>], Result)
   65:       end).
   66: 
   67: enabling_carbons(Config) ->
   68:     escalus:fresh_story(Config, [{alice, 1}], fun mongoose_helper:enable_carbons/1).
   69: 
   70: disabling_carbons(Config) ->
   71:     escalus:fresh_story(Config, [{alice, 1}],
   72:                         fun(Alice) -> enable_carbons(Alice),
   73:                                       disable_carbons(Alice) end).
   74: 
   75: avoiding_carbons(Config) ->
   76:     escalus:fresh_story(
   77:       Config, [{alice, 2}, {bob, 1}],
   78:       fun(Alice1, Alice2, Bob) ->
   79:               enable_carbons([Alice1, Alice2]),
   80:               Msg = escalus_stanza:chat_without_carbon_to(Bob, ?BODY),
   81:               escalus_client:send(Alice1, Msg),
   82:               BobReceived = escalus_client:wait_for_stanza(Bob),
   83:               escalus:assert(is_chat_message, [?BODY], BobReceived),
   84:               ?assertEqual([], escalus_client:wait_for_stanzas(Alice2, 1, 500)),
   85:               ?assertEqual([], escalus_client:peek_stanzas(Alice2))
   86:       end).
   87: 
   88: non_enabled_clients_dont_get_sent_carbons(Config) ->
   89:     escalus:fresh_story(
   90:       Config, [{alice, 2}, {bob, 1}],
   91:       fun(Alice1, Alice2, Bob) ->
   92:               Msg = escalus_stanza:chat_to(Bob, ?BODY),
   93:               escalus_client:send(Alice1, Msg),
   94:               BobReceived = escalus_client:wait_for_stanza(Bob),
   95:               escalus:assert(is_chat_message, [?BODY], BobReceived),
   96:               ?assertEqual([], escalus_client:wait_for_stanzas(Alice2, 1, 500)),
   97:               ?assertEqual([], escalus_client:peek_stanzas(Alice2))
   98:       end).
   99: 
  100: non_enabled_clients_dont_get_received_carbons(Config) ->
  101:     escalus:fresh_story(
  102:       Config, [{alice, 2}, {bob, 1}],
  103:       fun(Alice1, Alice2, Bob) ->
  104:               Msg = escalus_stanza:chat_to(Alice1, ?BODY),
  105:               escalus_client:send(Bob, Msg),
  106:               AliceReceived = escalus_client:wait_for_stanza(Alice1),
  107:               escalus:assert(is_chat_message, [?BODY], AliceReceived),
  108:               ?assertEqual([], escalus_client:wait_for_stanzas(Alice2, 1, 500)),
  109:               ?assertEqual([], escalus_client:peek_stanzas(Alice2))
  110:       end).
  111: 
  112: enabled_single_resource_doesnt_get_carbons(Config) ->
  113:     BobsMessages = [
  114:                     <<"There's such a thing as dwelling">>,
  115:                     <<"On the thought ourselves have nursed,">>,
  116:                     <<"And with scorn and courage telling">>,
  117:                     <<"The world to do its worst.">>
  118:                    ],
  119:     escalus:fresh_story(
  120:       Config, [{alice, 1}, {bob, 1}],
  121:       fun(Alice, Bob) ->
  122:               enable_carbons(Alice),
  123:               [ escalus_client:send(Bob, escalus_stanza:chat_to(Alice, M))
  124:                 || M <- BobsMessages ],
  125:               [ escalus:assert(is_chat_message, [M], escalus_client:wait_for_stanza(Alice))
  126:                 || M <- BobsMessages ]
  127:       end).
  128: 
  129: unavailable_resources_dont_get_carbons(Config) ->
  130:     escalus:fresh_story(
  131:       Config, [{alice, 2}, {bob, 1}],
  132:       fun(Alice1, Alice2, Bob) ->
  133:           enable_carbons([Alice1, Alice2]),
  134:           client_unsets_presence(Alice1),
  135:           escalus:assert(is_presence_with_type, [<<"unavailable">>],
  136:                          escalus_client:wait_for_stanza(Alice2)),
  137: 
  138:           escalus_client:send(Bob, escalus_stanza:chat_to(Alice2, ?BODY)),
  139:           %% Alice2 receives the message, no carbons for Alice1
  140:           wait_for_message_with_body(Alice2, ?BODY),
  141:           client_sets_presence(Alice1),
  142:           %% still no carbons for Alice1, only presences
  143:           escalus_new_assert:mix_match([is_presence, is_presence],
  144:                                        escalus:wait_for_stanzas(Alice1, 2)),
  145:           escalus:assert(is_presence, [],
  146:                          escalus_client:wait_for_stanza(Alice2)),
  147:           enable_carbons(Alice1),
  148:           %% Send a message with a different body and wait for it.
  149:           %% If we receive it, we can be sure, that our first message has
  150:           %% been fully processed by the routing pipeline.
  151:           Body2 = <<"carbonated">>,
  152:           escalus_client:send(Bob, escalus_stanza:chat_to(Alice2, Body2)),
  153:           wait_for_message_with_body(Alice2, Body2),
  154:           wait_for_carbon_with_body(Alice1, Body2, #{from => Bob, to => Alice2})
  155:       end).
  156: 
  157: dropped_client_doesnt_create_duplicate_carbons(Config) ->
  158:     escalus:fresh_story(
  159:       Config, [{alice, 2}, {bob, 1}],
  160:       fun(Alice1, Alice2, Bob) ->
  161:               enable_carbons([Alice1, Alice2]),
  162:               Msg = escalus_stanza:chat_to(Bob, ?BODY),
  163:               escalus_client:stop(Config, Alice2),
  164:               escalus:assert(is_presence_with_type, [<<"unavailable">>],
  165:                              escalus_client:wait_for_stanza(Alice1)),
  166:               escalus_client:send(Alice1, Msg),
  167:               escalus:assert(is_chat_message, [?BODY],
  168:                              escalus_client:wait_for_stanza(Bob)),
  169:               [] = escalus_client:peek_stanzas(Alice1)
  170:       end).
  171: 
  172: prop_forward_received_chat_messages(Config) ->
  173:     run_prop
  174:       (forward_received,
  175:     ?FORALL({N, Msg}, {no_of_resources(), utterance()},
  176:                true_story
  177:                  (Config, [{alice, 1}, {bob, N}],
  178:                        fun(Users) ->
  179:                            all_bobs_other_resources_get_received_carbons(Users,
  180:                                                                          Msg)
  181:                   end))).
  182: 
  183: prop_forward_sent_chat_messages(Config) ->
  184:     run_prop
  185:         (forward_sent,
  186:     ?FORALL({N, Msg}, {no_of_resources(), utterance()},
  187:                  true_story
  188:                    (Config, [{alice, 1}, {bob, N}],
  189:                        fun(Users) ->
  190:                                all_bobs_other_resources_get_sent_carbons(Users,
  191:                                                                          Msg)
  192:                     end))).
  193: 
  194: prop_normal_routing_to_bare_jid(Config) ->
  195:     run_prop
  196:         (normal_routing,
  197:     ?FORALL({N, Msg}, {no_of_resources(), utterance()},
  198:                  true_story
  199:                    (Config, [{alice, 1}, {bob, N}],
  200:                        fun(Users) ->
  201:                                all_bobs_resources_get_message_to_bare_jid(Users,
  202:                                                                           Msg)
  203:                     end))).
  204: 
  205: 
  206: %%
  207: %% Test scenarios w/assertions
  208: %%
  209: 
  210: all_bobs_resources_get_message_to_bare_jid([Alice, Bob1 | Bobs], Msg) ->
  211:     %% All connected resources receive messages sent
  212:     %% to the user's bare JID without carbon wrappers.
  213:     enable_carbons([Bob1|Bobs]),
  214:     escalus_client:send(
  215:       Alice, escalus_stanza:chat_to(escalus_client:short_jid(Bob1), Msg)),
  216:     GotMsg = fun(BobsResource) ->
  217:                      escalus:assert(
  218:                        is_chat_message,
  219:                        [Msg],
  220:                        escalus_client:wait_for_stanza(BobsResource)),
  221:                      escalus_assert:has_no_stanzas(BobsResource)
  222:              end,
  223:     lists:foreach(GotMsg, [Bob1|Bobs]).
  224: 
  225: all_bobs_other_resources_get_received_carbons([Alice, Bob1 | Bobs], Msg) ->
  226:     enable_carbons([Bob1|Bobs]),
  227:     escalus_client:send(Alice, escalus_stanza:chat_to(Bob1, Msg)),
  228:     escalus_client:wait_for_stanza(Bob1),
  229:     GotForward = fun(BobsResource) ->
  230:                          escalus:assert(
  231:                            is_forwarded_received_message,
  232:                            [escalus_client:full_jid(Alice),
  233:                             escalus_client:full_jid(Bob1),
  234:                             Msg],
  235:                            escalus_client:wait_for_stanza(BobsResource)),
  236:                          escalus_assert:has_no_stanzas(BobsResource) end,
  237:     lists:foreach(GotForward, Bobs).
  238: 
  239: all_bobs_other_resources_get_sent_carbons([Alice, Bob1 | Bobs], Msg) ->
  240:     enable_carbons([Bob1|Bobs]),
  241:     escalus_client:send(Bob1, escalus_stanza:chat_to(Alice, Msg)),
  242:     escalus:assert(is_chat_message, [Msg], escalus_client:wait_for_stanza(Alice)),
  243:     GotCarbon = fun(BobsResource) ->
  244:                         escalus:assert(
  245:                           is_forwarded_sent_message,
  246:                           [escalus_client:full_jid(Bob1),
  247:                            escalus_client:full_jid(Alice),
  248:                            Msg],
  249:                           escalus_client:wait_for_stanza(BobsResource)),
  250:                         escalus_assert:has_no_stanzas(BobsResource) end,
  251:     lists:foreach(GotCarbon, Bobs).
  252: 
  253: %%
  254: %% Internal helpers
  255: %%
  256: 
  257: %% Wrapper around escalus:story. Returns PropEr result.
  258: true_story(Config, UserSpecs, TestFun) ->
  259:     try escalus_fresh:story_with_client_list(Config, UserSpecs, TestFun), true
  260:     catch E ->
  261:               {error, E}
  262:     end.
  263: 
  264: %% Number of resources per users
  265: no_of_resources() -> 1 + rand:uniform(4).
  266: 
  267: %% A sample chat message
  268: utterance() ->
  269:     proper_types:oneof(
  270:       [<<"Now, fair Hippolyta, our nuptial hour">>,
  271:        <<"Draws on apace; four happy days bring in">>,
  272:        <<"Another moon: but, O, methinks, how slow">>,
  273:        <<"This old moon wanes! she lingers my desires">>,
  274:        <<"Like to a step-dame or a dowager">>,
  275:        <<"Long withering out a young man revenue.">>]).
  276: 
  277: 
  278: client_unsets_presence(Client) ->
  279:     escalus_client:send(Client, escalus_stanza:presence(<<"unavailable">>)).
  280: 
  281: client_sets_presence(Client) ->
  282:     escalus_client:send(Client, escalus_stanza:presence(<<"available">>)).
  283: 
  284: run_prop(PropName, Property) ->
  285:     ?AE(true, proper:quickcheck(proper:conjunction([{PropName, Property}]),
  286:                                 [verbose, long_result, {numtests, 3}])).
  287: 
  288: wait_for_message_with_body(Alice, Body) ->
  289:     AliceReceived = escalus_client:wait_for_stanza(Alice),
  290:     escalus:assert(is_chat_message, [Body], AliceReceived).
  291: 
  292: wait_for_carbon_with_body(Alice, Body, #{from := From, to := To}) ->
  293:     escalus:assert(
  294:       is_forwarded_received_message,
  295:       [escalus_client:full_jid(From), escalus_client:full_jid(To), Body],
  296:       escalus_client:wait_for_stanza(Alice)).