1: -module(push_integration_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("common_test/include/ct.hrl").
    5: -include_lib("eunit/include/eunit.hrl").
    6: -include_lib("exml/include/exml.hrl").
    7: -include_lib("inbox.hrl").
    8: 
    9: -define(RPC_SPEC, distributed_helper:mim()).
   10: -define(SESSION_KEY, publish_service).
   11: 
   12: -import(muc_light_helper,
   13:         [
   14:          when_muc_light_message_is_sent/4,
   15:          then_muc_light_message_is_received_by/2,
   16:          when_muc_light_affiliations_are_set/3,
   17:          then_muc_light_affiliations_are_received_by/2
   18:         ]).
   19: -import(push_helper,
   20:         [
   21:          enable_stanza/3,
   22:          become_unavailable/1,
   23:          become_available/2
   24:         ]).
   25: -import(distributed_helper, [rpc/4, subhost_pattern/1]).
   26: -import(domain_helper, [domain/0]).
   27: -import(config_parser_helper, [mod_config/2, config/2]).
   28: 
   29: %%--------------------------------------------------------------------
   30: %% Suite configuration
   31: %%--------------------------------------------------------------------
   32: 
   33: all() ->
   34:     [
   35:      {group, pubsub_ful},
   36:      {group, pubsub_less}
   37:     ].
   38: 
   39: basic_groups() ->
   40:     [
   41:      {group, pm_msg_notifications},
   42:      {group, muclight_msg_notifications},
   43:      {group, pm_notifications_with_inbox},
   44:      {group, groupchat_notifications_with_inbox},
   45:      {group, failure_cases_v3},
   46:      {group, failure_cases_v2},
   47:      {group, integration_with_sm_and_offline_storage},
   48:      {group, enhanced_integration_with_sm}
   49:     ].
   50: 
   51: groups() ->
   52:     G = [
   53:          {pubsub_ful, [], basic_groups()},
   54:          {pubsub_less, [], basic_groups()},
   55:          {integration_with_sm_and_offline_storage,[parallel],
   56:           [
   57:            no_duplicates_default_plugin,
   58:            sm_unack_messages_notified_default_plugin
   59:           ]},
   60:          {enhanced_integration_with_sm,[],
   61:           [
   62:               immediate_notification,
   63:               double_notification_with_two_sessions_in_resume
   64:           ]},
   65:          {pm_msg_notifications, [parallel],
   66:           [
   67:            pm_msg_notify_on_apns_w_high_priority,
   68:            pm_msg_notify_on_fcm_w_high_priority,
   69:            pm_msg_notify_on_apns_w_high_priority_silent,
   70:            pm_msg_notify_on_fcm_w_high_priority_silent,
   71:            pm_msg_notify_on_apns_no_click_action,
   72:            pm_msg_notify_on_fcm_no_click_action,
   73:            pm_msg_notify_on_apns_w_click_action,
   74:            pm_msg_notify_on_fcm_w_click_action,
   75:            pm_msg_notify_on_apns_silent,
   76:            pm_msg_notify_on_fcm_silent,
   77:            pm_msg_notify_on_apns_w_topic
   78:           ]},
   79:          {muclight_msg_notifications, [parallel],
   80:           [
   81:            muclight_msg_notify_on_apns_w_high_priority,
   82:            muclight_msg_notify_on_fcm_w_high_priority,
   83:            muclight_msg_notify_on_apns_w_high_priority_silent,
   84:            muclight_msg_notify_on_fcm_w_high_priority_silent,
   85:            muclight_msg_notify_on_apns_no_click_action,
   86:            muclight_msg_notify_on_fcm_no_click_action,
   87:            muclight_msg_notify_on_apns_w_click_action,
   88:            muclight_msg_notify_on_fcm_w_click_action,
   89:            muclight_msg_notify_on_apns_silent,
   90:            muclight_msg_notify_on_fcm_silent,
   91:            muclight_msg_notify_on_w_topic
   92:           ]},
   93:          {groupchat_notifications_with_inbox, [parallel],
   94:           [
   95:            muclight_inbox_msg_unread_count_apns,
   96:            muclight_inbox_msg_unread_count_fcm,
   97:            muclight_aff_change_fcm,
   98:            muclight_aff_change_apns
   99:           ]},
  100:          {pm_notifications_with_inbox, [parallel],
  101:           [
  102:            inbox_msg_unread_count_apns,
  103:            inbox_msg_unread_count_fcm,
  104:            inbox_msg_reset_unread_count_apns,
  105:            inbox_msg_reset_unread_count_fcm
  106:           ]},
  107:          {failure_cases_v3, [parallel], failure_cases()},
  108:          {failure_cases_v2, [parallel], failure_cases()}
  109:         ],
  110:     G.
  111: 
  112: failure_cases() ->
  113:     [
  114:      no_push_notification_for_expired_device,
  115:      no_push_notification_for_internal_mongoose_push_error
  116:     ].
  117: 
  118: suite() ->
  119:     escalus:suite().
  120: 
  121: %%--------------------------------------------------------------------
  122: %% Init & teardown
  123: %%--------------------------------------------------------------------
  124: 
  125: init_per_suite(Config) ->
  126:     catch mongoose_push_mock:stop(),
  127:     mongoose_push_mock:start(Config),
  128:     Port = mongoose_push_mock:port(),
  129:     PoolOpts = #{strategy => available_worker, workers => 20},
  130:     ConnOpts = #{host => "https://localhost:" ++ integer_to_list(Port), request_timeout => 2000},
  131:     Pool = config([outgoing_pools, http, mongoose_push_http], #{opts => PoolOpts, conn_opts => ConnOpts}),
  132:     [{ok, _Pid}] = rpc(?RPC_SPEC, mongoose_wpool, start_configured_pools, [[Pool]]),
  133:     ConfigWithModules = dynamic_modules:save_modules(domain(), Config),
  134:     escalus:init_per_suite(ConfigWithModules).
  135: 
  136: end_per_suite(Config) ->
  137:     escalus_fresh:clean(),
  138:     rpc(?RPC_SPEC, mongoose_wpool, stop, [http, global, mongoose_push_http]),
  139:     mongoose_push_mock:stop(),
  140:     escalus:end_per_suite(Config).
  141: 
  142: init_per_group(pubsub_less, Config) ->
  143:     [{pubsub_host, virtual} | Config];
  144: init_per_group(pubsub_ful, Config) ->
  145:     [{pubsub_host, real} | Config];
  146: init_per_group(G, Config) when G =:= pm_notifications_with_inbox;
  147:                                G =:= groupchat_notifications_with_inbox;
  148:                                G =:= integration_with_sm_and_offline_storage ->
  149:     case mongoose_helper:is_rdbms_enabled(domain()) of
  150:         true ->
  151:             init_modules(G, Config);
  152:         _ ->
  153:             {skip, require_rdbms}
  154:     end;
  155: init_per_group(G, Config) ->
  156:     %% Some cleaning up
  157:     C = init_modules(G, Config),
  158:     ReqMods = proplists:get_value(required_modules, C, []),
  159:     case lists:keymember(mod_muc_light, 1, ReqMods) of
  160:         true ->
  161:             muc_light_helper:clear_db(domain_helper:host_type());
  162:         false ->
  163:             ct:log("Skip muc_light_helper:clear_db()", [])
  164:     end,
  165:     C.
  166: 
  167: end_per_group(_, Config) ->
  168:     dynamic_modules:restore_modules(Config),
  169:     Config.
  170: 
  171: init_per_testcase(CaseName, Config) ->
  172:     escalus:init_per_testcase(CaseName, Config).
  173: 
  174: end_per_testcase(CaseName, Config) ->
  175:     escalus:end_per_testcase(CaseName, Config).
  176: 
  177: %%------------------------------------------------------------------------------------
  178: %% GROUP integration_with_sm_and_offline_storage & enhanced_integration_with_sm
  179: %%------------------------------------------------------------------------------------
  180: no_duplicates_default_plugin(Config) ->
  181:     ConnSteps = [start_stream, stream_features, maybe_use_ssl,
  182:                  authenticate, bind, session],
  183: 
  184:     %% connect bob and alice
  185:     BobSpec = escalus_fresh:create_fresh_user(Config, bob),
  186:     {ok, Bob, _} = escalus_connection:start(BobSpec),
  187:     escalus_connection:send(Bob, escalus_stanza:presence(<<"available">>)),
  188:     BobJID = bare_jid(Bob),
  189: 
  190:     AliceSpec = escalus_fresh:create_fresh_user(Config, alice),
  191:     {ok, Alice, _} = escalus_connection:start(AliceSpec, ConnSteps),
  192:     escalus_connection:send(Alice, escalus_stanza:presence(<<"available">>)),
  193:     escalus_connection:get_stanza(Alice, presence),
  194: 
  195:     #{device_token := APNSDevice} = enable_push_for_user(Alice, <<"apns">>, [], Config),
  196: 
  197:     escalus_connection:stop(Alice),
  198:     push_helper:wait_for_user_offline(Alice),
  199: 
  200:     escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-1">>)),
  201:     mongoose_helper:wait_until(fun() -> get_number_of_offline_msgs(AliceSpec) end, 1),
  202:     verify_notification(APNSDevice, <<"apns">>, [], BobJID, <<"msg-1">>),
  203:     {ok, NewAlice, _} = escalus_connection:start([{manual_ack, true} | AliceSpec],
  204:                                                  ConnSteps ++ [stream_management]),
  205:     escalus_connection:send(NewAlice, escalus_stanza:presence(<<"available">>)),
  206:     Stanzas = escalus:wait_for_stanzas(NewAlice, 2),
  207:     escalus:assert_many([is_presence, is_chat_message], Stanzas),
  208: 
  209:     escalus_connection:stop(NewAlice),
  210:     mongoose_helper:wait_until(fun() -> get_number_of_offline_msgs(AliceSpec) end, 1),
  211: 
  212:     ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice, 500)),
  213: 
  214:     escalus_connection:stop(Bob).
  215: 
  216: get_number_of_offline_msgs(Spec) ->
  217:     Username = escalus_utils:jid_to_lower(proplists:get_value(username, Spec)),
  218:     Server = proplists:get_value(server, Spec),
  219:     mongoose_helper:total_offline_messages({Username, Server}).
  220: 
  221: sm_unack_messages_notified_default_plugin(Config) ->
  222:     ConnSteps = [start_stream, stream_features, maybe_use_ssl,
  223:                  authenticate, bind, session, stream_management],
  224: 
  225:     %% connect bob and alice
  226:     BobSpec = escalus_fresh:create_fresh_user(Config, bob),
  227:     {ok, Bob, _} = escalus_connection:start(BobSpec),
  228:     escalus_connection:send(Bob, escalus_stanza:presence(<<"available">>)),
  229:     escalus_connection:get_stanza(Bob, presence),
  230:     BobJID = bare_jid(Bob),
  231: 
  232:     AliceSpec = [{manual_ack, false}, {stream_management, true} |
  233:                  escalus_fresh:create_fresh_user(Config, alice)],
  234:     {ok, Alice, _} = escalus_connection:start(AliceSpec, ConnSteps),
  235:     escalus_connection:send(Alice, escalus_stanza:presence(<<"available">>)),
  236:     escalus_connection:get_stanza(Alice, presence),
  237: 
  238:     Room = fresh_room_name(),
  239:     RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, [{Bob, member}]),
  240: 
  241:     #{device_token := FCMDevice} = enable_push_for_user(Alice, <<"fcm">>, [], Config),
  242: 
  243:     escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-0">>)),
  244:     escalus:assert(is_chat_message, [<<"msg-0">>], escalus_connection:get_stanza(Alice, msg)),
  245: 
  246:     H = escalus_connection:get_sm_h(Alice),
  247:     escalus:send(Alice, escalus_stanza:sm_ack(H)),
  248: 
  249:     escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-1">>)),
  250:     escalus:assert(is_chat_message, [<<"msg-1">>], escalus_connection:get_stanza(Alice, msg)),
  251:     SenderJID = muclight_conversation(Bob, RoomJID, <<"msg-2">>),
  252:     escalus:assert(is_groupchat_message, [<<"msg-2">>], escalus_connection:get_stanza(Alice, msg)),
  253: 
  254:     escalus_connection:stop(Alice),
  255:     push_helper:wait_for_user_offline(Alice),
  256: 
  257:     verify_notification(FCMDevice, <<"fcm">>, [], [{SenderJID, <<"msg-2">>},
  258:                                                    {BobJID, <<"msg-1">>}]),
  259: 
  260:     ?assertExit({test_case_failed, _}, wait_for_push_request(FCMDevice, 500)),
  261: 
  262:     escalus_connection:stop(Bob).
  263: 
  264: immediate_notification(Config) ->
  265:     ConnSteps = [start_stream, stream_features, maybe_use_ssl,
  266:                  authenticate, bind, session, stream_resumption],
  267: 
  268:     %% connect bob and alice
  269:     BobSpec = escalus_fresh:create_fresh_user(Config, bob),
  270:     {ok, Bob, _} = escalus_connection:start(BobSpec),
  271:     escalus_connection:send(Bob, escalus_stanza:presence(<<"available">>)),
  272:     escalus_connection:get_stanza(Bob, presence),
  273:     BobJID = bare_jid(Bob),
  274: 
  275:     AliceSpec = [{manual_ack, false}, {stream_management, true} |
  276:                  escalus_fresh:create_fresh_user(Config, alice)],
  277:     {ok, Alice, _} = escalus_connection:start(AliceSpec, ConnSteps),
  278:     escalus_connection:send(Alice, escalus_stanza:presence(<<"available">>)),
  279:     escalus_connection:get_stanza(Alice, presence),
  280: 
  281:     #{device_token := APNSDevice} = enable_push_for_user(Alice, <<"apns">>, [], Config),
  282:     #{device_token := FCMDevice} = enable_push_for_user(Alice, <<"fcm">>, [], Config),
  283: 
  284:     escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-0">>)),
  285:     escalus:assert(is_chat_message, [<<"msg-0">>], escalus_connection:get_stanza(Alice, msg)),
  286: 
  287:     H = escalus_connection:get_sm_h(Alice),
  288:     escalus:send(Alice, escalus_stanza:sm_ack(H)),
  289: 
  290:     escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-1">>)),
  291:     escalus:assert(is_chat_message, [<<"msg-1">>], escalus_connection:get_stanza(Alice, msg)),
  292: 
  293:     C2SPid = mongoose_helper:get_session_pid(Alice, distributed_helper:mim()),
  294:     escalus_connection:kill(Alice),
  295: 
  296:     verify_notification(FCMDevice, <<"fcm">>, [], BobJID, <<"msg-1">>),
  297: 
  298:     escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice), <<"msg-2">>)),
  299:     verify_notification(FCMDevice, <<"fcm">>, [], BobJID, <<"msg-2">>),
  300: 
  301:     ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice, 500)),
  302: 
  303:     rpc(?RPC_SPEC, sys, terminate, [C2SPid, normal]),
  304: 
  305:     verify_notification(APNSDevice, <<"apns">>, [], [{BobJID, <<"msg-1">>},
  306:                                                      {BobJID, <<"msg-2">>}]),
  307: 
  308:     ?assertExit({test_case_failed, _}, wait_for_push_request(FCMDevice, 500)),
  309: 
  310:     escalus_connection:stop(Bob).
  311: 
  312: double_notification_with_two_sessions_in_resume(Config) ->
  313: 
  314: %%    This test case serves as a demonstration of doubled push notifications
  315: %%    which occur with multiple push devices and sessions in resume state
  316: 
  317: %%    diagram presenting the test's logic
  318: %%    generated using https://www.sequencediagram.org/
  319: %%           title double notifications in 2 sessions in resume
  320: %%           BobSocket -> BobC2S: msg-1
  321: %%           participant Alice1C2S #red
  322: %%           participant Alice2C2S #blue
  323: %%           BobC2S -#red> Alice1C2S: msg-1
  324: %%           BobC2S -#blue> Alice2C2S: msg-1
  325: %%           note over Alice1C2S: connection dies
  326: %%           activate Alice1C2S
  327: %%           note over Alice2C2S: connection dies
  328: %%           activate Alice2C2S
  329: %%           Alice1C2S -#red> PushService: msg-1 (alice1 device)
  330: %%           deactivate Alice1C2S
  331: %%           Alice2C2S -#blue> PushService: msg-1 (alice2 device)
  332: %%           deactivate Alice2C2S
  333: %%           note over Alice1C2S: resumption t/o
  334: %%           activate Alice1C2S
  335: %%           Alice1C2S -#red> Alice2C2S: msg-1
  336: %%           activate Alice2C2S #red
  337: %%           deactivate Alice1C2S
  338: %%           destroyafter Alice1C2S
  339: %%           Alice2C2S -#red> PushService: msg-1 (alice2 device)
  340: %%           deactivate Alice2C2S
  341: %%           note over Alice2C2S: resumption t/o
  342: %%           activate Alice2C2S
  343: %%           Alice2C2S -#blue> PushService: msg-1 (alice1 device)
  344: %%           deactivate Alice2C2S
  345: %%           destroyafter Alice2C2S
  346: 
  347:     ConnSteps = [start_stream, stream_features, maybe_use_ssl,
  348:                  authenticate, bind, session, stream_resumption],
  349: 
  350:     %% connect bob
  351:     BobSpec = escalus_fresh:create_fresh_user(Config, bob),
  352:     {ok, Bob, _} = escalus_connection:start(BobSpec),
  353:     escalus_session:send_presence_available(Bob),
  354:     escalus_connection:get_stanza(Bob, presence),
  355:     BobJID = bare_jid(Bob),
  356: 
  357:     %% connect two resources for alice
  358:     AliceSpec1 = [{manual_ack, false}, {stream_resumption, true} |
  359:                   escalus_fresh:create_fresh_user(Config, alice)],
  360:     AliceSpec2 = [{resource,<<"RES2">>} | AliceSpec1],
  361: 
  362:     {ok, Alice1, _} = escalus_connection:start(AliceSpec1, ConnSteps),
  363:     {ok, Alice2, _} = escalus_connection:start(AliceSpec2, ConnSteps),
  364: 
  365:     escalus_session:send_presence_available(Alice1),
  366:     escalus_connection:get_stanza(Alice1, presence),
  367: 
  368:     escalus_session:send_presence_available(Alice2),
  369:     escalus_connection:get_stanza(Alice2, presence),
  370: 
  371:     escalus_connection:get_stanza(Alice1, presence),
  372:     escalus_connection:get_stanza(Alice2, presence),
  373: 
  374:     #{device_token := APNSDevice1} = enable_push_for_user(Alice1, <<"apns">>, [], Config),
  375:     #{device_token := APNSDevice2} = enable_push_for_user(Alice2, <<"apns">>, [], Config),
  376: 
  377:     escalus_connection:send(Bob, escalus_stanza:chat_to(bare_jid(Alice1), <<"msg-1">>)),
  378:     escalus:assert(is_chat_message, [<<"msg-1">>], escalus_connection:get_stanza(Alice1, msg)),
  379:     escalus:assert(is_chat_message, [<<"msg-1">>], escalus_connection:get_stanza(Alice2, msg)),
  380: 
  381:     %% go into resume state, which should fire a hook which pushes notifications
  382:     C2SPid1 = mongoose_helper:get_session_pid(Alice1, distributed_helper:mim()),
  383:     escalus_connection:kill(Alice1),
  384:     C2SPid2 = mongoose_helper:get_session_pid(Alice2, distributed_helper:mim()),
  385:     escalus_connection:kill(Alice2),
  386: 
  387:     verify_notification(APNSDevice1, <<"apns">>, [], [{BobJID, <<"msg-1">>}]),
  388:     verify_notification(APNSDevice2, <<"apns">>, [], [{BobJID, <<"msg-1">>}]),
  389: 
  390:     ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice1, 500)),
  391:     ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice2, 1)),
  392: 
  393:     %% close xmpp stream for Alice1, which causes push notification for APNSDevice2
  394:     rpc(?RPC_SPEC, sys, terminate, [C2SPid1, normal]),
  395: 
  396:     verify_notification(APNSDevice2, <<"apns">>, [], [{BobJID, <<"msg-1">>}]),
  397:     ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice1, 500)),
  398: 
  399:     %% close xmpp stream for Alice2, which causes push notification for APNSDevice1
  400:     rpc(?RPC_SPEC, sys, terminate, [C2SPid2, normal]),
  401: 
  402:     verify_notification(APNSDevice1, <<"apns">>, [], [{BobJID, <<"msg-1">>}]),
  403:     ?assertExit({test_case_failed, _}, wait_for_push_request(APNSDevice2, 500)),
  404: 
  405:     escalus_connection:stop(Bob).
  406: 
  407: verify_notification(DeviceToken, Service, EnableOpts, Jid, Msg) ->
  408:     verify_notification(DeviceToken, Service, EnableOpts, [{Jid, Msg}]).
  409: 
  410: verify_notification(DeviceToken, Service, EnableOpts, ParamsList) ->
  411:     PredGen = fun({Jid, Msg}) ->
  412:                   fun(Notification) ->
  413:                       try
  414:                           Expected = [{body, Msg}, {unread_count, 1}, {badge, 1}],
  415:                           assert_push_notification(Notification, Service, EnableOpts,
  416:                                                    Jid, Expected),
  417:                           true
  418:                       catch
  419:                           _:_ -> false
  420:                       end
  421:                   end
  422:               end,
  423:     Notifications = [begin
  424:                          {Notification, _} = wait_for_push_request(DeviceToken),
  425:                          Notification
  426:                      end || _ <- ParamsList],
  427:     ?assertEqual(true, escalus_utils:mix_match(PredGen, ParamsList, Notifications)).
  428: 
  429: %%--------------------------------------------------------------------
  430: %% GROUP pm_msg_notifications
  431: %%--------------------------------------------------------------------
  432: 
  433: pm_msg_notify_on_apns(Config, EnableOpts) ->
  434:     escalus:fresh_story(
  435:         Config, [{bob, 1}, {alice, 1}],
  436:         fun(Bob, Alice) ->
  437:             {SenderJID, DeviceToken} = pm_conversation(Alice, Bob, <<"apns">>, EnableOpts, Config),
  438:             {Notification, _} = wait_for_push_request(DeviceToken),
  439: 
  440:             assert_push_notification(Notification, <<"apns">>, EnableOpts, SenderJID, [])
  441: 
  442:         end).
  443: 
  444: pm_msg_notify_on_fcm(Config, EnableOpts) ->
  445:     escalus:fresh_story(
  446:         Config, [{bob, 1}, {alice, 1}],
  447:         fun(Bob, Alice) ->
  448:             {SenderJID, DeviceToken} = pm_conversation(Alice, Bob, <<"fcm">>, EnableOpts, Config),
  449:             {Notification, _} = wait_for_push_request(DeviceToken),
  450: 
  451:             assert_push_notification(Notification, <<"fcm">>, EnableOpts, SenderJID)
  452: 
  453:         end).
  454: 
  455: assert_push_notification(Notification, Service, EnableOpts, SenderJID) ->
  456:     assert_push_notification(Notification, Service, EnableOpts, SenderJID, []).
  457: 
  458: assert_push_notification(Notification, Service, EnableOpts, SenderJID, Expected) ->
  459: 
  460:     ?assertMatch(#{<<"service">> := Service}, Notification),
  461: 
  462:     Alert = maps:get(<<"alert">>, Notification, undefined),
  463:     Data = maps:get(<<"data">>, Notification, undefined),
  464: 
  465:     ExpectedBody = proplists:get_value(body, Expected, <<"OH, HAI!">>),
  466:     UnreadCount = proplists:get_value(unread_count, Expected, 1),
  467:     Badge = proplists:get_value(badge, Expected, 1),
  468: 
  469:     case proplists:get_value(<<"silent">>, EnableOpts) of
  470:         undefined ->
  471:             ?assertMatch(#{<<"body">> := ExpectedBody}, Alert),
  472:             ?assertMatch(#{<<"title">> := SenderJID}, Alert),
  473:             ?assertMatch(#{<<"badge">> := Badge}, Alert),
  474:             ?assertMatch(#{<<"tag">> := SenderJID}, Alert),
  475: 
  476:             case proplists:get_value(<<"click_action">>, EnableOpts) of
  477:                 undefined ->
  478:                     ?assertEqual(error, maps:find(<<"click_action">>, Alert));
  479:                 Activity ->
  480:                     ?assertMatch(#{<<"click_action">> := Activity}, Alert)
  481:             end;
  482:         <<"true">> ->
  483:             ?assertMatch(#{<<"last-message-body">> := ExpectedBody}, Data),
  484:             ?assertMatch(#{<<"last-message-sender">> := SenderJID}, Data),
  485:             ?assertMatch(#{<<"message-count">> := UnreadCount}, Data)
  486:     end,
  487: 
  488:     case proplists:get_value(<<"priority">>, EnableOpts) of
  489:         undefined -> ok;
  490:         Priority ->
  491:             ?assertMatch(Priority, maps:get(<<"priority">>, Notification, undefined))
  492:     end,
  493: 
  494:     case proplists:get_value(<<"topic">>, EnableOpts) of
  495:         undefined -> ok;
  496:         Topic ->
  497:             ?assertMatch(Topic, maps:get(<<"topic">>, Notification, undefined))
  498:     end.
  499: 
  500: 
  501: pm_msg_notify_on_apns_no_click_action(Config) ->
  502:     pm_msg_notify_on_apns(Config, []).
  503: 
  504: pm_msg_notify_on_fcm_no_click_action(Config) ->
  505:     pm_msg_notify_on_fcm(Config, []).
  506: 
  507: pm_msg_notify_on_apns_w_high_priority(Config) ->
  508:     pm_msg_notify_on_apns(Config, [{<<"priority">>, <<"high">>}]).
  509: 
  510: pm_msg_notify_on_fcm_w_high_priority(Config) ->
  511:     pm_msg_notify_on_fcm(Config, [{<<"priority">>, <<"high">>}]).
  512: 
  513: pm_msg_notify_on_apns_w_high_priority_silent(Config) ->
  514:     pm_msg_notify_on_apns(Config, [{<<"silent">>, <<"true">>}, {<<"priority">>, <<"high">>}]).
  515: 
  516: pm_msg_notify_on_fcm_w_high_priority_silent(Config) ->
  517:     pm_msg_notify_on_fcm(Config, [{<<"silent">>, <<"true">>}, {<<"priority">>, <<"high">>}]).
  518: 
  519: pm_msg_notify_on_apns_w_click_action(Config) ->
  520:     pm_msg_notify_on_apns(Config, [{<<"click_action">>, <<"myactivity">>}]).
  521: 
  522: pm_msg_notify_on_fcm_w_click_action(Config) ->
  523:     pm_msg_notify_on_fcm(Config, [{<<"click_action">>, <<"myactivity">>}]).
  524: 
  525: pm_msg_notify_on_fcm_silent(Config) ->
  526:     pm_msg_notify_on_fcm(Config, [{<<"silent">>, <<"true">>}]).
  527: 
  528: pm_msg_notify_on_apns_silent(Config) ->
  529:     pm_msg_notify_on_apns(Config, [{<<"silent">>, <<"true">>}]).
  530: 
  531: pm_msg_notify_on_apns_w_topic(Config) ->
  532:     pm_msg_notify_on_apns(Config, [{<<"topic">>, <<"some_topic">>}]).
  533: 
  534: 
  535: %%--------------------------------------------------------------------
  536: %% GROUP inbox_msg_notifications
  537: %%--------------------------------------------------------------------
  538: 
  539: inbox_msg_unread_count_apns(Config) ->
  540:     inbox_msg_unread_count(Config, <<"apns">>, [{<<"silent">>, <<"true">>}]).
  541: 
  542: inbox_msg_unread_count_fcm(Config) ->
  543:     inbox_msg_unread_count(Config, <<"fcm">>, [{<<"silent">>, <<"true">>}]).
  544: 
  545: muclight_inbox_msg_unread_count_apns(Config) ->
  546:     muclight_inbox_msg_unread_count(Config, <<"apns">>, [{<<"silent">>, <<"true">>}]).
  547: 
  548: muclight_inbox_msg_unread_count_fcm(Config) ->
  549:     muclight_inbox_msg_unread_count(Config, <<"fcm">>, [{<<"silent">>, <<"true">>}]).
  550: 
  551: inbox_msg_reset_unread_count_apns(Config) ->
  552:     inbox_msg_reset_unread_count(Config, <<"apns">>, [{<<"silent">>, <<"true">>}]).
  553: 
  554: inbox_msg_reset_unread_count_fcm(Config) ->
  555:     inbox_msg_reset_unread_count(Config, <<"fcm">>, [{<<"silent">>, <<"true">>}]).
  556: 
  557: inbox_msg_unread_count(Config, Service, EnableOpts) ->
  558:     escalus:fresh_story(
  559:       Config, [{bob, 1}, {alice, 1}, {kate, 1}],
  560:       fun(Bob, Alice, Kate) ->
  561:               % In this test Bob is the only recipient of all messages
  562:               #{device_token := DeviceToken} =
  563:                     enable_push_and_become_unavailable(Bob, Service, EnableOpts, Config),
  564: 
  565:               % We're going to interleave messages from Alice and Kate to ensure
  566:               % that their unread counts don't leak to each other's notifications
  567: 
  568:               % We send a first message from Alice, unread counts in convs.: Alice 1, Kate 0
  569:               send_private_message(Alice, Bob),
  570:               check_notification(DeviceToken, 1),
  571: 
  572:               % We send a first message from Kate, unread counts in convs.: Alice 1, Kate 1
  573:               send_private_message(Kate, Bob),
  574:               check_notification(DeviceToken, 1),
  575: 
  576:               % Now a second message from Alice, unread counts in convs.: Alice 2, Kate 1
  577:               send_private_message(Alice, Bob),
  578:               check_notification(DeviceToken, 2),
  579: 
  580:               % And one more from Alice, unread counts in convs.: Alice 3, Kate 1
  581:               send_private_message(Alice, Bob),
  582:               check_notification(DeviceToken, 3),
  583: 
  584:               % Time for Kate again, unread counts in convs.: Alice 3, Kate 2
  585:               send_private_message(Kate, Bob),
  586:               check_notification(DeviceToken, 2)
  587:       end).
  588: 
  589: inbox_msg_reset_unread_count(Config, Service, EnableOpts) ->
  590:     escalus:fresh_story(
  591:       Config, [{bob, 1}, {alice, 1}],
  592:       fun(Bob, Alice) ->
  593:               #{device_token := DeviceToken} =
  594:                     enable_push_and_become_unavailable(Bob, Service, EnableOpts, Config),
  595:               send_private_message(Alice, Bob, <<"FIRST MESSAGE">>),
  596:               check_notification(DeviceToken, 1),
  597:               MsgId = send_private_message(Alice, Bob, <<"SECOND MESSAGE">>),
  598:               check_notification(DeviceToken, 2),
  599: 
  600:               become_available(Bob, 2),
  601:               inbox_helper:get_inbox(Bob, #{ count => 1 }),
  602:               ChatMarker = escalus_stanza:chat_marker(Alice, <<"displayed">>, MsgId),
  603:               escalus:send(Bob, ChatMarker),
  604:               escalus:wait_for_stanza(Alice),
  605: 
  606:               become_unavailable(Bob),
  607:               send_private_message(Alice, Bob, <<"THIRD MESSAGE">>),
  608:               check_notification(DeviceToken, 1)
  609:       end).
  610: 
  611: 
  612: muclight_inbox_msg_unread_count(Config, Service, EnableOpts) ->
  613:     escalus:fresh_story(
  614:       Config, [{alice, 1}, {kate, 1}],
  615:       fun(Alice, Kate) ->
  616:               Room = fresh_room_name(),
  617:               RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, []),
  618:               KateJid = inbox_helper:to_bare_lower(Kate),
  619: 
  620:               when_muc_light_affiliations_are_set(Alice, Room, [{Kate, member}]),
  621:               muc_light_helper:verify_aff_bcast([{Kate, member}, {Alice, owner}], [{Kate, member}]),
  622:               escalus:wait_for_stanza(Alice),
  623: 
  624:               #{device_token := KateToken} =
  625:                     enable_push_and_become_unavailable(Kate, Service, EnableOpts, Config),
  626: 
  627:               SenderJID = muclight_conversation(Alice, RoomJID, <<"First!">>),
  628:               escalus:wait_for_stanza(Alice),
  629:               {Notification, _} = wait_for_push_request(KateToken),
  630:               assert_push_notification(Notification, Service, EnableOpts, SenderJID,
  631:                                        [{body, <<"First!">>}, {unread_count, 1}, {badge, 1}]),
  632: 
  633:               muclight_conversation(Alice, RoomJID, <<"Second!">>),
  634:               escalus:wait_for_stanza(Alice),
  635:               {Notification2, _} = wait_for_push_request(KateToken),
  636:               assert_push_notification(Notification2, Service, EnableOpts, SenderJID,
  637:                                        [{body, <<"Second!">>}, {unread_count, 2}, {badge, 1}]),
  638: 
  639:               {ok, true} = become_available(Kate, 0),
  640: 
  641:               muclight_conversation(Alice, RoomJID, <<"Third!">>),
  642:               escalus:wait_for_stanza(Kate),
  643:               escalus:wait_for_stanza(Alice),
  644:               inbox_helper:check_inbox(Kate, [#conv{unread = 3,
  645:                                                     from = SenderJID,
  646:                                                     to = KateJid,
  647:                                                     content = <<"Third!">>}])
  648:       end).
  649: 
  650: send_private_message(Sender, Recipient) ->
  651:     send_private_message(Sender, Recipient, <<"Private message">>).
  652: 
  653: send_private_message(Sender, Recipient, Body) ->
  654:     Id = escalus_stanza:id(),
  655:     Msg = escalus_stanza:set_id( escalus_stanza:chat_to(bare_jid(Recipient), Body), Id),
  656:     escalus:send(Sender, Msg),
  657:     Id.
  658: 
  659: check_notification(DeviceToken, ExpectedCount) ->
  660:     {Notification, _} = wait_for_push_request(DeviceToken),
  661:     Data = maps:get(<<"data">>, Notification, undefined),
  662:     ?assertMatch(#{<<"message-count">> := ExpectedCount}, Data).
  663: 
  664: send_message_to_room(Sender, RoomJID) ->
  665:     Stanza = escalus_stanza:groupchat_to(RoomJID, <<"GroupChat message">>),
  666:     escalus:send(Sender, Stanza).
  667: 
  668: 
  669: %%--------------------------------------------------------------------
  670: %% GROUP muclight_msg_notifications
  671: %%--------------------------------------------------------------------
  672: 
  673: muclight_msg_notify_on_apns(Config, EnableOpts) ->
  674:     escalus:fresh_story(
  675:         Config, [{alice, 1}, {bob, 1}],
  676:         fun(Alice, Bob) ->
  677:             Room = fresh_room_name(),
  678:             RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, [{Bob, member}]),
  679:             #{device_token := DeviceToken} =
  680:                 enable_push_and_become_unavailable(Bob, <<"apns">>, EnableOpts, Config),
  681: 
  682:             SenderJID = muclight_conversation(Alice, RoomJID, <<"Heyah!">>),
  683:             {Notification, _} = wait_for_push_request(DeviceToken),
  684:             assert_push_notification(Notification, <<"apns">>, EnableOpts, SenderJID,
  685:                                      [{body, <<"Heyah!">>}, {unread_count, 1}, {badge, 1}])
  686:         end).
  687: 
  688: muclight_msg_notify_on_fcm(Config, EnableOpts) ->
  689:     escalus:fresh_story(
  690:         Config, [{alice, 1}, {bob, 1}],
  691:         fun(Alice, Bob) ->
  692:             Room = fresh_room_name(),
  693:             RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, [{Bob, member}]),
  694:             #{device_token := DeviceToken} =
  695:                 enable_push_and_become_unavailable(Bob, <<"fcm">>, EnableOpts, Config),
  696: 
  697:             SenderJID = muclight_conversation(Alice, RoomJID, <<"Heyah!">>),
  698:             {Notification, _} = wait_for_push_request(DeviceToken),
  699:             assert_push_notification(Notification, <<"fcm">>,
  700:                                      EnableOpts, SenderJID, [{body, <<"Heyah!">>}, {unread_count, 1}, {badge, 1}])
  701:         end).
  702: 
  703: muclight_aff_change(Config, Service, EnableOpts) ->
  704:     escalus:fresh_story(
  705:       Config, [{alice, 1}, {kate, 1}, {bob, 1}],
  706:       fun(Alice, Kate, Bob) ->
  707:               Room = fresh_room_name(),
  708:               RoomJID = muc_light_helper:given_muc_light_room(Room, Alice, []),
  709: 
  710:               {_, Affiliations} = when_muc_light_affiliations_are_set(Alice, Room, [{Kate, member}]),
  711:               then_muc_light_affiliations_are_received_by([Alice, Kate], {Room, Affiliations}),
  712:               escalus:wait_for_stanza(Alice),
  713: 
  714:               #{device_token := KateToken} =
  715:                     enable_push_and_become_unavailable(Kate, Service, EnableOpts, Config),
  716: 
  717:               Bare = bare_jid(Alice),
  718:               SenderJID = <<RoomJID/binary, "/", Bare/binary>>,
  719: 
  720:               {Room, Body, M1} = when_muc_light_message_is_sent(Alice, Room, <<"First!">>, <<"M1">>),
  721:               then_muc_light_message_is_received_by([Alice], {Room, Body, M1}),
  722: 
  723:               {Notification, _} = wait_for_push_request(KateToken),
  724:               assert_push_notification(Notification, Service, EnableOpts, SenderJID,
  725:                                        [{body, <<"First!">>}, {unread_count, 1}, {badge, 1}]),
  726: 
  727:               {_, Aff} = when_muc_light_affiliations_are_set(Alice, Room, [{Bob, member}]),
  728:               then_muc_light_affiliations_are_received_by([Alice, Bob], {Room, Aff}),
  729:               escalus:wait_for_stanza(Alice),
  730: 
  731:               {_, B2, M2} = when_muc_light_message_is_sent(Alice, Room, <<"Second!">>, <<"M2">>),
  732:               then_muc_light_message_is_received_by([Alice, Bob], {Room, B2, M2}),
  733: 
  734:               {Notification2, _} = wait_for_push_request(KateToken),
  735:               assert_push_notification(Notification2, Service, EnableOpts, SenderJID,
  736:                                        [{body, <<"Second!">>}, {unread_count, 2}, {badge, 1}])
  737: 
  738:       end).
  739: 
  740: 
  741: muclight_msg_notify_on_apns_no_click_action(Config) ->
  742:     muclight_msg_notify_on_apns(Config, []).
  743: 
  744: muclight_msg_notify_on_fcm_no_click_action(Config) ->
  745:     muclight_msg_notify_on_fcm(Config, []).
  746: 
  747: muclight_msg_notify_on_apns_w_high_priority(Config) ->
  748:     muclight_msg_notify_on_apns(Config, [{<<"priority">>, <<"high">>}]).
  749: 
  750: muclight_msg_notify_on_fcm_w_high_priority(Config) ->
  751:     muclight_msg_notify_on_fcm(Config, [{<<"priority">>, <<"high">>}]).
  752: 
  753: muclight_msg_notify_on_apns_w_high_priority_silent(Config) ->
  754:     muclight_msg_notify_on_apns(Config, [{<<"silent">>, <<"true">>}, {<<"priority">>, <<"high">>}]).
  755: 
  756: muclight_msg_notify_on_fcm_w_high_priority_silent(Config) ->
  757:     muclight_msg_notify_on_fcm(Config, [{<<"silent">>, <<"true">>}, {<<"priority">>, <<"high">>}]).
  758: 
  759: muclight_msg_notify_on_apns_w_click_action(Config) ->
  760:     muclight_msg_notify_on_apns(Config, [{<<"click_action">>, <<"myactivity">>}]).
  761: 
  762: muclight_msg_notify_on_fcm_w_click_action(Config) ->
  763:     muclight_msg_notify_on_fcm(Config, [{<<"click_action">>, <<"myactivity">>}]).
  764: 
  765: muclight_msg_notify_on_fcm_silent(Config) ->
  766:     muclight_msg_notify_on_fcm(Config, [{<<"silent">>, <<"true">>}]).
  767: 
  768: muclight_msg_notify_on_apns_silent(Config) ->
  769:     muclight_msg_notify_on_apns(Config, [{<<"silent">>, <<"true">>}]).
  770: 
  771: muclight_msg_notify_on_w_topic(Config) ->
  772:     muclight_msg_notify_on_apns(Config, [{<<"topic">>, <<"some_topic">>}]).
  773: 
  774: muclight_aff_change_fcm(Config) ->
  775:     muclight_aff_change(Config, <<"fcm">>, [{<<"silent">>, <<"true">>}]).
  776: 
  777: muclight_aff_change_apns(Config) ->
  778:     muclight_aff_change(Config, <<"apns">>, [{<<"silent">>, <<"true">>}]).
  779: 
  780: no_push_notification_for_expired_device(Config) ->
  781:     escalus:fresh_story(
  782:         Config, [{bob, 1}, {alice, 1}],
  783:         fun(Bob, Alice) ->
  784:             Response = mongoose_push_unregistered_device_resp(Config),
  785:             #{device_token := DeviceToken, pubsub_node := PushNode} =
  786:                     enable_push_and_become_unavailable(Bob, <<"fcm">>, [], Response, Config),
  787:             escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)),
  788:             {_, Response} = wait_for_push_request(DeviceToken),
  789:             maybe_check_if_push_node_was_disabled(?config(api_v, Config), Bob, PushNode)
  790: 
  791:         end).
  792: 
  793: mongoose_push_unregistered_device_resp(Config) ->
  794:     case ?config(api_v, Config) of
  795:         <<"v3">> ->
  796:             {410, jiffy:encode(#{<<"reason">> => <<"unregistered">>})};
  797:         <<"v2">> ->
  798:             {500, jiffy:encode(#{<<"details">> => <<"probably_unregistered">>})}
  799:     end.
  800: 
  801: maybe_check_if_push_node_was_disabled(<<"v2">>, _, _) ->
  802:     ok;
  803: maybe_check_if_push_node_was_disabled(<<"v3">>, User, PushNode) ->
  804:     JID = rpc(?RPC_SPEC, jid, binary_to_bare, [escalus_utils:get_jid(User)]),
  805:     Host = escalus_utils:get_server(User),
  806:     Fun = fun() ->
  807:                   {ok, Services} = rpc(?RPC_SPEC, mod_event_pusher_push_backend, get_publish_services, [Host, JID]),
  808:                   lists:keymember(PushNode, 2, Services)
  809:           end,
  810:     mongoose_helper:wait_until(Fun, false),
  811: 
  812:     Fun2 = fun() ->
  813:                    Info = mongoose_helper:get_session_info(?RPC_SPEC, User),
  814:                    maps:get(?SESSION_KEY, Info, false)
  815:            end,
  816:     mongoose_helper:wait_until(Fun2, false).
  817: 
  818: no_push_notification_for_internal_mongoose_push_error(Config) ->
  819:     escalus:fresh_story(
  820:         Config, [{bob, 1}, {alice, 1}],
  821:         fun(Bob, Alice) ->
  822:             Response = {503, jiffy:encode(#{<<"reason">> => <<"unspecified">>})},
  823:             #{device_token := DeviceToken} =
  824:                 enable_push_and_become_unavailable(Bob, <<"fcm">>, [], Response, Config),
  825:             escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)),
  826:             {_, Response} = wait_for_push_request(DeviceToken)
  827: 
  828:         end).
  829: 
  830: 
  831: %%--------------------------------------------------------------------
  832: %% Test helpers
  833: %%--------------------------------------------------------------------
  834: 
  835: muclight_conversation(Sender, RoomJID, Msg) ->
  836:     Bare = bare_jid(Sender),
  837:     SenderJID = <<RoomJID/binary, "/", Bare/binary>>,
  838:     Stanza = escalus_stanza:groupchat_to(RoomJID, Msg),
  839:     escalus:send(Sender, Stanza),
  840:     SenderJID.
  841: 
  842: pm_conversation(Alice, Bob, Service, EnableOpts, Config) ->
  843:     AliceJID = bare_jid(Alice),
  844:     #{device_token := DeviceToken} =
  845:         enable_push_and_become_unavailable(Bob, Service, EnableOpts, Config),
  846:     escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"OH, HAI!">>)),
  847:     {AliceJID, DeviceToken}.
  848: 
  849: enable_push_and_become_unavailable(User, Service, EnableOpts, Config) ->
  850:     enable_push_and_become_unavailable(User, Service, EnableOpts, {200, <<"OK">>}, Config).
  851: 
  852: enable_push_and_become_unavailable(User, Service, EnableOpts, MockResponse, Config) ->
  853:     Ret = enable_push_for_user(User, Service, EnableOpts, MockResponse, Config),
  854:     become_unavailable(User),
  855:     Ret.
  856: 
  857: enable_push_for_user(User, Service, EnableOpts, Config) ->
  858:     enable_push_for_user(User, Service, EnableOpts, {200, <<"OK">>}, Config).
  859: 
  860: enable_push_for_user(User, Service, EnableOpts, MockResponse, Config) ->
  861:     Node = {PubsubJID, NodeName} = pubsub_node_from_host(Config),
  862: 
  863:     DeviceToken = gen_token(),
  864: 
  865:     case ?config(pubsub_host, Config) of
  866:         real ->
  867:             Configuration = [{<<"pubsub#access_model">>, <<"whitelist">>},
  868:                              {<<"pubsub#publish_model">>, <<"publishers">>}],
  869:             pubsub_tools:create_node(User, Node, [{type, <<"push">>},
  870:                                                   {config, Configuration}]),
  871:             add_user_server_to_whitelist(User, Node);
  872:         _ ->
  873:             skip
  874:     end,
  875: 
  876:     escalus:send(User, enable_stanza(PubsubJID, NodeName,
  877:                                      [{<<"service">>, Service},
  878:                                       {<<"device_id">>, DeviceToken}] ++ EnableOpts)),
  879:     escalus:assert(is_iq_result, escalus:wait_for_stanza(User)),
  880: 
  881:     assert_push_notification_in_session(User, NodeName, Service, DeviceToken),
  882: 
  883:     mongoose_push_mock:subscribe(DeviceToken, MockResponse),
  884:     #{device_token => DeviceToken,
  885:       pubsub_node => NodeName}.
  886: 
  887: add_user_server_to_whitelist(User, {NodeAddr, NodeName}) ->
  888:     AffList = [ #xmlel{ name = <<"affiliation">>,
  889:                         attrs = [{<<"jid">>, escalus_utils:get_server(User)},
  890:                                  {<<"affiliation">>, <<"publish-only">>}] }
  891:               ],
  892:     Affiliations = #xmlel{ name = <<"affiliations">>, attrs = [{<<"node">>, NodeName}],
  893:                            children = AffList },
  894:     Id = base64:encode(crypto:strong_rand_bytes(5)),
  895:     Stanza = escalus_pubsub_stanza:pubsub_owner_iq(<<"set">>, User, Id, NodeAddr, [Affiliations]),
  896:     escalus:send(User, Stanza),
  897:     escalus:assert(is_iq_result, [Stanza], escalus:wait_for_stanza(User)).
  898: 
  899: assert_push_notification_in_session(User, NodeName, Service, DeviceToken) ->
  900:     Info = mongoose_helper:get_session_info(?RPC_SPEC, User),
  901:     {_JID, NodeName, Details} = maps:get(?SESSION_KEY, Info),
  902:     ?assertMatch({<<"service">>, Service}, lists:keyfind(<<"service">>, 1, Details)),
  903:     ?assertMatch({<<"device_id">>, DeviceToken}, lists:keyfind(<<"device_id">>, 1, Details)).
  904: 
  905: wait_for_push_request(DeviceToken) ->
  906:     mongoose_push_mock:wait_for_push_request(DeviceToken, 10000).
  907: 
  908: wait_for_push_request(DeviceToken, Timeout) ->
  909:     mongoose_push_mock:wait_for_push_request(DeviceToken, Timeout).
  910: 
  911: %% ----------------------------------
  912: %% Other helpers
  913: %% ----------------------------------
  914: 
  915: fresh_room_name(Username) ->
  916:     escalus_utils:jid_to_lower(<<"room-", Username/binary>>).
  917: 
  918: fresh_room_name() ->
  919:     fresh_room_name(base16:encode(crypto:strong_rand_bytes(5))).
  920: 
  921: 
  922: bare_jid(JIDOrClient) ->
  923:     ShortJID = escalus_client:short_jid(JIDOrClient),
  924:     escalus_utils:jid_to_lower(ShortJID).
  925: 
  926: gen_token() ->
  927:     integer_to_binary(binary:decode_unsigned(crypto:strong_rand_bytes(16)), 24).
  928: 
  929: lower(Bin) when is_binary(Bin) ->
  930:     list_to_binary(string:to_lower(binary_to_list(Bin))).
  931: 
  932: pubsub_node_from_host(Config) ->
  933:     case ?config(pubsub_host, Config) of
  934:         virtual ->
  935:             pubsub_tools:pubsub_node_with_subdomain("virtual.");
  936:         real ->
  937:             pubsub_tools:pubsub_node()
  938:     end.
  939: 
  940: getenv(VarName, Default) ->
  941:     case os:getenv(VarName) of
  942:         false ->
  943:             Default;
  944:         Value ->
  945:             Value
  946:     end.
  947: 
  948: init_modules(G, Config) ->
  949:     MongoosePushAPI = mongoose_push_api_for_group(G),
  950:     PubSubHost = ?config(pubsub_host, Config),
  951:     Modules = required_modules_for_group(G, MongoosePushAPI, PubSubHost),
  952:     C = dynamic_modules:save_modules(domain(), Config),
  953:     Fun = fun() -> catch dynamic_modules:ensure_modules(domain(), Modules) end,
  954:     mongoose_helper:wait_until(Fun, ok),
  955:     [{api_v, MongoosePushAPI}, {required_modules, Modules} | C].
  956: 
  957: mongoose_push_api_for_group(failure_cases_v2) ->
  958:     <<"v2">>;
  959: mongoose_push_api_for_group(_) ->
  960:     <<"v3">>.
  961: 
  962: required_modules_for_group(pm_notifications_with_inbox, API, PubSubHost) ->
  963:     [{mod_inbox, inbox_opts()},
  964:      {mod_offline, config_parser_helper:mod_config(mod_offline, #{})} |
  965:      required_modules(API, PubSubHost)];
  966: required_modules_for_group(groupchat_notifications_with_inbox, API, PubSubHost)->
  967:     [{mod_inbox, inbox_opts()}, {mod_muc_light, muc_light_opts()}
  968:      | required_modules(API, PubSubHost)];
  969: required_modules_for_group(muclight_msg_notifications, API, PubSubHost) ->
  970:     [{mod_muc_light, muc_light_opts()} | required_modules(API, PubSubHost)];
  971: required_modules_for_group(integration_with_sm_and_offline_storage, API, PubSubHost) ->
  972:     [{mod_muc_light, muc_light_opts()},
  973:      {mod_stream_management, config_parser_helper:mod_config(mod_stream_management,
  974:                                                              #{ack_freq => never, resume_timeout => 1})},
  975:      {mod_offline, config_parser_helper:mod_config(mod_offline, #{})} |
  976:      required_modules(API, PubSubHost)];
  977: required_modules_for_group(enhanced_integration_with_sm, API, PubSubHost) ->
  978:     [{mod_stream_management, config_parser_helper:mod_config(mod_stream_management, #{ack_freq => never})} |
  979:      required_modules(API, PubSubHost, enhanced_plugin_module_opts())];
  980: required_modules_for_group(_, API, PubSubHost) ->
  981:     required_modules(API, PubSubHost).
  982: 
  983: required_modules(API, PubSubHost)->
  984:     required_modules(API, PubSubHost, #{}).
  985: 
  986: required_modules(API, PubSubHost, ExtraPushOpts) ->
  987:     PubSubHostOpts = virtual_pubsub_hosts_opts(PubSubHost),
  988:     PushOpts = maps:merge(ExtraPushOpts, PubSubHostOpts),
  989:     pubsub_modules(PubSubHost) ++ event_pusher_modules(API, PushOpts).
  990: 
  991: pubsub_modules(virtual) ->
  992:     [];
  993: pubsub_modules(real) ->
  994:     [{mod_pubsub, mod_config(mod_pubsub, #{plugins => [<<"dag">>, <<"push">>],
  995:                                            backend => mongoose_helper:mnesia_or_rdbms_backend(),
  996:                                            nodetree => nodetree_dag,
  997:                                            host => subhost_pattern("pubsub.@HOST@")})}].
  998: 
  999: event_pusher_modules(API, PushOpts) ->
 1000:     [{mod_push_service_mongoosepush, mod_config(mod_push_service_mongoosepush,
 1001:                                                 #{pool_name => mongoose_push_http,
 1002:                                                   api_version => API})},
 1003:      {mod_event_pusher, #{push => push_opts(PushOpts)}}].
 1004: 
 1005: virtual_pubsub_hosts_opts(virtual) ->
 1006:     #{virtual_pubsub_hosts => [subhost_pattern("virtual.@HOST@")]};
 1007: virtual_pubsub_hosts_opts(real) ->
 1008:     #{}.
 1009: 
 1010: push_opts(ExtraOpts) ->
 1011:     config([modules, mod_event_pusher, push],
 1012:            ExtraOpts#{backend => mongoose_helper:mnesia_or_rdbms_backend()}).
 1013: 
 1014: enhanced_plugin_module_opts() ->
 1015:     #{plugin_module => mod_event_pusher_push_plugin_enhanced}.
 1016: 
 1017: muc_light_opts() ->
 1018:     mod_config(mod_muc_light, #{backend => mongoose_helper:mnesia_or_rdbms_backend(),
 1019:                                 rooms_in_rosters => true}).
 1020: 
 1021: inbox_opts() ->
 1022:     (inbox_helper:inbox_opts())#{aff_changes := false}.