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