diff --git a/big_tests/tests/carboncopy_SUITE.erl b/big_tests/tests/carboncopy_SUITE.erl index 1879f15aff0..2a5cddb6cca 100644 --- a/big_tests/tests/carboncopy_SUITE.erl +++ b/big_tests/tests/carboncopy_SUITE.erl @@ -12,23 +12,32 @@ -import(domain_helper, [domain/0]). all() -> - [{group, all}]. + [{group, one2one}, + {group, muc}]. groups() -> - [{all, [parallel], - [discovering_support, - enabling_carbons, - disabling_carbons, - avoiding_carbons, - non_enabled_clients_dont_get_sent_carbons, - non_enabled_clients_dont_get_received_carbons, - enabled_single_resource_doesnt_get_carbons, - unavailable_resources_dont_get_carbons, - dropped_client_doesnt_create_duplicate_carbons, - prop_forward_received_chat_messages, - prop_forward_sent_chat_messages, - prop_normal_routing_to_bare_jid - ]}]. + [{one2one, [parallel], + [discovering_support, + enabling_carbons, + disabling_carbons, + avoiding_carbons, + non_enabled_clients_dont_get_sent_carbons, + non_enabled_clients_dont_get_received_carbons, + enabled_single_resource_doesnt_get_carbons, + unavailable_resources_dont_get_carbons, + dropped_client_doesnt_create_duplicate_carbons, + prop_forward_received_chat_messages, + prop_forward_sent_chat_messages, + prop_normal_routing_to_bare_jid, + chat_message_is_carbon_copied, + normal_message_with_body_is_carbon_copied, + normal_message_with_receipt_is_carbon_copied, + normal_message_with_csn_is_carbon_copied, + normal_message_with_chat_marker_is_carbon_copied]}, + {muc, [parallel], + [group_chat_is_not_carbon_copied, + local_user_to_muc_participant_is_carbon_copied, + muc_participant_to_local_user_is_not_carbon_copied]}]. %%%=================================================================== %%% Overall setup/teardown @@ -40,6 +49,18 @@ end_per_suite(C) -> escalus_fresh:clean(), escalus:end_per_suite(C). +init_per_group(muc, Config) -> + muc_helper:load_muc(Config), + Config; +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(muc, Config) -> + muc_helper:unload_muc(), + Config; +end_per_group(_GroupName, Config) -> + Config. + %%%=================================================================== %%% Testcase specific setup/teardown %%%=================================================================== @@ -150,7 +171,7 @@ unavailable_resources_dont_get_carbons(Config) -> Body2 = <<"carbonated">>, escalus_client:send(Bob, escalus_stanza:chat_to(Alice2, Body2)), wait_for_message_with_body(Alice2, Body2), - carboncopy_helper:wait_for_carbon_with_body(Alice1, Body2, #{from => Bob, to => Alice2}) + carboncopy_helper:wait_for_carbon_chat_with_body(Alice1, Body2, #{from => Bob, to => Alice2}) end). dropped_client_doesnt_create_duplicate_carbons(Config) -> @@ -201,6 +222,75 @@ prop_normal_routing_to_bare_jid(Config) -> Msg) end))). +chat_message_is_carbon_copied(Config) -> + message_is_carbon_copied(Config, fun carboncopy_helper:chat_message_with_body/1). + +normal_message_with_body_is_carbon_copied(Config) -> + message_is_carbon_copied(Config, fun carboncopy_helper:normal_message_with_body/1). + +normal_message_with_receipt_is_carbon_copied(Config) -> + message_is_carbon_copied(Config, fun carboncopy_helper:normal_message_with_receipt/1). + +normal_message_with_csn_is_carbon_copied(Config) -> + message_is_carbon_copied(Config, fun carboncopy_helper:normal_message_with_csn/1). + +normal_message_with_chat_marker_is_carbon_copied(Config) -> + message_is_carbon_copied(Config, fun carboncopy_helper:normal_message_with_chat_marker/1). + +message_is_carbon_copied(Config, StanzaFun) -> + escalus:fresh_story( + Config, [{alice, 2}, {bob, 1}], + fun(Alice1, Alice2, Bob) -> + enable_carbons([Alice1, Alice2]), + Body = <<"carbonated">>, + escalus_client:send(Bob, StanzaFun(#{to => Alice2, body => Body})), + AliceReceived = escalus_client:wait_for_stanza(Alice2), + escalus:assert(is_message, AliceReceived), + carboncopy_helper:wait_for_carbon_message(Alice1, #{from => Bob, to => Alice2}) + end). + +group_chat_is_not_carbon_copied(Config) -> + escalus:fresh_story(Config, [{alice, 2}, {bob, 1}], + fun(Alice1, Alice2, Bob) -> + enable_carbons([Alice1, Alice2]), + RoomCfg = muc_helper:start_fresh_room(Config, inbox_helper:extract_user_specs(Bob), <<"some_friendly_name">>, default), + muc_helper:enter_room(RoomCfg, [{Alice1, <<"cool_alice">>}, {Bob, <<"cool_bob">>}]), + + Msg = <<"Hi Room!">>, + muc_helper:send_to_room(RoomCfg, Bob, Msg), + muc_helper:verify_message_received(RoomCfg, [Alice1, Bob], <<"cool_bob">>, Msg), + ?assertEqual([], escalus_client:peek_stanzas(Alice2)) + end). + +local_user_to_muc_participant_is_carbon_copied(Config) -> + escalus:fresh_story(Config, [{alice, 2}, {bob, 1}], + fun(Alice1, Alice2, Bob) -> + enable_carbons([Alice1, Alice2]), + RoomCfg = muc_helper:start_fresh_room(Config, inbox_helper:extract_user_specs(Bob), <<"some_friendly_name">>, default), + muc_helper:enter_room(RoomCfg, [{Alice1, <<"cool_alice">>}, {Bob, <<"cool_bob">>}]), + RoomJid = proplists:get_value(room_jid, RoomCfg), + Body = <<"Hello!">>, + Stanza = escalus_stanza:chat_to(<>, Body), + + escalus:send(Bob, Stanza), + escalus:wait_for_stanza(Alice1), + carboncopy_helper:wait_for_carbon_chat_with_body(Alice2, Body, #{from => <>, to => Alice1}) + end). + +muc_participant_to_local_user_is_not_carbon_copied(Config) -> + escalus:fresh_story(Config, [{alice, 2}, {bob, 1}], + fun(Alice1, Alice2, Bob) -> + enable_carbons([Alice1, Alice2]), + RoomCfg = muc_helper:start_fresh_room(Config, inbox_helper:extract_user_specs(Bob), <<"some_friendly_name">>, default), + muc_helper:enter_room(RoomCfg, [{Alice1, <<"cool_alice">>}, {Bob, <<"cool_bob">>}]), + RoomJid = proplists:get_value(room_jid, RoomCfg), + Body = <<"Hello!">>, + Stanza = escalus_stanza:chat(<>, Alice1, Body), + + escalus:send(Bob, Stanza), + escalus:wait_for_stanza(Alice1), + ?assertEqual([], escalus_client:peek_stanzas(Alice2)) + end). %% %% Test scenarios w/assertions diff --git a/big_tests/tests/carboncopy_helper.erl b/big_tests/tests/carboncopy_helper.erl index 89edf98602f..b66873005df 100644 --- a/big_tests/tests/carboncopy_helper.erl +++ b/big_tests/tests/carboncopy_helper.erl @@ -1,8 +1,76 @@ -module(carboncopy_helper). --export([wait_for_carbon_with_body/3]). +-compile([export_all, nowarn_export_all]). -wait_for_carbon_with_body(Client, Body, #{from := From, to := To}) -> +-include_lib("exml/include/exml.hrl"). +-include_lib("escalus/include/escalus_xmlns.hrl"). + +wait_for_carbon_chat_with_body(Client, Body, #{from := From, to := To}) when is_binary(From) -> + escalus:assert( + is_forwarded_received_message, + [From, escalus_client:full_jid(To), Body], + escalus_client:wait_for_stanza(Client) + ); + +wait_for_carbon_chat_with_body(Client, Body, #{from := From, to := To}) -> escalus:assert( - is_forwarded_received_message, - [escalus_client:full_jid(From), escalus_client:full_jid(To), Body], - escalus_client:wait_for_stanza(Client)). + is_forwarded_received_message, + [escalus_client:full_jid(From), escalus_client:full_jid(To), Body], + escalus_client:wait_for_stanza(Client) + ). + +wait_for_carbon_message(Client, #{from := From, to := To}) -> + escalus:assert( + fun is_forwarded_received_message/3, + [escalus_client:full_jid(From), escalus_client:full_jid(To)], + escalus_client:wait_for_stanza(Client) + ). + +is_forwarded_received_message(From, To, Stanza) -> + Carbon = exml_query:subelement(Stanza, <<"received">>), + escalus_pred:has_ns(?NS_CARBONS_2, Carbon) andalso + is_forwarded_message(From, To, exml_query:subelement(Carbon, <<"forwarded">>)). + +is_forwarded_message(From, To, Stanza) -> + escalus_pred:has_ns(?NS_FORWARD_0, Stanza) andalso + is_message_from_to(From, To, exml_query:subelement(Stanza, <<"message">>)). + +is_message_from_to(From, To, #xmlel{attrs = Attrs} = Stanza) -> + escalus_pred:is_message(Stanza) andalso + escalus_compat:bin(From) == proplists:get_value(<<"from">>, Attrs) andalso + escalus_compat:bin(To) == proplists:get_value(<<"to">>, Attrs). + +chat_message_with_body(#{to := User, body := Body}) -> + escalus_stanza:chat_to(User, Body). + +normal_message_with_body(#{to := User, body := Body}) -> + escalus_stanza:message(Body, #{type => <<"normal">>, to => User}). + +normal_message_with_receipt(#{to := User}) -> + Msg = #xmlel{ + name = <<"message">>, + attrs = [ + {<<"type">>, <<"normal">>}, + {<<"from">>, escalus_utils:get_jid(User)}, + {<<"id">>, escalus_stanza:id()} + ] + }, + escalus_stanza:receipt_conf(Msg). + +normal_message_with_csn(#{to := User}) -> + #xmlel{ + name = <<"message">>, + attrs = [ + {<<"type">>, <<"normal">>}, + {<<"to">>, escalus_utils:get_jid(User)} + ], + children = [ + #xmlel{ + name = <<"stateName">>, + attrs = [{<<"xmlns">>, ?NS_CHATSTATES}] + } + ] + }. + +normal_message_with_chat_marker(#{to := User}) -> + Msg = escalus_stanza:chat_marker(User, <<"received">>, escalus_stanza:id()), + escalus_stanza:setattr(Msg, <<"type">>, <<"normal">>). diff --git a/big_tests/tests/sm_SUITE.erl b/big_tests/tests/sm_SUITE.erl index e19d4cdea16..8518f2492e0 100644 --- a/big_tests/tests/sm_SUITE.erl +++ b/big_tests/tests/sm_SUITE.erl @@ -950,7 +950,7 @@ carboncopy_works(Config) -> mongoose_helper:enable_carbons([Alice1, Alice]), escalus_connection:send(Bob, escalus_stanza:chat_to(Alice1, <<"msg-4">>)), sm_helper:wait_for_messages(Alice1, [<<"msg-4">>]), - carboncopy_helper:wait_for_carbon_with_body(Alice, <<"msg-4">>, #{from => Bob, to => Alice1}) + carboncopy_helper:wait_for_carbon_chat_with_body(Alice, <<"msg-4">>, #{from => Bob, to => Alice1}) end). carboncopy_works_after_resume(Config) -> @@ -977,12 +977,12 @@ carboncopy_works_after_resume(Config) -> %% Direct send escalus_connection:send(Bob, escalus_stanza:chat_to(Alice1, <<"msg-4">>)), sm_helper:wait_for_messages(Alice1, [<<"msg-4">>]), - carboncopy_helper:wait_for_carbon_with_body(Alice, <<"msg-4">>, #{from => Bob, to => Alice1}), + carboncopy_helper:wait_for_carbon_chat_with_body(Alice, <<"msg-4">>, #{from => Bob, to => Alice1}), escalus_connection:stop(Alice) end). wait_for_carbon_with_bodies(Client, Texts, Params) -> - [carboncopy_helper:wait_for_carbon_with_body(Client, Text, Params) || Text <- Texts]. + [carboncopy_helper:wait_for_carbon_chat_with_body(Client, Text, Params) || Text <- Texts]. buffer_unacked_messages_and_die(Config, AliceSpec, Bob, Texts) -> F = fun(_Client) -> ok end, diff --git a/src/mod_carboncopy.erl b/src/mod_carboncopy.erl index ef6aaff104c..e88b8a54418 100644 --- a/src/mod_carboncopy.erl +++ b/src/mod_carboncopy.erl @@ -25,7 +25,7 @@ %%%---------------------------------------------------------------------- -module (mod_carboncopy). -author ('ecestari@process-one.net'). --xep([{xep, 280}, {version, "0.13.3"}, {legacy_versions, ["0.6"]}]). +-xep([{xep, 280}, {version, "1.0.1"}, {legacy_versions, ["0.6"]}]). -behaviour(gen_mod). -behaviour(mongoose_module_metrics). @@ -180,7 +180,8 @@ is_chat(Packet) -> case exml_query:attr(Packet, <<"type">>, <<"normal">>) of <<"normal">> -> contains_body(Packet) orelse contains_receipts(Packet) orelse - contains_csn(Packet); + contains_csn(Packet) orelse + contains_chat_markers(Packet); <<"chat">> -> true; _ -> false end. @@ -226,6 +227,10 @@ contains_receipts(Packet) -> contains_csn(Packet) -> undefined =/= exml_query:subelement_with_ns(Packet, ?NS_CHATSTATES). +-spec contains_chat_markers(exml:element()) -> boolean(). +contains_chat_markers(Packet) -> + undefined =/= exml_query:subelement_with_ns(Packet, ?NS_CHAT_MARKERS). + -spec is_carbon_private(exml:element()) -> boolean(). is_carbon_private(Packet) -> [] =/= subelements_with_nss(Packet, <<"private">>, carbon_namespaces()).