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