1: %%==============================================================================
    2: %% Copyright 2010 Erlang Solutions Ltd.
    3: %%
    4: %% Licensed under the Apache License, Version 2.0 (the "License");
    5: %% you may not use this file except in compliance with the License.
    6: %% You may obtain a copy of the License at
    7: %%
    8: %% http://www.apache.org/licenses/LICENSE-2.0
    9: %%
   10: %% Unless required by applicable law or agreed to in writing, software
   11: %% distributed under the License is distributed on an "AS IS" BASIS,
   12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13: %% See the License for the specific language governing permissions and
   14: %% limitations under the License.
   15: %%==============================================================================
   16: 
   17: -module(mod_blocking_SUITE).
   18: -compile([export_all, nowarn_export_all]).
   19: 
   20: -include_lib("exml/include/exml.hrl").
   21: -include_lib("escalus/include/escalus.hrl").
   22: -include_lib("common_test/include/ct.hrl").
   23: -include_lib("eunit/include/eunit.hrl").
   24: -include_lib("escalus/include/escalus_xmlns.hrl").
   25: 
   26: -import(config_parser_helper, [mod_config_with_auto_backend/1]).
   27: 
   28: -define(SLEEP_TIME, 50).
   29: 
   30: %%--------------------------------------------------------------------
   31: %% Suite configuration
   32: %%--------------------------------------------------------------------
   33: 
   34: all() ->
   35:     [
   36:         {group, manage},
   37:         {group, effect},
   38:         {group, offline},
   39:         {group, errors},
   40:         {group, pushes}
   41:     ].
   42: 
   43: groups() ->
   44:     [
   45:          {manage, [parallel], manage_test_cases()},
   46:          {effect, [parallel], effect_test_cases()},
   47:          {offline, [sequence], offline_test_cases()},
   48:          {errors, [parallel], error_test_cases()},
   49:          {pushes, [parallel], push_test_cases()},
   50:          {notify, [parallel], notify_test_cases()}
   51:     ].
   52: 
   53: manage_test_cases() ->
   54:     [
   55:         discovering_support,
   56:         get_block_list,
   57:         add_user_to_blocklist,
   58:         add_user_to_blocklist_with_white_spaces,
   59:         add_another_user_to_blocklist,
   60:         add_many_users_to_blocklist,
   61:         remove_user_from_blocklist,
   62:         remove_many_user_from_blocklist,
   63:         clear_blocklist,
   64:         invalid_block_request
   65:     ].
   66: 
   67: effect_test_cases() ->
   68:     [
   69:         messages_from_blocked_user_dont_arrive,
   70:         messages_from_unblocked_user_arrive_again,
   71:         messages_from_any_blocked_resource_dont_arrive,
   72:         blocking_doesnt_interfere,
   73:         blocking_propagates_to_resources,
   74:         iq_reply_doesnt_crash_user_process
   75:     ].
   76: 
   77: offline_test_cases() ->
   78:     [
   79:         messages_after_relogin,
   80:         messages_arrive_after_unblock_and_relogin,
   81:         blocking_and_relogin_many,
   82:         clear_list_relogin
   83:     ].
   84: 
   85: error_test_cases() ->
   86:     [blocker_cant_send_to_blockee].
   87: 
   88: push_test_cases() ->
   89:     [block_push_sent].
   90: 
   91: notify_test_cases() ->
   92:     [notify_blockee].
   93: 
   94: suite() ->
   95:     escalus:suite().
   96: 
   97: %%--------------------------------------------------------------------
   98: %% Init & teardown
   99: %%--------------------------------------------------------------------
  100: 
  101: init_per_suite(Config0) ->
  102:     HostType = domain_helper:host_type(),
  103:     Config1 = dynamic_modules:save_modules(HostType, Config0),
  104:     ModConfig = [{mod_blocking, mod_config_with_auto_backend(mod_blocking)}],
  105:     dynamic_modules:ensure_modules(HostType, ModConfig),
  106:     escalus:init_per_suite(Config1).
  107: 
  108: end_per_suite(Config) ->
  109:     escalus_fresh:clean(),
  110:     dynamic_modules:restore_modules(Config),
  111:     escalus:end_per_suite(Config).
  112: 
  113: init_per_group(_GroupName, Config) ->
  114:     escalus_fresh:create_users(Config, escalus:get_users([alice, bob, kate, mike, john])).
  115: 
  116: end_per_group(_GroupName, Config) ->
  117:     Config.
  118: 
  119: init_per_testcase(CaseName, Config) ->
  120:     escalus:init_per_testcase(CaseName, Config).
  121: 
  122: end_per_testcase(CaseName, Config) ->
  123:     escalus:end_per_testcase(CaseName, Config).
  124: 
  125: %%--------------------------------------------------------------------
  126: %% Tests
  127: %%--------------------------------------------------------------------
  128: 
  129: discovering_support(Config) ->
  130:     escalus:fresh_story(
  131:         Config, [{alice, 1}],
  132:         fun(User1) ->
  133:             Server = escalus_client:server(User1),
  134:             IqGet = escalus_stanza:disco_info(Server),
  135:             escalus_client:send(User1, IqGet),
  136:             Result = escalus_client:wait_for_stanza(User1),
  137:             escalus:assert(is_iq_result, [IqGet], Result),
  138:             escalus:assert(has_feature, [?NS_BLOCKING], Result)
  139:         end).
  140: 
  141: 
  142: get_block_list(Config) ->
  143:     escalus:fresh_story(
  144:         Config, [{alice, 1}],
  145:         fun(User1) ->
  146:             Result = get_blocklist(User1),
  147:             escalus:assert(is_iq_result, Result),
  148:             escalus:assert(fun is_blocklist_result_empty/1, Result)
  149:         end).
  150: 
  151: 
  152: add_user_to_blocklist(Config) ->
  153:     escalus:fresh_story(
  154:         Config, [{alice, 1}, {bob, 1}],
  155:         fun(User1, User2) ->
  156:             user_blocks(User1, [User2]),
  157:             BlockList = get_blocklist(User1),
  158:             blocklist_contains_jid(BlockList, User2)
  159:         end).
  160: 
  161: add_user_to_blocklist_with_white_spaces(Config) ->
  162:     escalus:fresh_story(
  163:         Config, [{alice, 1}, {bob, 1}],
  164:         fun(User1, User2) ->
  165:             BlockeeJIDs = [escalus_utils:jid_to_lower(escalus_client:short_jid(B)) || B <- [User2]],
  166:             AddStanza = block_users_stanza_with_white_spaces(BlockeeJIDs),
  167:             escalus_client:send(User1, AddStanza),
  168:             Res = escalus:wait_for_stanza(User1),
  169:             escalus:assert(is_iq_result, Res)
  170:         end).
  171: 
  172: add_another_user_to_blocklist(Config) ->
  173:     escalus:fresh_story(
  174:         Config, [{alice, 1}, {mike, 1}],
  175:         fun(User1, User2) ->
  176:             user_blocks(User1, [User2]),
  177:             BlockList = get_blocklist(User1),
  178:             blocklist_contains_jid(BlockList, User2)
  179:         end).
  180: 
  181: add_many_users_to_blocklist(Config) ->
  182:     escalus:fresh_story(
  183:         Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  184:         fun(User1, User2, User3, User4) ->
  185:             user_blocks(User1, [User2, User3, User4]),
  186:             BlockList = get_blocklist(User1),
  187:             blocklist_contains_jid(BlockList, User2),
  188:             blocklist_contains_jid(BlockList, User3),
  189:             blocklist_contains_jid(BlockList, User4)
  190:         end).
  191: 
  192: remove_user_from_blocklist(Config) ->
  193:     escalus:fresh_story(
  194:         Config, [{alice, 1}, {bob, 1}],
  195:         fun(User1, User2) ->
  196:             user_blocks(User1, [User2]),
  197:             user_unblocks(User1, User2),
  198:             NewList = get_blocklist(User1),
  199:             blocklist_doesnt_contain_jid(NewList, User2)
  200:         end).
  201: 
  202: remove_many_user_from_blocklist(Config) ->
  203:     escalus:fresh_story(
  204:         Config, [{alice, 1}, {bob, 1}, {kate, 1}],
  205:         fun(User1, User2, User3) ->
  206:             user_blocks(User1, [User2, User3]),
  207:             user_unblocks(User1, [User2, User3]),
  208:             NewList = get_blocklist(User1),
  209:             blocklist_doesnt_contain_jid(NewList, User2),
  210:             blocklist_doesnt_contain_jid(NewList, User3)
  211:         end).
  212: 
  213: clear_blocklist(Config) ->
  214:     escalus:fresh_story(
  215:         Config, [{alice, 1}, {bob, 1}, {kate, 1}],
  216:         fun(User1, User2, User3) ->
  217:             user_blocks(User1, [User2, User3]),
  218:             user_unblocks_all(User1),
  219:             NewList = get_blocklist(User1),
  220:             blocklist_is_empty(NewList)
  221:         end).
  222: 
  223: invalid_block_request(Config) ->
  224:     escalus:fresh_story(
  225:         Config, [{alice, 1}],
  226:         fun(User1) ->
  227:             St = block_users_stanza([]),
  228:             escalus_client:send(User1, St),
  229:             privacy_helper:gets_error(User1, <<"modify">>, <<"bad-request">>)
  230:         end).
  231: 
  232: messages_from_blocked_user_dont_arrive(Config) ->
  233:     escalus:fresh_story(
  234:         Config, [{alice, 1}, {bob, 1}],
  235:         fun(User1, User2) ->
  236:             user_blocks(User1, [User2]),
  237:             message(User2, User1, <<"Hi!">>),
  238:             ct:sleep(100),
  239:             escalus_assert:has_no_stanzas(User1),
  240:             privacy_helper:gets_error(User2, <<"cancel">>, <<"service-unavailable">>)
  241:         end).
  242: 
  243: messages_from_unblocked_user_arrive_again(Config) ->
  244:     escalus:fresh_story(
  245:         Config, [{alice, 1}, {bob, 1}],
  246:         fun(User1, User2) ->
  247:             %% given
  248:             user_blocks(User1, [User2]),
  249:             %% when
  250:             user_unblocks(User1, User2),
  251:             %% then
  252:             message_is_delivered(User2, User1, <<"Hello again!">>)
  253:         end).
  254: 
  255: messages_from_any_blocked_resource_dont_arrive(Config) ->
  256:     escalus:fresh_story(
  257:         Config, [{alice, 3}, {bob, 1}],
  258:         fun(User1a, User1b, User1c, User2) ->
  259:             %% given
  260:             user_blocks(User2, [User1a]),
  261:             %% then
  262:             message_is_not_delivered(User1a, [User2], <<"roar!">>),
  263:             privacy_helper:gets_error(User1a, <<"cancel">>, <<"service-unavailable">>),
  264:             message_is_not_delivered(User1b, [User2], <<"woof!">>),
  265:             privacy_helper:gets_error(User1b, <<"cancel">>, <<"service-unavailable">>),
  266:             message_is_not_delivered(User1c, [User2], <<"grrr!">>),
  267:             privacy_helper:gets_error(User1c, <<"cancel">>, <<"service-unavailable">>),
  268:             ct:sleep(100),
  269:             escalus_assert:has_no_stanzas(User2)
  270:         end).
  271: 
  272: blocking_doesnt_interfere(Config) ->
  273:     escalus:fresh_story(
  274:         Config, [{alice, 1}, {bob, 1}, {kate, 1}],
  275:         fun(User1, User2, User3) ->
  276:             %% given
  277:             user_blocks(User1, [User2]),
  278:             %% then
  279:             message_is_not_delivered(User2, [User1], <<"!@#@$@#$%">>),
  280:             privacy_helper:gets_error(User2, <<"cancel">>, <<"service-unavailable">>),
  281:             message_is_delivered(User3, [User1], <<"Ni hao.">>)
  282:         end).
  283: 
  284: blocking_propagates_to_resources(Config) ->
  285:     escalus:fresh_story(
  286:         Config, [{alice, 2}, {bob, 1}],
  287:         fun(User1a, User1b, User2) ->
  288:             %% given
  289:             user_blocks(User1a, [User2]),
  290:             %% then
  291:             client_gets_block_iq(User1b),
  292:             % Alice can't send from any of her resources
  293:             message_is_not_delivered(User1a, [User2], <<"roar!">>),
  294:             client_gets_blocking_error(User1a),
  295:             message_is_not_delivered(User1b, [User2], <<"woof!">>),
  296:             client_gets_blocking_error(User1b),
  297:             % Bob can't send to any of Alice's resources
  298:             message_is_not_delivered(User2, [User1a], <<"hau!">>),
  299:             privacy_helper:gets_error(User2, <<"cancel">>, <<"service-unavailable">>),
  300:             message_is_not_delivered(User2, [User1b], <<"miau!">>),
  301:             privacy_helper:gets_error(User2, <<"cancel">>, <<"service-unavailable">>)
  302:         end).
  303: 
  304: iq_reply_doesnt_crash_user_process(Config) ->
  305:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  306: 
  307:         QueryWithBlockingNS = escalus_stanza:query_el(?NS_BLOCKING, []),
  308:         %% Send IQ reply with blocking ns
  309:         %% Send message to check user process still alive
  310:         privacy_helper:does_user_process_crash(Alice,
  311:             Bob,
  312:             <<"error">>,
  313:             QueryWithBlockingNS,
  314:             <<"Hello, Bob">>),
  315: 
  316:         privacy_helper:does_user_process_crash(Bob,
  317:             Alice,
  318:             <<"result">>,
  319:             QueryWithBlockingNS,
  320:             <<"Hello, Alice">>)
  321:     end).
  322: 
  323: messages_after_relogin(Config) ->
  324:     %% given
  325:     escalus:story(
  326:         Config, [{alice, 1}, {bob, 1}],
  327:         fun(User1, User2) ->
  328:             user_blocks(User1, [User2])
  329:         end),
  330:     %% XXX Because alice can receive presence unavalable from alice
  331:     %% XXX It's a potential bug, please test it.
  332:     %% XXX has_stanzas_but_shouldnt
  333:     %% XXX reported as https://github.com/esl/MongooseIM/issues/1799
  334:     mongoose_helper:kick_everyone(),
  335:     escalus:story(
  336:         Config, [{alice, 1}, {bob, 1}],
  337:         fun(User1, User2) ->
  338:             message_is_not_delivered(User2, [User1], <<"Hey alice, are you there?">>),
  339:             message_is_delivered(User1, [User1], <<"Hey bob, carpe diem!">>)
  340:         end).
  341: 
  342: messages_arrive_after_unblock_and_relogin(Config) ->
  343:     %% given when
  344:     escalus:story(
  345:         Config, [{alice, 1}, {bob, 1}],
  346:         fun(User1, User2) ->
  347:             user_unblocks(User1, [User2])
  348:         end),
  349:     %% then
  350:     escalus:story(
  351:         Config, [{alice, 1}, {bob, 1}],
  352:         fun(User1, User2) ->
  353:             message_is_delivered(User2, [User1], <<"Hey bob, are you there?">>)
  354:         end).
  355: 
  356: blocking_and_relogin_many(Config) ->
  357:     %% given
  358:     simple_story(Config,
  359:         fun(User1, User2, User3, User4, _) ->
  360:             user_blocks(User1, [User2, User3]),
  361:             user_blocks(User1, [User3, User4])
  362:         end),
  363:     %% when
  364:     simple_story(Config,
  365:         fun(User1, User2, _, User4, User5) ->
  366:             user_unblocks(User1,  [User4]),
  367:             user_unblocks(User1,  [User4, User5]),
  368:             user_unblocks(User1,  [User2, User5])
  369:         end),
  370:     %% then
  371:     simple_story(Config,
  372:         fun(User1, User2, User3, User4, _) ->
  373:             message_is_delivered(User1, [User4], <<"Under the bridge!">>),
  374:             message_is_not_delivered(User1, [User3], <<"Cant stop">>),
  375:             client_gets_blocking_error(User1),
  376:             message_is_delivered(User1, [User2], <<"House of th rising sun">>),
  377:             BlockList = get_blocklist(User1),
  378:             blocklist_contains_jid(BlockList, User3)
  379:         end).
  380: 
  381: simple_story(Config, Fun) ->
  382:     escalus:story(
  383:         Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}, {john, 1}],
  384:         Fun
  385:     ).
  386: 
  387: clear_list_relogin(Config) ->
  388:     %% unexpected presence unavailable
  389:     mongoose_helper:kick_everyone(),
  390:     escalus:story(
  391:         Config, [{alice, 1}, {bob, 1}],
  392:         fun(User1, User2) ->
  393:             user_blocks(User1, [User2])
  394:         end),
  395:     escalus:story(
  396:         Config, [{alice, 1}],
  397:         fun(User1) ->
  398:             user_unblocks_all(User1)
  399:         end),
  400:     escalus:story(
  401:         Config, [{alice, 1}, {bob, 1}],
  402:         fun(User1, User2) ->
  403:             message_is_delivered(User1, [User2], <<"Doom and gloom!">>)
  404:         end).
  405: 
  406: blocker_cant_send_to_blockee(Config) ->
  407:     escalus:fresh_story(
  408:         Config, [{alice, 1}, {bob, 1}],
  409:         fun(User1, User2) ->
  410:             user_blocks(User1, [User2]),
  411:             message(User1, User2, <<"I'm not talking to you!">>),
  412:             client_gets_blocking_error(User1)
  413:         end).
  414: 
  415: block_push_sent(Config) ->
  416:     %% make sure privacy list push arrives to all the user's resources
  417:     escalus:fresh_story(
  418:         Config, [{alice, 2}, {bob, 2}],
  419:         fun(User1a, User1b, User2a, _User2b) ->
  420:             user_blocks(User1a, [User2a]),
  421:             client_gets_block_iq(User1b)
  422:         end).
  423: 
  424: notify_blockee(Config) ->
  425:     %% as XEP-0191 says, when we block a user he should receive 'unavailable', and a contrario.
  426:     escalus:fresh_story(
  427:         Config, [{alice, 1}, {bob, 1}],
  428:         fun(Alice, Bob) ->
  429:             %% make sure they're friends and Bob receives Alice's presences
  430:             subscribe(Bob, Alice),
  431:             escalus:send(Alice, escalus_stanza:presence(<<"available">>)),
  432:             escalus:assert(is_presence_with_type, [<<"available">>],
  433:                            escalus:wait_for_stanza(Alice)),
  434:             escalus:assert(is_presence_with_type, [<<"available">>],
  435:                            escalus:wait_for_stanza(Bob)),
  436:             user_blocks(Alice, [Bob]),
  437:             escalus:assert(is_presence_with_type, [<<"unavailable">>],
  438:                            escalus:wait_for_stanza(Bob)),
  439:             user_unblocks(Alice, [Bob]),
  440:             escalus:assert(is_presence_with_type, [<<"available">>],
  441:                            escalus:wait_for_stanza(Bob))
  442:         end).
  443: 
  444: %% common
  445: 
  446: %%
  447: get_blocklist(User) ->
  448:     IQGet = get_blocklist_stanza(),
  449:     escalus_client:send(User, IQGet),
  450:     escalus_client:wait_for_stanza(User).
  451: 
  452: %%
  453: %% stanza generators
  454: %%
  455: 
  456: get_blocklist_stanza() ->
  457:     Payload = #xmlel{name = <<"blocklist">>,
  458:         attrs=[{<<"xmlns">>, ?NS_BLOCKING}]},
  459:     #xmlel{name = <<"iq">>,
  460:         attrs = [{<<"type">>, <<"get">>}],
  461:         children = [Payload]}.
  462: 
  463: block_users_stanza(UsersToBlock) ->
  464:     Childs = [item_el(U) || U <- UsersToBlock],
  465:     Payload = #xmlel{name = <<"block">>,
  466:         attrs=[{<<"xmlns">>, ?NS_BLOCKING}],
  467:         children = Childs
  468:     },
  469:     #xmlel{name = <<"iq">>,
  470:         attrs = [{<<"type">>, <<"set">>}],
  471:         children = [Payload]}.
  472: 
  473: block_users_stanza_with_white_spaces(UsersToBlock) ->
  474:     Childs = [item_el(U) || U <- UsersToBlock],
  475:     % when client adds some white characters in blocking list
  476:     WhiteSpacedChilds = Childs ++ [{xmlcdata, "\n"}],
  477:     Payload = #xmlel{name = <<"block">>,
  478:         attrs=[{<<"xmlns">>, ?NS_BLOCKING}],
  479:         children = WhiteSpacedChilds
  480:     },
  481:     #xmlel{name = <<"iq">>,
  482:         attrs = [{<<"type">>, <<"set">>}],
  483:         children = [Payload]}.
  484: 
  485: 
  486: %%block_user_stanza(UserToBlock) ->
  487: %%    Payload = #xmlel{name = <<"block">>,
  488: %%        attrs=[{<<"xmlns">>, ?NS_BLOCKING}],
  489: %%        children = [item_el(UserToBlock)]
  490: %%    },
  491: %%    #xmlel{name = <<"iq">>,
  492: %%        attrs = [{<<"type">>, <<"set">>}],
  493: %%        children = Payload}.
  494: 
  495: unblock_user_stanza(UserToUnblock) ->
  496:     Payload = #xmlel{name = <<"unblock">>,
  497:         attrs=[{<<"xmlns">>, ?NS_BLOCKING}],
  498:         children = [item_el(UserToUnblock)]
  499:     },
  500:     #xmlel{name = <<"iq">>,
  501:         attrs = [{<<"type">>, <<"set">>}],
  502:         children = [Payload]}.
  503: 
  504: unblock_users_stanza(UsersToBlock) ->
  505:     Childs = [item_el(U) || U <- UsersToBlock],
  506:     Payload = #xmlel{name = <<"unblock">>,
  507:         attrs=[{<<"xmlns">>, ?NS_BLOCKING}],
  508:         children = Childs
  509:     },
  510:     #xmlel{name = <<"iq">>,
  511:         attrs = [{<<"type">>, <<"set">>}],
  512:         children = [Payload]}.
  513: 
  514: unblock_all_stanza() ->
  515:     Payload = #xmlel{name = <<"unblock">>,
  516:         attrs=[{<<"xmlns">>, ?NS_BLOCKING}],
  517:         children = []
  518:     },
  519:     #xmlel{name = <<"iq">>,
  520:         attrs = [{<<"type">>, <<"set">>}],
  521:         children = [Payload]}.
  522: 
  523: item_el(User) when is_binary(User) ->
  524:     #xmlel{name = <<"item">>,
  525:         attrs = [{<<"jid">>, User}]}.
  526: %%
  527: %% predicates
  528: %%
  529: 
  530: is_xep191_not_available(#xmlel{} = Stanza) ->
  531:     ErrorEl = exml_query:subelement(Stanza, <<"error">>),
  532:     <<"error">> == exml_query:attr(Stanza, <<"type">>)
  533:         andalso
  534:         undefined =/= exml_query:subelement(ErrorEl, <<"not-acceptable">>)
  535:         andalso
  536:         undefined =/= exml_query:subelement(ErrorEl, <<"blocked">>)
  537:         andalso
  538:         <<"urn:xmpp:blocking:errors">> ==
  539:             exml_query:path(ErrorEl, [{element, <<"blocked">>},
  540:                 {attr, <<"xmlns">>}]).
  541: 
  542: 
  543: is_blocklist_result_empty(#xmlel{children = [#xmlel{name =Name,
  544:     attrs = Attrs,
  545:     children= Child}]} = Stanza) ->
  546:     true = escalus_pred:is_iq(Stanza),
  547:     <<"blocklist">> = Name,
  548:     {<<"xmlns">>, ?NS_BLOCKING} = lists:keyfind(<<"xmlns">>, 1, Attrs),
  549:     [] = Child,
  550:     true.
  551: 
  552: blocklist_result_has(ExpectedUser, Stanza) ->
  553:     true = escalus_pred:is_iq(Stanza),
  554:     Blocklist = hd(Stanza#xmlel.children),
  555:     Attrs = Blocklist#xmlel.attrs,
  556:     Children = Blocklist#xmlel.children,
  557:     <<"blocklist">> = Blocklist#xmlel.name,
  558:     {<<"xmlns">>, ?NS_BLOCKING} = lists:keyfind(<<"xmlns">>, 1, Attrs),
  559:     true == lists:member(ExpectedUser, get_blocklist_items(Children)).
  560: 
  561: is_xep191_push(Type, #xmlel{attrs = A, children = [#xmlel{name = Type,
  562:     attrs = Attrs}]}=Stanza) ->
  563:     true = escalus_pred:is_iq_set(Stanza),
  564:     {<<"id">>, <<"push">>} = lists:keyfind(<<"id">>, 1, A),
  565:     {<<"xmlns">>, ?NS_BLOCKING} = lists:keyfind(<<"xmlns">>, 1, Attrs),
  566:     true.
  567: 
  568: is_xep191_push(Type, [], #xmlel{children = [#xmlel{name = Type, children = []}]}=Stanza) ->
  569:     is_xep191_push(Type, Stanza);
  570: is_xep191_push(Type, [], #xmlel{children = [#xmlel{name = Type, children = _}]}) ->
  571:     false;
  572: is_xep191_push(Type, JIDs, #xmlel{attrs = _, children = [#xmlel{name = Type,
  573:     attrs = Attrs, children = Items}]}=Stanza) ->
  574:     true = escalus_pred:is_iq_set(Stanza),
  575:     {<<"xmlns">>, ?NS_BLOCKING} = lists:keyfind(<<"xmlns">>, 1, Attrs),
  576:     F = fun(El) ->
  577:         #xmlel{name = <<"item">>, attrs =  [{<<"jid">>, Value}]} = El,
  578:         lists:member(Value, JIDs)
  579:         end,
  580:     TrueList = lists:map(F, Items),
  581:     lists:all(fun(El) -> El end, TrueList);
  582: is_xep191_push(_, _, _) ->
  583:     false.
  584: 
  585: %%
  586: %% helpers
  587: %%
  588: 
  589: bare(C) ->  escalus_utils:jid_to_lower(escalus_client:short_jid(C)).
  590: 
  591: get_blocklist_items(Items) ->
  592:     lists:map(fun(#xmlel{name = <<"item">>, attrs=A}) ->
  593:         {_, R} = lists:keyfind(<<"jid">>, 1, A),
  594:         R
  595:               end, Items).
  596: 
  597: user_blocks(Blocker, Blockees) when is_list(Blockees) ->
  598:     BlockeeJIDs = [ escalus_utils:jid_to_lower(escalus_client:short_jid(B)) || B <- Blockees ],
  599:     AddStanza = block_users_stanza(BlockeeJIDs),
  600:     escalus_client:send(Blocker, AddStanza),
  601:     Res = escalus:wait_for_stanzas(Blocker, 2),
  602:     CheckPush = fun(E) -> is_xep191_push(<<"block">>, BlockeeJIDs, E) end,
  603:     Preds = [is_iq_result, CheckPush],
  604:     escalus:assert_many(Preds, Res).
  605: 
  606: blocklist_is_empty(BlockList) ->
  607:     escalus:assert(is_iq_result, BlockList),
  608:     escalus:assert(fun is_blocklist_result_empty/1, BlockList).
  609: 
  610: blocklist_contains_jid(BlockList, Client) ->
  611:     JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Client)),
  612:     escalus:assert(fun blocklist_result_has/2, [JID], BlockList).
  613: 
  614: user_unblocks(Unblocker, Unblockees) when is_list(Unblockees) ->
  615:     UnblockeeJIDs = [ escalus_utils:jid_to_lower(escalus_client:short_jid(B)) || B <- Unblockees ],
  616:     AddStanza = unblock_users_stanza(UnblockeeJIDs),
  617:     escalus_client:send(Unblocker, AddStanza),
  618:     Res = escalus:wait_for_stanzas(Unblocker, 2),
  619:     CheckPush = fun(E) -> is_xep191_push(<<"unblock">>, UnblockeeJIDs, E) end,
  620:     Preds = [is_iq_result, CheckPush],
  621:     escalus:assert_many(Preds, Res);
  622: user_unblocks(Unblocker, Unblockee) ->
  623:     JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Unblockee)),
  624:     escalus_client:send(Unblocker, unblock_user_stanza(JID)),
  625:     user_gets_remove_result(Unblocker, [JID]).
  626: 
  627: blocklist_doesnt_contain_jid(BlockList, Client) ->
  628:     JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Client)),
  629:     escalus:assert(is_iq_result, BlockList),
  630:     ?assertNot(blocklist_result_has(JID, BlockList)).
  631: 
  632: user_gets_remove_result(Client, ContactList) ->
  633:     RemoveResult = escalus:wait_for_stanzas(Client, 2),
  634:     CheckPush = fun(E) -> is_xep191_push(<<"unblock">>, ContactList, E) end,
  635:     Preds = [is_iq_result, CheckPush],
  636:     escalus:assert_many(Preds, RemoveResult).
  637: 
  638: 
  639: user_unblocks_all(User) ->
  640:     escalus_client:send(User, unblock_all_stanza()),
  641:     user_gets_remove_result(User, []).
  642: 
  643: message(From, To, MsgTxt) ->
  644:     escalus_client:send(From, escalus_stanza:chat_to(To, MsgTxt)).
  645: 
  646: message_is_delivered(From, [To|_] = Tos, MessageText) ->
  647:     BareTo = escalus_utils:jid_to_lower(escalus_client:short_jid(To)),
  648:     escalus:send(From, escalus_stanza:chat_to(BareTo, MessageText)),
  649:     [ escalus:assert(is_chat_message, [MessageText], escalus:wait_for_stanza(C)) ||
  650:         C <- Tos ];
  651: message_is_delivered(From, To, MessageText) ->
  652:     BareTo =  escalus_utils:jid_to_lower(escalus_client:short_jid(To)),
  653:     escalus:send(From, escalus_stanza:chat_to(BareTo, MessageText)),
  654:     escalus:assert(is_chat_message, [MessageText], escalus:wait_for_stanza(To)).
  655: 
  656: message_is_not_delivered(From, [To|_] = Tos, MessageText) ->
  657:     BareTo = escalus_utils:jid_to_lower(escalus_client:short_jid(To)),
  658:     escalus:send(From, escalus_stanza:chat_to(BareTo, MessageText)),
  659:     clients_have_no_messages(Tos).
  660: 
  661: clients_have_no_messages(Cs) when is_list (Cs) -> [ client_has_no_messages(C) || C <- Cs ].
  662: 
  663: client_has_no_messages(C) -> escalus_assert:has_no_stanzas(C).
  664: 
  665: client_gets_blocking_error(C) ->
  666:     Stanza = escalus_client:wait_for_stanza(C),
  667:     escalus:assert(fun is_xep191_not_available/1, [], Stanza).
  668: 
  669: client_gets_block_iq(C) ->
  670:     escalus:assert(fun is_xep191_push/2, [<<"block">>], escalus:wait_for_stanza(C)).
  671: 
  672: flush(User) ->
  673:     escalus:wait_for_stanzas(User, 10, 100).
  674: 
  675: add_sample_contact(Alice, Bob) ->
  676:     escalus:send(Alice, escalus_stanza:roster_add_contact(Bob,
  677:         [<<"friends">>],
  678:         <<"Bobby">>)),
  679: 
  680:     Received = escalus:wait_for_stanzas(Alice, 2),
  681:     escalus:assert_many([is_roster_set, is_iq_result], Received),
  682: 
  683:     Result = hd([R || R <- Received, escalus_pred:is_roster_set(R)]),
  684:     escalus:assert(count_roster_items, [1], Result),
  685:     escalus:send(Alice, escalus_stanza:iq_result(Result)).
  686: 
  687: 
  688: subscribe(Bob, Alice) ->
  689:     %% Bob adds Alice as a contact
  690:     add_sample_contact(Bob, Alice),
  691:     %% He subscribes to her presences
  692:     escalus:send(Bob, escalus_stanza:presence_direct(alice, <<"subscribe">>)),
  693:     PushReq = escalus:wait_for_stanza(Bob),
  694:     escalus:assert(is_roster_set, PushReq),
  695:     escalus:send(Bob, escalus_stanza:iq_result(PushReq)),
  696:     %% Alice receives subscription request
  697:     Received = escalus:wait_for_stanza(Alice),
  698:     escalus:assert(is_presence_with_type, [<<"subscribe">>], Received),
  699:     %% Alice adds new contact to his roster
  700:     escalus:send(Alice, escalus_stanza:roster_add_contact(Bob,
  701:         [<<"enemies">>],
  702:         <<"Bob">>)),
  703:     PushReqB = escalus:wait_for_stanza(Alice),
  704:     escalus:assert(is_roster_set, PushReqB),
  705:     escalus:send(Alice, escalus_stanza:iq_result(PushReqB)),
  706:     escalus:assert(is_iq_result, escalus:wait_for_stanza(Alice)),
  707:     %% Alice sends subscribed presence
  708:     escalus:send(Alice, escalus_stanza:presence_direct(bob, <<"subscribed">>)),
  709:     %% Bob receives subscribed
  710:     Stanzas = escalus:wait_for_stanzas(Bob, 2),
  711:     check_subscription_stanzas(Stanzas, <<"subscribed">>),
  712:     escalus:assert(is_presence, escalus:wait_for_stanza(Bob)),
  713:     %% Alice receives roster push
  714:     PushReqB1 = escalus:wait_for_stanza(Alice),
  715:     escalus:assert(is_roster_set, PushReqB1),
  716:     %% Alice sends presence
  717:     escalus:send(Alice, escalus_stanza:presence(<<"available">>)),
  718:     escalus:assert(is_presence, escalus:wait_for_stanza(Bob)),
  719:     escalus:assert(is_presence, escalus:wait_for_stanza(Alice)),
  720:     ok.
  721: 
  722: 
  723: check_subscription_stanzas(Stanzas, Type) ->
  724:     IsPresWithType = fun(S) -> escalus_pred:is_presence_with_type(Type, S) end,
  725:     escalus:assert_many([is_roster_set, IsPresWithType], Stanzas).