1: -module(inbox_extensions_SUITE).
    2: 
    3: -include_lib("escalus/include/escalus.hrl").
    4: -include_lib("escalus/include/escalus_xmlns.hrl").
    5: -include_lib("common_test/include/ct.hrl").
    6: -include_lib("exml/include/exml.hrl").
    7: -include_lib("eunit/include/eunit.hrl").
    8: -include("inbox.hrl").
    9: 
   10: -define(ROOM_MARKERS_RESET, <<"room_markers_reset">>).
   11: -define(HOUR, 3600).
   12: -define(VALID_JID, <<"mike@localhost">>).
   13: -define(INVALID_JID, <<"$@/">>).
   14: -define(INVALID_VALUE, <<"invalid">>).
   15: 
   16: -export([all/0,
   17:          groups/0,
   18:          suite/0,
   19:          init_per_suite/1,
   20:          end_per_suite/1,
   21:          init_per_group/2,
   22:          end_per_group/2,
   23:          init_per_testcase/2,
   24:          end_per_testcase/2]).
   25: 
   26: %% ERRORS
   27: -export([
   28:          % General
   29:          returns_error_when_no_jid_provided/1,
   30:          returns_error_when_invalid_jid_provided/1,
   31:          returns_error_when_valid_jid_but_no_property/1,
   32:          % Set-unread errors
   33:          returns_error_when_read_invalid_value/1,
   34:          returns_error_when_read_valid_request_but_not_in_inbox/1,
   35:          % Archiving errors
   36:          returns_error_when_archive_invalid_value/1,
   37:          returns_error_when_archive_valid_request_but_not_in_inbox/1,
   38:          % Muting errors
   39:          returns_error_when_mute_invalid_value/1,
   40:          returns_error_when_mute_invalid_seconds/1,
   41:          returns_error_when_mute_valid_request_but_not_in_inbox/1,
   42:          % Form errors
   43:          returns_error_when_archive_field_is_invalid/1,
   44:          returns_error_when_max_is_not_a_number/1
   45:         ]).
   46: %% SUCCESSES
   47: -export([
   48:          % read
   49:          read_unread_entry_set_to_read/1,
   50:          read_unread_entry_set_to_read_iq_id_as_fallback/1,
   51:          read_unread_entry_set_to_read_queryid/1,
   52:          read_read_entry_set_to_unread/1,
   53:          read_unread_entry_with_two_messages_when_set_unread_then_unread_count_stays_in_two/1,
   54:          % archive
   55:          archive_active_entry_gets_archived/1,
   56:          archive_archived_entry_gets_active_on_request/1,
   57:          archive_archived_entry_gets_active_for_the_sender_on_new_message/1,
   58:          archive_archived_entry_gets_active_for_the_receiver_on_new_message/1,
   59:          archive_active_unread_entry_gets_archived_and_still_unread/1,
   60:          archive_full_archive_can_be_fetched/1,
   61:          archive_full_archive_can_be_fetched_queryid/1,
   62:          % mute
   63:          mute_unmuted_entry_gets_muted/1,
   64:          mute_muted_entry_gets_unmuted/1,
   65:          mute_after_timestamp_gets_unmuted/1,
   66:          mute_muted_conv_restarts_timestamp/1,
   67:          % other
   68:          returns_valid_properties_form/1,
   69:          properties_can_be_get/1,
   70:          properties_many_can_be_set/1,
   71:          properties_many_can_be_set_queryid/1,
   72:          max_queries_can_be_limited/1,
   73:          max_queries_can_fetch_ahead/1,
   74:          timestamp_is_not_reset_with_setting_properties/1
   75:         ]).
   76: %% Groupchats
   77: -export([
   78:          groupchat_setunread_stanza_sets_inbox/1 % muclight
   79:         ]).
   80: 
   81: 
   82: all() ->
   83:     inbox_helper:skip_or_run_inbox_tests(tests()).
   84: 
   85: tests() ->
   86:     [
   87:      {group, regular},
   88:      {group, async_pools}
   89:     ].
   90: 
   91: suite() ->
   92:     escalus:suite().
   93: 
   94: groups() ->
   95:     Gs = [
   96:      {generic, [], [
   97:         % General errors
   98:         returns_error_when_no_jid_provided,
   99:         returns_error_when_invalid_jid_provided,
  100:         returns_error_when_valid_jid_but_no_property,
  101:         % Set-unread errors
  102:         returns_error_when_read_invalid_value,
  103:         returns_error_when_read_valid_request_but_not_in_inbox,
  104:         % Archiving errors
  105:         returns_error_when_archive_invalid_value,
  106:         returns_error_when_archive_valid_request_but_not_in_inbox,
  107:         % Muting errors
  108:         returns_error_when_mute_invalid_value,
  109:         returns_error_when_mute_invalid_seconds,
  110:         returns_error_when_mute_valid_request_but_not_in_inbox,
  111:         % Form errors
  112:         returns_error_when_archive_field_is_invalid,
  113:         returns_error_when_max_is_not_a_number
  114:       ]},
  115:      {one_to_one, [], [
  116:         % read
  117:         read_unread_entry_set_to_read,
  118:         read_unread_entry_set_to_read_iq_id_as_fallback,
  119:         read_unread_entry_set_to_read_queryid,
  120:         read_read_entry_set_to_unread,
  121:         read_unread_entry_with_two_messages_when_set_unread_then_unread_count_stays_in_two,
  122:         % archive
  123:         archive_active_entry_gets_archived,
  124:         archive_archived_entry_gets_active_on_request,
  125:         archive_archived_entry_gets_active_for_the_sender_on_new_message,
  126:         archive_archived_entry_gets_active_for_the_receiver_on_new_message,
  127:         archive_active_unread_entry_gets_archived_and_still_unread,
  128:         archive_full_archive_can_be_fetched,
  129:         archive_full_archive_can_be_fetched_queryid,
  130:         % mute
  131:         mute_unmuted_entry_gets_muted,
  132:         mute_muted_entry_gets_unmuted,
  133:         mute_after_timestamp_gets_unmuted,
  134:         mute_muted_conv_restarts_timestamp,
  135:         % other
  136:         returns_valid_properties_form,
  137:         properties_can_be_get,
  138:         properties_many_can_be_set,
  139:         properties_many_can_be_set_queryid,
  140:         max_queries_can_be_limited,
  141:         max_queries_can_fetch_ahead,
  142:         timestamp_is_not_reset_with_setting_properties
  143:       ]},
  144:      {muclight, [], [
  145:         groupchat_setunread_stanza_sets_inbox
  146:       ]},
  147:      {regular, [], [
  148:         {group, generic},
  149:         {group, one_to_one},
  150:         {group, muclight}
  151:       ]},
  152:      {async_pools, [], [
  153:         {group, generic},
  154:         {group, one_to_one},
  155:         {group, muclight}
  156:       ]}
  157:     ],
  158:     inbox_helper:maybe_run_in_parallel(Gs).
  159: 
  160: init_per_suite(Config) ->
  161:     escalus:init_per_suite(Config).
  162: 
  163: end_per_suite(Config) ->
  164:     escalus:end_per_suite(Config).
  165: 
  166: init_per_group(GroupName, Config) when GroupName =:= regular; GroupName =:= async_pools ->
  167:     HostType = domain_helper:host_type(),
  168:     Config1 = dynamic_modules:save_modules(HostType, Config),
  169:     ok = dynamic_modules:ensure_modules(
  170:            HostType,
  171:            inbox_helper:inbox_modules(GroupName) ++ inbox_helper:muclight_modules()),
  172:     InboxOptions = inbox_helper:inbox_opts(GroupName),
  173:     Config2 = [{inbox_opts, InboxOptions} | Config1],
  174:     escalus:create_users(Config2, escalus:get_users([alice, bob, kate, mike]));
  175: init_per_group(_GroupName, Config) ->
  176:     Config.
  177: 
  178: end_per_group(GroupName, Config) when GroupName =:= regular; GroupName =:= async_pools ->
  179:     muc_light_helper:clear_db(domain_helper:host_type()),
  180:     escalus_fresh:clean(),
  181:     Config1 = escalus:delete_users(Config, escalus:get_users([alice, bob, kate, mike])),
  182:     dynamic_modules:restore_modules(Config1);
  183: end_per_group(_GroupName, Config) ->
  184:     Config.
  185: 
  186: init_per_testcase(groupchat_setunread_stanza_sets_inbox, Config) ->
  187:     inbox_helper:clear_inbox_all(),
  188:     muc_light_helper:create_room(?ROOM_MARKERS_RESET, muc_light_helper:muc_host(), alice, [bob, kate],
  189:                                  Config, muc_light_helper:ver(1)),
  190:     escalus:init_per_testcase(groupchat_setunread_stanza_sets_inbox, Config);
  191: init_per_testcase(TestCase, Config) ->
  192:     escalus:init_per_testcase(TestCase, Config).
  193: 
  194: end_per_testcase(groupchat_setunread_stanza_sets_inbox, Config) ->
  195:     inbox_helper:clear_inbox_all(),
  196:     inbox_helper:restore_inbox_option(Config),
  197:     escalus:end_per_testcase(groupchat_setunread_stanza_sets_inbox, Config);
  198: end_per_testcase(TestCase, Config) ->
  199:     escalus:end_per_testcase(TestCase, Config).
  200: 
  201: 
  202: %%--------------------------------------------------------------------
  203: %% tests
  204: %%--------------------------------------------------------------------
  205: 
  206: % General
  207: returns_error_when_no_jid_provided(Config) ->
  208:     Stanza = make_inbox_iq_request(undefined, anything, anything),
  209:     returns_error(Config, Stanza, <<"jid-required">>).
  210: 
  211: returns_error_when_invalid_jid_provided(Config) ->
  212:     Stanza = make_inbox_iq_request(?INVALID_JID, anything, anything),
  213:     returns_error(Config, Stanza, <<"invalid-jid">>).
  214: 
  215: returns_error_when_valid_jid_but_no_property(Config) ->
  216:     Stanza = make_inbox_iq_request(?VALID_JID, undefined, anything),
  217:     returns_error(Config, Stanza, <<"no-property">>).
  218: 
  219: % Set-unread errors
  220: returns_error_when_read_invalid_value(Config) ->
  221:     Stanza = make_inbox_iq_request(?VALID_JID, read, ?INVALID_VALUE),
  222:     returns_error(Config, Stanza, <<"bad-request">>).
  223: 
  224: returns_error_when_read_valid_request_but_not_in_inbox(Config) ->
  225:     Stanza = make_inbox_iq_request(?VALID_JID, read, true),
  226:     returns_error(Config, Stanza, <<"item-not-found">>).
  227: 
  228: % Archiving errors
  229: returns_error_when_archive_invalid_value(Config) ->
  230:     Stanza = make_inbox_iq_request(?VALID_JID, archive, ?INVALID_VALUE),
  231:     returns_error(Config, Stanza, <<"bad-request">>).
  232: 
  233: returns_error_when_archive_valid_request_but_not_in_inbox(Config) ->
  234:     Stanza = make_inbox_iq_request(?VALID_JID, archive, true),
  235:     returns_error(Config, Stanza, <<"item-not-found">>).
  236: 
  237: % Muting errors
  238: returns_error_when_mute_invalid_value(Config) ->
  239:     Stanza = make_inbox_iq_request(?VALID_JID, mute, ?INVALID_VALUE),
  240:     returns_error(Config, Stanza, <<"bad-request">>).
  241: 
  242: returns_error_when_mute_invalid_seconds(Config) ->
  243:     Stanza = make_inbox_iq_request(?VALID_JID, mute, -?HOUR),
  244:     returns_error(Config, Stanza, <<"bad-request">>).
  245: 
  246: returns_error_when_mute_valid_request_but_not_in_inbox(Config) ->
  247:     Stanza = make_inbox_iq_request(?VALID_JID, mute, ?HOUR),
  248:     returns_error(Config, Stanza, <<"item-not-found">>).
  249: 
  250: % Form errors
  251: returns_error_when_archive_field_is_invalid(Config) ->
  252:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  253:       inbox_helper:assert_invalid_inbox_form_value_error(Alice, <<"archive">>, <<"invalid">>)
  254:     end).
  255: 
  256: returns_error_when_max_is_not_a_number(Config) ->
  257:     Stanza = inbox_helper:make_inbox_stanza(#{box => both, limit => <<"NaN">>}),
  258:     returns_error(Config, Stanza, <<"bad-request">>).
  259: 
  260: returns_error(Config, Stanza, Value) ->
  261:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  262:         assert_invalid_request(Alice, Stanza, Value)
  263:     end).
  264: 
  265: % read
  266: read_unread_entry_set_to_read(Config) ->
  267:     read_unread_entry_set_to_read(Config, undefined).
  268: 
  269: read_unread_entry_set_to_read_iq_id_as_fallback(Config) ->
  270:     read_unread_entry_set_to_read(Config, iq_id).
  271: 
  272: read_unread_entry_set_to_read_queryid(Config) ->
  273:     read_unread_entry_set_to_read(Config, queryid).
  274: 
  275: read_unread_entry_set_to_read(Config, QueryId) ->
  276:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  277:         % Alice sends a message to Bob
  278:         Body = <<"Hi Bob">>,
  279:         inbox_helper:send_msg(Alice, Bob, Body),
  280:         % Bob has one unread message
  281:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  282:         % Then Bob decides to mark it as read
  283:         set_inbox_properties(Bob, Alice, [{read, true}], inbox_helper:maybe_make_queryid(QueryId)),
  284:         % Bob's inbox has no unread messages
  285:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}])
  286:     end).
  287: 
  288: read_read_entry_set_to_unread(Config) ->
  289:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  290:         % Alice sends a message to Bob and Bob reads it
  291:         Body = <<"Hi Bob">>,
  292:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  293:         % Bob has no unread messages
  294:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  295:         % Then Bob decides to mark it again as unread
  296:         set_inbox_properties(Bob, Alice, [{read, false}]),
  297:         % Bob's inbox has something unread
  298:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}])
  299:     end).
  300: 
  301: read_unread_entry_with_two_messages_when_set_unread_then_unread_count_stays_in_two(Config) ->
  302:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  303:         % Alice sends a message to Bob and Bob reads it
  304:         Body = <<"Hi again Bob">>,
  305:         inbox_helper:send_msg(Alice, Bob, <<"Hi Bob">>),
  306:         inbox_helper:send_msg(Alice, Bob, Body),
  307:         % Bob has some unread messages
  308:         inbox_helper:check_inbox(Bob, [#conv{unread = 2, from = Alice, to = Bob, content = Body}]),
  309:         % Then Bob decides to mark it again as unread
  310:         set_inbox_properties(Bob, Alice, [{read, false}]),
  311:         % And the count didn't really change, to prevent losing higher counts
  312:         inbox_helper:check_inbox(Bob, [#conv{unread = 2, from = Alice, to = Bob, content = Body}])
  313:     end).
  314: 
  315: % archive
  316: archive_active_entry_gets_archived(Config) ->
  317:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  318:         % Alice sends a message to Bob
  319:         Body = <<"Hi Bob">>,
  320:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  321:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  322:         % Then Bob decides to archive it
  323:         set_inbox_properties(Bob, Alice, [{archive, true}]),
  324:         % Then the conversation is in the archive and not in the active box
  325:         inbox_helper:check_inbox(Bob, [], #{box => active}),
  326:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}], #{box => archive})
  327:     end).
  328: 
  329: archive_archived_entry_gets_active_on_request(Config) ->
  330:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  331:         % Alice sends a message to Bob and Bob archives it immediately
  332:         Body = <<"Hi Bob">>,
  333:         inbox_helper:send_msg(Alice, Bob, Body),
  334:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  335:         set_inbox_properties(Bob, Alice, [{archive, true}]),
  336:         % Then bob decides to recover the conversation
  337:         set_inbox_properties(Bob, Alice, [{archive, false}]),
  338:         % Then the conversation is in the active and not in the archive box
  339:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}], #{box => active}),
  340:         inbox_helper:check_inbox(Bob, [], #{box => archive})
  341:     end).
  342: 
  343: archive_archived_entry_gets_active_for_the_receiver_on_new_message(Config) ->
  344:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  345:         % Alice sends a message to Bob and Bob archives it immediately
  346:         Body = <<"Hi Bob">>,
  347:         inbox_helper:send_msg(Alice, Bob, Body),
  348:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  349:         set_inbox_properties(Bob, Alice, [{archive, true}]),
  350:         % But then Alice keeps writing:
  351:         inbox_helper:send_msg(Alice, Bob, Body),
  352:         % Then the conversation is automatically in the active and not in the archive box
  353:         inbox_helper:check_inbox(Bob, [#conv{unread = 2, from = Alice, to = Bob, content = Body}], #{box => active}),
  354:         inbox_helper:check_inbox(Bob, [], #{box => archive})
  355:     end).
  356: 
  357: archive_archived_entry_gets_active_for_the_sender_on_new_message(Config) ->
  358:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  359:         % Alice sends a message to Bob and then she archives the conversation
  360:         Body = <<"Hi Bob">>,
  361:         inbox_helper:send_msg(Alice, Bob, Body),
  362:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  363:         set_inbox_properties(Alice, Bob, [{archive, true}]),
  364:         % But then Alice keeps writing
  365:         inbox_helper:send_msg(Alice, Bob, Body),
  366:         % Then the conversation is automatically in the active and not in the archive box
  367:         inbox_helper:check_inbox(Alice, [], #{box => archive}),
  368:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = Alice, to = Bob, content = Body}], #{box => active})
  369:     end).
  370: 
  371: archive_active_unread_entry_gets_archived_and_still_unread(Config) ->
  372:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  373:         % Alice sends a message to Bob, but Bob archives it without reading it
  374:         Body = <<"Hi Bob">>,
  375:         inbox_helper:send_msg(Alice, Bob, Body),
  376:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  377:         set_inbox_properties(Bob, Alice, [{archive, true}]),
  378:         inbox_helper:check_inbox(Bob, [], #{box => active}),
  379:         % Then Bob queries his archive and the conversation is there still unread
  380:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}], #{box => archive})
  381:     end).
  382: 
  383: archive_full_archive_can_be_fetched(Config) ->
  384:     archive_full_archive_can_be_fetched(Config, undefined).
  385: archive_full_archive_can_be_fetched_queryid(Config) ->
  386:     archive_full_archive_can_be_fetched(Config, queryid).
  387: 
  388: archive_full_archive_can_be_fetched(Config, QueryId) ->
  389:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}], fun(Alice, Bob, Kate, Mike) ->
  390:         % Several people write to Alice, and Alice reads and archives all of them
  391:         inbox_helper:check_inbox(Alice, [], #{box => archive}),
  392:         #{Alice := AliceConvs} = inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  393:         inbox_helper:check_inbox(Alice, AliceConvs),
  394:         set_inbox_properties(Alice, Bob, [{archive, true}], inbox_helper:maybe_make_queryid(QueryId)),
  395:         set_inbox_properties(Alice, Kate, [{archive, true}], inbox_helper:maybe_make_queryid(QueryId)),
  396:         set_inbox_properties(Alice, Mike, [{archive, true}], inbox_helper:maybe_make_queryid(QueryId)),
  397:         % Then Alice queries her archive and the conversations are there and not in the active box
  398:         inbox_helper:check_inbox(Alice, [], #{box => active}),
  399:         inbox_helper:check_inbox(Alice, AliceConvs, #{box => archive})
  400:     end).
  401: 
  402: % mute
  403: mute_unmuted_entry_gets_muted(Config) ->
  404:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  405:         % Alice sends a message to Bob and Bob reads it
  406:         Body = <<"Hi Bob">>,
  407:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  408:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  409:         % Then Bob decides to mute the conversation
  410:         set_inbox_properties(Bob, Alice, [{mute, 24*?HOUR}]),
  411:         % Alice keeps writing
  412:         inbox_helper:send_msg(Alice, Bob, Body),
  413:         % Bob's inbox has an unread message, but it's marked as muted
  414:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body,
  415:                                              verify = fun(_, _, Outer) -> muted_status(23*?HOUR, Outer) end}])
  416:     end).
  417: 
  418: mute_muted_entry_gets_unmuted(Config) ->
  419:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  420:         % Alice sends a message to Bob and Bob reads it
  421:         Body = <<"Hi Bob">>,
  422:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  423:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  424:         % Then Bob decides to mute the conversation
  425:         set_inbox_properties(Bob, Alice, [{mute, 24*?HOUR}]),
  426:         % Alice keeps writing
  427:         inbox_helper:send_msg(Alice, Bob, Body),
  428:         % And Bob unmutes it again because he's now interested
  429:         set_inbox_properties(Bob, Alice, [{mute, 0}]),
  430:         % Bob's inbox has an unread message immediately unmuted
  431:         inbox_helper:check_inbox(
  432:           Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body,
  433:                       verify = fun(_, _, Outer) -> muted_status(unmuted, Outer) end}])
  434:     end).
  435: 
  436: mute_after_timestamp_gets_unmuted(Config) ->
  437:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  438:         % Alice sends a message to Bob and Bob reads it
  439:         Body = <<"Hi Bob">>,
  440:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  441:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  442:         % Then Bob decides to mute the conversation (for a very short 1 second for testing purposes)
  443:         set_inbox_properties(Bob, Alice, [{mute, 1}]),
  444:         % Alice keeps writing
  445:         inbox_helper:send_msg(Alice, Bob, Body),
  446:         % Inbox eventually returns unmuted
  447:         Fun = fun() ->
  448:                       try inbox_helper:check_inbox(
  449:                             Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body,
  450:                                         verify = fun(_, _, Outer) -> muted_status(unmuted, Outer) end}]),
  451:                           ok
  452:                       catch _:_ -> not_unmuted_yet
  453:                       end
  454:               end,
  455:         mongoose_helper:wait_until(Fun, ok, #{name => verify_its_unmuted, sleep_time => 250})
  456:     end).
  457: 
  458: mute_muted_conv_restarts_timestamp(Config) ->
  459:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  460:         % Alice sends a message to Bob and Bob reads it
  461:         Body = <<"Hi Bob">>,
  462:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  463:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  464:         % Then Bob decides to mute the conversation
  465:         set_inbox_properties(Bob, Alice, [{mute, ?HOUR}]),
  466:         % Alice keeps writing
  467:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  468:         % Then Bob decides to mute the conversation for way longer
  469:         set_inbox_properties(Bob, Alice, [{mute, 24*?HOUR}]),
  470:         % Muted timestamp is way longer than before
  471:         inbox_helper:check_inbox(
  472:           Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body,
  473:                       verify = fun(_, _, Outer) -> muted_status(23*?HOUR, Outer) end}])
  474:     end).
  475: 
  476: % other
  477: returns_valid_properties_form(Config) ->
  478:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  479:         InboxConversationNS = inbox_helper:inbox_ns_conversation(),
  480:         escalus:send(Alice, escalus_stanza:iq_get(InboxConversationNS, [])),
  481:         ResIQ = escalus:wait_for_stanza(Alice),
  482:         #{field_count := 4} = Form = inbox_helper:parse_form_iq(ResIQ),
  483:         #{<<"FORM_TYPE">> := #{type := <<"hidden">>, value := InboxConversationNS}} = Form,
  484:         #{<<"archive">> := #{type := <<"boolean">>, value := <<"false">>}} = Form,
  485:         #{<<"read">> := #{type := <<"boolean">>, value := <<"false">>}} = Form,
  486:         #{<<"mute">> := #{type := <<"text-single">>, value := <<"0">>}} = Form
  487:       end).
  488: 
  489: properties_can_be_get(Config) ->
  490:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  491:         % Alice sends a message to Bob
  492:         Body = <<"Hi Bob">>,
  493:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  494:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  495:         % Then Bob can just query the properties of this entry at will
  496:         query_properties(Bob, Alice, [{archive, false}, {read, true}, {mute, 0}])
  497:     end).
  498: properties_many_can_be_set(Config) ->
  499:     properties_many_can_be_set(Config, undefined).
  500: properties_many_can_be_set_queryid(Config) ->
  501:     properties_many_can_be_set(Config, queryid).
  502: 
  503: properties_many_can_be_set(Config, QueryId) ->
  504:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  505:         % Alice sends a message to Bob, and Bob sets a bunch of properties about it
  506:         Body = <<"Hi Bob">>,
  507:         inbox_helper:send_msg(Alice, Bob, Body),
  508:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  509:         set_inbox_properties(Bob, Alice, [{archive, true}, {read, true}, {mute, 24*?HOUR}],
  510:                              inbox_helper:maybe_make_queryid(QueryId)),
  511:         % Then Bob queries his boxes and everything is as expected
  512:         inbox_helper:check_inbox(Bob, [], #{box => active}),
  513:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body,
  514:                                        verify = fun(_, _, Outer) -> muted_status(23*?HOUR, Outer)
  515:                                                 end}], #{box => archive})
  516:     end).
  517: 
  518: max_queries_can_be_limited(Config) ->
  519:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  520:                         fun(Alice, Bob, Kate, Mike) ->
  521:         % Several people write to Alice
  522:         #{Alice := AliceConvs} =
  523:             inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  524:         % Alice has some messages in her inbox
  525:         inbox_helper:check_inbox(Alice, AliceConvs),
  526:         % Then Alice queries her inbox setting a limit to only one conversation,
  527:         % and she gets the newest one
  528:         ConvWithMike = lists:keyfind(Mike, #conv.to, AliceConvs),
  529:         inbox_helper:check_inbox(Alice, [ConvWithMike], #{limit => 1, box => active}),
  530:         % And a limit to two also works fine
  531:         ConvWithKate = lists:keyfind(Kate, #conv.to, AliceConvs),
  532:         inbox_helper:check_inbox(Alice, [ConvWithMike, ConvWithKate], #{limit => 2, box => active})
  533:     end).
  534: 
  535: max_queries_can_fetch_ahead(Config) ->
  536:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  537:                         fun(Alice, Bob, Kate, Mike) ->
  538:         #{Alice := AliceConvs} =
  539:             inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  540:         ConvWithBob = lists:keyfind(Bob, #conv.to, AliceConvs),
  541:         ConvWithKate = lists:keyfind(Kate, #conv.to, AliceConvs),
  542:         % ConvWithMike = lists:keyfind(Mike, #conv.to, AliceConvs),
  543:         TimeAfterKate = ConvWithKate#conv.time_after,
  544:         inbox_helper:check_inbox(Alice, [ConvWithKate, ConvWithBob],
  545:                   #{limit => 2, 'end' => TimeAfterKate, box => active})
  546:     end).
  547: 
  548: timestamp_is_not_reset_with_setting_properties(Config) ->
  549:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  550:         % Alice sends a message to Bob
  551:         inbox_helper:send_msg(Alice, Bob),
  552:         %% We capture the timestamp
  553:         [Item1] = inbox_helper:get_inbox(Bob, #{count => 1}),
  554:         TStamp1 = inbox_helper:timestamp_from_item(Item1),
  555:         % Bob sets a bunch of properties
  556:         set_inbox_properties(Bob, Alice, [{read, true}, {mute, 24*?HOUR}]),
  557:         % Bob gets the inbox again, and timestamp should be the same
  558:         [Item2] = inbox_helper:get_inbox(Bob, #{count => 1}),
  559:         TStamp2 = inbox_helper:timestamp_from_item(Item2),
  560:         ?assertEqual(TStamp1, TStamp2)
  561:   end).
  562: 
  563: % muclight
  564: groupchat_setunread_stanza_sets_inbox(Config) ->
  565:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  566:         %%% DATA
  567:         MsgBody = <<"marker time!">>,
  568:         AliceJid = inbox_helper:to_bare_lower(Alice),
  569:         BobJid = inbox_helper:to_bare_lower(Bob),
  570:         KateJid = inbox_helper:to_bare_lower(Kate),
  571:         RoomJid = muc_light_helper:room_bin_jid(?ROOM_MARKERS_RESET),
  572:         AliceRoomJid = <<RoomJid/binary,"/", AliceJid/binary>>,
  573:         %%% WHEN A MESSAGE IS SENT (two times the same message)
  574:         MsgStanza1 = escalus_stanza:set_id(escalus_stanza:groupchat_to(RoomJid, MsgBody), <<"id-1">>),
  575:         MsgStanza2 = escalus_stanza:set_id(escalus_stanza:groupchat_to(RoomJid, MsgBody), <<"id-2">>),
  576:         escalus:send(Alice, MsgStanza1),
  577:         inbox_helper:wait_for_groupchat_msg([Alice, Bob, Kate]),
  578:         escalus:send(Alice, MsgStanza2),
  579:         inbox_helper:wait_for_groupchat_msg([Alice, Bob, Kate]),
  580:         % verify that Bob has the message on inbox, reset it, and verify is still there but read
  581:         inbox_helper:check_inbox(Bob, [#conv{unread = 2, from = AliceRoomJid, to = BobJid, content = MsgBody}]),
  582:         set_inbox_properties(Bob, RoomJid, [{read, true}]),
  583:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = AliceRoomJid, to = BobJid, content = MsgBody}]),
  584:         % Bob sets the inbox as unread again and has so in his inbox
  585:         set_inbox_properties(Bob, RoomJid, [{read, false}]),
  586:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = AliceRoomJid, to = BobJid, content = MsgBody}]),
  587:         %% Alice has 0 unread messages because she was the sender
  588:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = AliceRoomJid, to = AliceJid, content = MsgBody}]),
  589:         %% Kate still has unread messages, and setting the entry as unread keeps the count to two
  590:         set_inbox_properties(Kate, RoomJid, [{read, false}]),
  591:         inbox_helper:check_inbox(Kate, [#conv{unread = 2, from = AliceRoomJid, to = KateJid, content = MsgBody}]),
  592:         %% And nobody received any other stanza
  593:         inbox_helper:assert_has_no_stanzas([Alice, Bob, Kate])
  594:     end).
  595: 
  596: 
  597: %%--------------------------------------------------------------------
  598: %% Helpers
  599: %%--------------------------------------------------------------------
  600: 
  601: -type maybe_client() :: undefined | escalus:client().
  602: -type box() :: both | active | archive.
  603: 
  604: -spec query_properties(escalus:client(), escalus:client(), proplists:proplist()) -> [exml:element()].
  605: query_properties(From, To, Expected) ->
  606:     Stanza = make_inbox_get_properties(To),
  607:     escalus:send(From, Stanza),
  608:     Result = escalus:wait_for_stanza(From),
  609:     ?assert(escalus_pred:is_iq_result(Stanza, Result)),
  610:     [Props] = exml_query:subelements(Result, <<"query">>),
  611:     ?assertEqual(inbox_helper:inbox_ns_conversation(), exml_query:attr(Props, <<"xmlns">>)),
  612:     lists:foreach(fun({Key, Val}) -> assert_property(Props, Key, Val) end, Expected).
  613: 
  614: -spec make_inbox_get_properties(escalus:client()) -> exml:element().
  615: make_inbox_get_properties(To) ->
  616:     Query = escalus_stanza:query_el(inbox_helper:inbox_ns_conversation(), jid_attr(To), []),
  617:     escalus_stanza:iq(<<"get">>, [Query]).
  618: 
  619: -spec set_inbox_properties(escalus:client(), escalus:client(), proplists:proplist()) -> ok.
  620: set_inbox_properties(From, To, Properties) ->
  621:     set_inbox_properties(From, To, Properties, #{}).
  622: 
  623: -spec set_inbox_properties(escalus:client(), escalus:client(), proplists:proplist(), map()) -> ok.
  624: set_inbox_properties(From, To, Properties, QueryOpts) ->
  625:     Stanza = make_inbox_iq_request_with_query_id(To, Properties, QueryOpts),
  626:     escalus:send(From, Stanza),
  627:     check_message_with_properties(From, Stanza, Properties, QueryOpts),
  628:     check_iq_result_for_property(From, Stanza).
  629: 
  630: -spec check_message_with_properties(escalus:client(), exml:element(), proplists:proplist(), map()) -> ok.
  631: check_message_with_properties(From, Stanza, Properties, QueryOpts) ->
  632:     Message = escalus:wait_for_stanza(From),
  633:     ?assert(escalus_pred:is_message(Message)),
  634:     ?assert(has_same_id(Stanza, Message)),
  635:     [X] = exml_query:subelements(Message, <<"x">>),
  636:     ?assertEqual(inbox_helper:inbox_ns_conversation(), exml_query:attr(X, <<"xmlns">>)),
  637:     inbox_helper:maybe_check_queryid(X, QueryOpts),
  638:     % ?assertEqual(QueryId, exml_query:attr(X, <<"queryid">>)),
  639:     lists:foreach(fun({Key, Val}) -> assert_property(X, Key, Val) end, Properties).
  640: 
  641: -spec check_iq_result_for_property(escalus:client(), exml:element()) -> ok.
  642: check_iq_result_for_property(From, Stanza) ->
  643:     Result = escalus:wait_for_stanza(From),
  644:     ?assert(escalus_pred:is_iq_result(Stanza, Result)).
  645: 
  646: -spec make_inbox_iq_request(maybe_client(), atom(), atom()) -> exml:element().
  647: make_inbox_iq_request(ToClient, Key, Value) ->
  648:     make_inbox_iq_request(ToClient, [{Key, Value}]).
  649: 
  650: -spec make_inbox_iq_request(maybe_client(), proplists:proplist()) -> exml:element().
  651: make_inbox_iq_request(ToClient, Properties) when is_list(Properties) ->
  652:     make_inbox_iq_request_with_query_id(ToClient, Properties, #{}).
  653: 
  654: -spec make_inbox_iq_request_with_query_id(maybe_client(), proplists:proplist(), map()) -> exml:element().
  655: make_inbox_iq_request_with_query_id(ToClient, Properties, QueryId) ->
  656:     JidAttr = jid_attr(ToClient),
  657:     IqAttr = id_attr(QueryId),
  658:     Children = props_to_children(Properties),
  659:     Query = escalus_stanza:query_el(inbox_helper:inbox_ns_conversation(), JidAttr ++ IqAttr, Children),
  660:     inbox_iq(QueryId, Query).
  661: 
  662: inbox_iq(#{iq_id := IqId}, Query) ->
  663:     IQ = escalus_stanza:iq(<<"set">>, [Query]),
  664:     escalus_stanza:set_id(IQ, IqId);
  665: inbox_iq(_, Query) ->
  666:     escalus_stanza:iq(<<"set">>, [Query]).
  667: 
  668: assert_invalid_request(From, Stanza, Value) ->
  669:     inbox_helper:assert_invalid_form(From, Stanza, Value, Value).
  670: 
  671: -spec jid_attr(maybe_client()) -> proplists:proplist().
  672: jid_attr(undefined) -> [];
  673: jid_attr(Client) -> [{<<"jid">>, escalus_utils:get_short_jid(Client)}].
  674: 
  675: id_attr(#{queryid := QueryId}) -> [{<<"queryid">>, QueryId}];
  676: id_attr(_) -> [].
  677: 
  678: props_to_children(L) -> props_to_children(L, []).
  679: props_to_children([], Acc) -> Acc;
  680: props_to_children([{undefined, _} | Rest], Acc) ->
  681:     props_to_children(Rest, Acc);
  682: props_to_children([{Key, undefined} | Rest], Acc) ->
  683:     props_to_children(Rest, [#xmlel{name = Key} | Acc]);
  684: props_to_children([{Key, Value} | Rest], Acc) ->
  685:     props_to_children(Rest,
  686:       [#xmlel{name = to_bin(Key), children = [#xmlcdata{content = to_bin(Value)}]} | Acc]).
  687: 
  688: assert_property(X, read, Val) ->
  689:     ?assertEqual(to_bin(Val), exml_query:path(X, [{element, <<"read">>}, cdata]));
  690: assert_property(X, archive, Val) ->
  691:     ?assertEqual(to_bin(Val), exml_query:path(X, [{element, <<"archive">>}, cdata]));
  692: assert_property(X, mute, 0) ->
  693:     ?assertEqual(0, binary_to_integer(exml_query:path(X, [{element, <<"mute">>}, cdata])));
  694: assert_property(X, mute, _) ->
  695:     Cal = binary_to_list(exml_query:path(X, [{element, <<"mute">>}, cdata])),
  696:     calendar:rfc3339_to_system_time(Cal, [{unit, microsecond}]).
  697: 
  698: -spec to_bin(term()) -> binary().
  699: to_bin(Value) when is_binary(Value) -> Value;
  700: to_bin(Value) when is_atom(Value) -> atom_to_binary(Value, utf8);
  701: to_bin(Value) when is_integer(Value) -> integer_to_binary(Value).
  702: 
  703: -spec has_same_id(exml:element(), exml:element()) -> boolean().
  704: has_same_id(OrigStanza, Stanza) ->
  705:     OrigId = exml_query:attr(OrigStanza, <<"id">>),
  706:     Id = exml_query:attr(Stanza, <<"id">>),
  707:     OrigId =:= Id.
  708: 
  709: muted_status(unmuted, Outer) ->
  710:     Res = exml_query:path(Outer, [{element, <<"result">>}, {element, <<"mute">>}, cdata]),
  711:     ?assertEqual(<<"0">>, Res);
  712: muted_status(MutedOrUnmuted, Outer) ->
  713:     GivenRfcTimestamp = exml_query:path(Outer, [{element, <<"result">>}, {element, <<"mute">>}, cdata]),
  714:     GivenMutedUntil = calendar:rfc3339_to_system_time(binary_to_list(GivenRfcTimestamp), [{offset, "Z"}, {unit, microsecond}]),
  715:     Now = erlang:system_time(microsecond),
  716:     case MutedOrUnmuted of
  717:         unmuted ->
  718:             ?assert(Now > GivenMutedUntil);
  719:         MutedDiff ->
  720:             Diff = erlang:convert_time_unit(MutedDiff, second, microsecond),
  721:             ?assert(Now + Diff < GivenMutedUntil)
  722:     end.