1: -module(inbox_extensions_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("escalus/include/escalus.hrl").
    5: -include_lib("escalus/include/escalus_xmlns.hrl").
    6: -include_lib("common_test/include/ct.hrl").
    7: -include_lib("exml/include/exml.hrl").
    8: -include_lib("eunit/include/eunit.hrl").
    9: -include("inbox.hrl").
   10: 
   11: -define(ROOM_MARKERS_RESET, <<"room_markers_reset">>).
   12: -define(HOUR, 3600).
   13: -define(VALID_JID, <<"mike@localhost">>).
   14: -define(INVALID_JID, <<"$@/">>).
   15: -define(INVALID_VALUE, <<"invalid">>).
   16: 
   17: -export([all/0,
   18:          groups/0,
   19:          suite/0,
   20:          init_per_suite/1,
   21:          end_per_suite/1,
   22:          init_per_group/2,
   23:          end_per_group/2,
   24:          init_per_testcase/2,
   25:          end_per_testcase/2]).
   26: 
   27: all() ->
   28:     inbox_helper:skip_or_run_inbox_tests(tests()).
   29: 
   30: tests() ->
   31:     [
   32:      {group, regular},
   33:      {group, async_pools}
   34:     ].
   35: 
   36: suite() ->
   37:     escalus:suite().
   38: 
   39: groups() ->
   40:     Gs = [
   41:      {generic, [], [
   42:         % General errors
   43:         returns_error_when_no_jid_provided,
   44:         returns_error_when_invalid_jid_provided,
   45:         returns_error_when_valid_jid_but_no_property,
   46:         % Set-unread errors
   47:         returns_error_when_read_invalid_value,
   48:         returns_error_when_read_valid_request_but_not_in_inbox,
   49:         % Boxes errors
   50:         returns_error_when_box_invalid_value,
   51:         returns_error_when_box_valid_request_but_not_in_inbox,
   52:         % Archiving errors
   53:         returns_error_when_archive_invalid_value,
   54:         returns_error_when_archive_valid_request_but_not_in_inbox,
   55:         % Muting errors
   56:         returns_error_when_mute_invalid_value,
   57:         returns_error_when_mute_invalid_seconds,
   58:         returns_error_when_mute_valid_request_but_not_in_inbox,
   59:         % Form errors
   60:         returns_error_when_archive_field_is_invalid,
   61:         returns_error_when_max_is_not_a_number
   62:       ]},
   63:      {one_to_one, [], [
   64:         % read
   65:         read_unread_entry_set_to_read,
   66:         read_unread_entry_set_to_read_iq_id_as_fallback,
   67:         read_unread_entry_set_to_read_queryid,
   68:         read_read_entry_set_to_unread,
   69:         read_unread_entry_with_two_messages_when_set_unread_then_unread_count_stays_in_two,
   70:         % box
   71:         box_move_to_other_works_successfully,
   72:         box_active_entry_gets_archived,
   73:         box_other_entry_does_not_get_unarchived,
   74:         box_archived_entry_gets_active_on_request,
   75:         box_archived_entry_gets_active_for_the_sender_on_new_message,
   76:         box_archived_entry_gets_active_for_the_receiver_on_new_message,
   77:         box_active_unread_entry_gets_archived_and_still_unread,
   78:         box_full_archive_can_be_fetched,
   79:         box_full_archive_can_be_fetched_queryid,
   80:         box_and_archive_box_has_preference,
   81:         box_other_does_get_fetched,
   82:         box_all_full_fetch,
   83:         % archive
   84:         archive_active_entry_gets_archived,
   85:         archive_archived_entry_gets_active_on_request,
   86:         archive_archived_entry_gets_active_for_the_sender_on_new_message,
   87:         archive_archived_entry_gets_active_for_the_receiver_on_new_message,
   88:         archive_active_unread_entry_gets_archived_and_still_unread,
   89:         archive_full_archive_can_be_fetched,
   90:         archive_full_archive_can_be_fetched_queryid,
   91:         % mute
   92:         mute_unmuted_entry_gets_muted,
   93:         mute_muted_entry_gets_unmuted,
   94:         mute_after_timestamp_gets_unmuted,
   95:         mute_muted_conv_restarts_timestamp,
   96:         % other
   97:         returns_valid_properties_form,
   98:         properties_can_be_get,
   99:         properties_full_entry_can_be_get,
  100:         properties_many_can_be_set,
  101:         properties_many_can_be_set_queryid,
  102:         timestamp_is_not_reset_with_setting_properties,
  103:         {group, pagination}
  104:       ]},
  105:      {pagination, [], [
  106:         pagination_error_conditions,
  107:         pagination_has_priority_over_form_before_overrides_start,
  108:         pagination_has_priority_over_form_after_overrides_end,
  109:         can_paginate_forwards,
  110:         can_paginate_backwards,
  111:         max_queries_can_be_limited,
  112:         max_queries_can_fetch_ahead
  113:       ]},
  114:      {muclight, [], [
  115:         groupchat_setunread_stanza_sets_inbox
  116:       ]},
  117:      {regular, [], [
  118:         {group, generic},
  119:         {group, one_to_one},
  120:         {group, muclight}
  121:       ]},
  122:      {async_pools, [], [
  123:         {group, generic},
  124:         {group, one_to_one},
  125:         {group, muclight}
  126:       ]}
  127:     ],
  128:     inbox_helper:maybe_run_in_parallel(Gs).
  129: 
  130: init_per_suite(Config) ->
  131:     escalus:init_per_suite(Config).
  132: 
  133: end_per_suite(Config) ->
  134:     escalus:end_per_suite(Config).
  135: 
  136: init_per_group(GroupName, Config) when GroupName =:= regular; GroupName =:= async_pools ->
  137:     HostType = domain_helper:host_type(),
  138:     Config1 = dynamic_modules:save_modules(HostType, Config),
  139:     ok = dynamic_modules:ensure_modules(
  140:            HostType,
  141:            inbox_helper:inbox_modules(GroupName) ++ inbox_helper:muclight_modules()),
  142:     InboxOptions = inbox_helper:inbox_opts(GroupName),
  143:     Config2 = [{inbox_opts, InboxOptions} | Config1],
  144:     escalus:create_users(Config2, escalus:get_users([alice, bob, kate, mike]));
  145: init_per_group(_GroupName, Config) ->
  146:     Config.
  147: 
  148: end_per_group(GroupName, Config) when GroupName =:= regular; GroupName =:= async_pools ->
  149:     muc_light_helper:clear_db(domain_helper:host_type()),
  150:     escalus_fresh:clean(),
  151:     Config1 = escalus:delete_users(Config, escalus:get_users([alice, bob, kate, mike])),
  152:     dynamic_modules:restore_modules(Config1);
  153: end_per_group(_GroupName, Config) ->
  154:     Config.
  155: 
  156: init_per_testcase(groupchat_setunread_stanza_sets_inbox, Config) ->
  157:     inbox_helper:clear_inbox_all(),
  158:     muc_light_helper:create_room(?ROOM_MARKERS_RESET, muc_light_helper:muc_host(), alice, [bob, kate],
  159:                                  Config, muc_light_helper:ver(1)),
  160:     escalus:init_per_testcase(groupchat_setunread_stanza_sets_inbox, Config);
  161: init_per_testcase(TestCase, Config) ->
  162:     escalus:init_per_testcase(TestCase, Config).
  163: 
  164: end_per_testcase(groupchat_setunread_stanza_sets_inbox, Config) ->
  165:     inbox_helper:clear_inbox_all(),
  166:     inbox_helper:restore_inbox_option(Config),
  167:     escalus:end_per_testcase(groupchat_setunread_stanza_sets_inbox, Config);
  168: end_per_testcase(TestCase, Config) ->
  169:     escalus:end_per_testcase(TestCase, Config).
  170: 
  171: 
  172: %%--------------------------------------------------------------------
  173: %% tests
  174: %%--------------------------------------------------------------------
  175: 
  176: % General
  177: returns_error_when_no_jid_provided(Config) ->
  178:     Stanza = make_inbox_iq_request(undefined, anything, anything),
  179:     returns_error(Config, Stanza, <<"jid-required">>).
  180: 
  181: returns_error_when_invalid_jid_provided(Config) ->
  182:     Stanza = make_inbox_iq_request(?INVALID_JID, anything, anything),
  183:     returns_error(Config, Stanza, <<"invalid-jid">>).
  184: 
  185: returns_error_when_valid_jid_but_no_property(Config) ->
  186:     Stanza = make_inbox_iq_request(?VALID_JID, undefined, anything),
  187:     returns_error(Config, Stanza, <<"no-property">>).
  188: 
  189: % Set-unread errors
  190: returns_error_when_read_invalid_value(Config) ->
  191:     Stanza = make_inbox_iq_request(?VALID_JID, read, ?INVALID_VALUE),
  192:     returns_error(Config, Stanza, <<"bad-request">>).
  193: 
  194: returns_error_when_read_valid_request_but_not_in_inbox(Config) ->
  195:     Stanza = make_inbox_iq_request(?VALID_JID, read, true),
  196:     returns_error(Config, Stanza, <<"item-not-found">>).
  197: 
  198: % Boxes errors
  199: returns_error_when_box_invalid_value(Config) ->
  200:     Stanza = make_inbox_iq_request(?VALID_JID, box, ?INVALID_VALUE),
  201:     returns_error(Config, Stanza, <<"invalid-box">>).
  202: 
  203: returns_error_when_box_valid_request_but_not_in_inbox(Config) ->
  204:     Stanza = make_inbox_iq_request(?VALID_JID, box, <<"archive">>),
  205:     returns_error(Config, Stanza, <<"item-not-found">>).
  206: 
  207: % Archiving errors
  208: returns_error_when_archive_invalid_value(Config) ->
  209:     Stanza = make_inbox_iq_request(?VALID_JID, archive, ?INVALID_VALUE),
  210:     returns_error(Config, Stanza, <<"bad-request">>).
  211: 
  212: returns_error_when_archive_valid_request_but_not_in_inbox(Config) ->
  213:     Stanza = make_inbox_iq_request(?VALID_JID, archive, true),
  214:     returns_error(Config, Stanza, <<"item-not-found">>).
  215: 
  216: % Muting errors
  217: returns_error_when_mute_invalid_value(Config) ->
  218:     Stanza = make_inbox_iq_request(?VALID_JID, mute, ?INVALID_VALUE),
  219:     returns_error(Config, Stanza, <<"bad-request">>).
  220: 
  221: returns_error_when_mute_invalid_seconds(Config) ->
  222:     Stanza = make_inbox_iq_request(?VALID_JID, mute, -?HOUR),
  223:     returns_error(Config, Stanza, <<"bad-request">>).
  224: 
  225: returns_error_when_mute_valid_request_but_not_in_inbox(Config) ->
  226:     Stanza = make_inbox_iq_request(?VALID_JID, mute, ?HOUR),
  227:     returns_error(Config, Stanza, <<"item-not-found">>).
  228: 
  229: % Form errors
  230: returns_error_when_archive_field_is_invalid(Config) ->
  231:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  232:       inbox_helper:assert_invalid_inbox_form_value_error(Alice, <<"archive">>, <<"invalid">>)
  233:     end).
  234: 
  235: returns_error_when_max_is_not_a_number(Config) ->
  236:     Stanza = inbox_helper:make_inbox_stanza(#{box => both, limit => <<"NaN">>}),
  237:     returns_error(Config, Stanza, <<"bad-request">>).
  238: 
  239: returns_error(Config, Stanza, Value) ->
  240:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  241:         assert_invalid_request(Alice, Stanza, Value)
  242:     end).
  243: 
  244: % read
  245: read_unread_entry_set_to_read(Config) ->
  246:     read_unread_entry_set_to_read(Config, undefined).
  247: 
  248: read_unread_entry_set_to_read_iq_id_as_fallback(Config) ->
  249:     read_unread_entry_set_to_read(Config, iq_id).
  250: 
  251: read_unread_entry_set_to_read_queryid(Config) ->
  252:     read_unread_entry_set_to_read(Config, queryid).
  253: 
  254: read_unread_entry_set_to_read(Config, QueryId) ->
  255:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  256:         % Alice sends a message to Bob
  257:         Body = <<"Hi Bob">>,
  258:         inbox_helper:send_msg(Alice, Bob, Body),
  259:         % Bob has one unread message
  260:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  261:         % Then Bob decides to mark it as read
  262:         set_inbox_properties(Bob, Alice, [{read, true}], inbox_helper:maybe_make_queryid(QueryId)),
  263:         % Bob's inbox has no unread messages
  264:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}])
  265:     end).
  266: 
  267: read_read_entry_set_to_unread(Config) ->
  268:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  269:         % Alice sends a message to Bob and Bob reads it
  270:         Body = <<"Hi Bob">>,
  271:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  272:         % Bob has no unread messages
  273:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  274:         % Then Bob decides to mark it again as unread
  275:         set_inbox_properties(Bob, Alice, [{read, false}]),
  276:         % Bob's inbox has something unread
  277:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}])
  278:     end).
  279: 
  280: read_unread_entry_with_two_messages_when_set_unread_then_unread_count_stays_in_two(Config) ->
  281:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  282:         % Alice sends a message to Bob and Bob reads it
  283:         Body = <<"Hi again Bob">>,
  284:         inbox_helper:send_msg(Alice, Bob, <<"Hi Bob">>),
  285:         inbox_helper:send_msg(Alice, Bob, Body),
  286:         % Bob has some unread messages
  287:         inbox_helper:check_inbox(Bob, [#conv{unread = 2, from = Alice, to = Bob, content = Body}]),
  288:         % Then Bob decides to mark it again as unread
  289:         set_inbox_properties(Bob, Alice, [{read, false}]),
  290:         % And the count didn't really change, to prevent losing higher counts
  291:         inbox_helper:check_inbox(Bob, [#conv{unread = 2, from = Alice, to = Bob, content = Body}])
  292:     end).
  293: 
  294: % box
  295: box_move_to_other_works_successfully(Config) ->
  296:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  297:         % Alice sends a message to Bob
  298:         Body = <<"Hi Bob">>,
  299:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  300:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  301:         % Then Bob decides to move it to other
  302:         set_inbox_properties(Bob, Alice, [{box, other}]),
  303:         % Then the conversation is in the right box
  304:         inbox_helper:check_inbox(Bob, [], #{box => archive}),
  305:         inbox_helper:check_inbox(Bob, [], #{box => inbox}),
  306:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}],
  307:                                  #{box => other})
  308:     end).
  309: 
  310: box_active_entry_gets_archived(Config) ->
  311:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  312:         % Alice sends a message to Bob
  313:         Body = <<"Hi Bob">>,
  314:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  315:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  316:         % Then Bob decides to archive it
  317:         set_inbox_properties(Bob, Alice, [{box, archive}]),
  318:         % Then the conversation is in the archive and not in the inbox box
  319:         inbox_helper:check_inbox(Bob, [], #{box => inbox}),
  320:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}],
  321:                                  #{box => archive})
  322:     end).
  323: 
  324: box_other_entry_does_not_get_unarchived(Config) ->
  325:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  326:         % Alice sends a message to Bob and then she moves the conversation to the 'other' box
  327:         Body1 = <<"Hi Bob">>,
  328:         inbox_helper:send_msg(Alice, Bob, Body1),
  329:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = Alice, to = Bob, content = Body1}]),
  330:         set_inbox_properties(Alice, Bob, [{box, other}]),
  331:         % But then Alice keeps writing
  332:         Body2 = <<"Hi Bob again">>,
  333:         inbox_helper:send_msg(Alice, Bob, Body2),
  334:         inbox_helper:get_inbox(Alice, #{box => all, hidden_read => false}, #{count => 1}),
  335:         % Then the conversation is still in the same box
  336:         inbox_helper:check_inbox(Alice, [], #{box => inbox}),
  337:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = Alice, to = Bob, content = Body2}],
  338:                                  #{box => other})
  339:     end).
  340: 
  341: box_archived_entry_gets_active_on_request(Config) ->
  342:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  343:         % Alice sends a message to Bob and Bob archives it immediately
  344:         Body = <<"Hi Bob">>,
  345:         inbox_helper:send_msg(Alice, Bob, Body),
  346:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  347:         set_inbox_properties(Bob, Alice, [{box, archive}]),
  348:         % Then bob decides to recover the conversation
  349:         set_inbox_properties(Bob, Alice, [{box, inbox}]),
  350:         % Then the conversation is in the inbox and not in the archive box
  351:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}],
  352:                                  #{box => inbox}),
  353:         inbox_helper:check_inbox(Bob, [], #{box => archive})
  354:     end).
  355: 
  356: box_archived_entry_gets_active_for_the_receiver_on_new_message(Config) ->
  357:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  358:         % Alice sends a message to Bob and Bob archives it immediately
  359:         Body = <<"Hi Bob">>,
  360:         inbox_helper:send_msg(Alice, Bob, Body),
  361:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  362:         set_inbox_properties(Bob, Alice, [{box, archive}]),
  363:         % But then Alice keeps writing:
  364:         inbox_helper:send_msg(Alice, Bob, Body),
  365:         % Then the conversation is automatically in the inbox and not in the archive box
  366:         inbox_helper:check_inbox(Bob, [#conv{unread = 2, from = Alice, to = Bob, content = Body}],
  367:                                  #{box => inbox}),
  368:         inbox_helper:check_inbox(Bob, [], #{box => archive})
  369:     end).
  370: 
  371: box_archived_entry_gets_active_for_the_sender_on_new_message(Config) ->
  372:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  373:         % Alice sends a message to Bob and then she archives the conversation
  374:         Body = <<"Hi Bob">>,
  375:         inbox_helper:send_msg(Alice, Bob, Body),
  376:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  377:         set_inbox_properties(Alice, Bob, [{box, archive}]),
  378:         % But then Alice keeps writing
  379:         inbox_helper:send_msg(Alice, Bob, Body),
  380:         % Then the conversation is automatically in the inbox and not in the archive box
  381:         inbox_helper:check_inbox(Alice, [], #{box => archive}),
  382:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = Alice, to = Bob, content = Body}],
  383:                                  #{box => inbox})
  384:     end).
  385: 
  386: box_active_unread_entry_gets_archived_and_still_unread(Config) ->
  387:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  388:         % Alice sends a message to Bob, but Bob archives it without reading it
  389:         Body = <<"Hi Bob">>,
  390:         inbox_helper:send_msg(Alice, Bob, Body),
  391:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  392:         set_inbox_properties(Bob, Alice, [{box, archive}]),
  393:         inbox_helper:check_inbox(Bob, [], #{box => inbox}),
  394:         % Then Bob queries his archive and the conversation is there still unread
  395:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}],
  396:                                  #{box => archive})
  397:     end).
  398: 
  399: box_full_archive_can_be_fetched(Config) ->
  400:     box_full_archive_can_be_fetched(Config, undefined).
  401: box_full_archive_can_be_fetched_queryid(Config) ->
  402:     box_full_archive_can_be_fetched(Config, queryid).
  403: 
  404: box_full_archive_can_be_fetched(Config, QueryId) ->
  405:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}], fun(Alice, Bob, Kate, Mike) ->
  406:         % Several people write to Alice, and Alice reads and archives all of them
  407:         inbox_helper:check_inbox(Alice, [], #{box => archive}),
  408:         #{Alice := AliceConvs} = inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  409:         inbox_helper:check_inbox(Alice, AliceConvs),
  410:         set_inbox_properties(Alice, Bob, [{box, archive}], inbox_helper:maybe_make_queryid(QueryId)),
  411:         set_inbox_properties(Alice, Kate, [{box, archive}], inbox_helper:maybe_make_queryid(QueryId)),
  412:         set_inbox_properties(Alice, Mike, [{box, archive}], inbox_helper:maybe_make_queryid(QueryId)),
  413:         % Then Alice queries her archive and the conversations are there and not in the inbox box
  414:         inbox_helper:check_inbox(Alice, [], #{box => inbox}),
  415:         inbox_helper:check_inbox(Alice, AliceConvs, #{box => archive})
  416:     end).
  417: 
  418: box_and_archive_box_has_preference(Config) ->
  419:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  420:         % Alice sends a message to Bob
  421:         Body = <<"Hi Bob">>,
  422:         inbox_helper:send_msg(Alice, Bob, Body),
  423:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  424:         % Then fetching with archive and box ignores the archive value
  425:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}],
  426:                                  #{box => inbox, archive => true}),
  427:         inbox_helper:check_inbox(Bob, [], #{box => archive, archive => false})
  428:     end).
  429: 
  430: box_other_does_get_fetched(Config) ->
  431:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  432:         % Alice sends a message to Bob and Bob throws in into 'other'
  433:         Body = <<"Hi Bob">>,
  434:         inbox_helper:send_msg(Alice, Bob, Body),
  435:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  436:         set_inbox_properties(Bob, Alice, [{box, other}]),
  437:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}],
  438:                                  #{box => other}),
  439:         % Then bob decides to recover the conversation
  440:         set_inbox_properties(Bob, Alice, [{box, inbox}]),
  441:         % Then the conversation is in the inbox and not in the archive box
  442:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}],
  443:                                  #{box => inbox}),
  444:         inbox_helper:check_inbox(Bob, [], #{box => archive})
  445:     end).
  446: 
  447: box_all_full_fetch(Config) ->
  448:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  449:         % Alice sends a message to Bob and Kate
  450:         #{ Alice := AliceConvs } = inbox_helper:given_conversations_between(Alice, [Bob, Kate]),
  451:         inbox_helper:check_inbox(Alice, AliceConvs),
  452:         set_inbox_properties(Alice, Bob, [{box, archive}]),
  453:         set_inbox_properties(Alice, Kate, [{box, other}]),
  454:         inbox_helper:check_inbox(Alice, AliceConvs, #{box => all})
  455:     end).
  456: 
  457: % archive
  458: archive_active_entry_gets_archived(Config) ->
  459:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  460:         % Alice sends a message to Bob
  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 archive it
  465:         set_inbox_properties(Bob, Alice, [{archive, true}]),
  466:         % Then the conversation is in the archive and not in the active box
  467:         inbox_helper:check_inbox(Bob, [], #{archive => false}),
  468:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}],
  469:                                  #{archive => true})
  470:     end).
  471: 
  472: archive_archived_entry_gets_active_on_request(Config) ->
  473:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  474:         % Alice sends a message to Bob and Bob archives it immediately
  475:         Body = <<"Hi Bob">>,
  476:         inbox_helper:send_msg(Alice, Bob, Body),
  477:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  478:         set_inbox_properties(Bob, Alice, [{archive, true}]),
  479:         % Then bob decides to recover the conversation
  480:         set_inbox_properties(Bob, Alice, [{archive, false}]),
  481:         % Then the conversation is in the active and not in the archive box
  482:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}],
  483:                                  #{archive => false}),
  484:         inbox_helper:check_inbox(Bob, [], #{archive => true})
  485:     end).
  486: 
  487: archive_archived_entry_gets_active_for_the_receiver_on_new_message(Config) ->
  488:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  489:         % Alice sends a message to Bob and Bob archives it immediately
  490:         Body = <<"Hi Bob">>,
  491:         inbox_helper:send_msg(Alice, Bob, Body),
  492:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  493:         set_inbox_properties(Bob, Alice, [{archive, true}]),
  494:         % But then Alice keeps writing:
  495:         inbox_helper:send_msg(Alice, Bob, Body),
  496:         % Then the conversation is automatically in the active and not in the archive box
  497:         inbox_helper:check_inbox(Bob, [#conv{unread = 2, from = Alice, to = Bob, content = Body}],
  498:                                  #{archive => false}),
  499:         inbox_helper:check_inbox(Bob, [], #{archive => true})
  500:     end).
  501: 
  502: archive_archived_entry_gets_active_for_the_sender_on_new_message(Config) ->
  503:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  504:         % Alice sends a message to Bob and then she archives the conversation
  505:         Body = <<"Hi Bob">>,
  506:         inbox_helper:send_msg(Alice, Bob, Body),
  507:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  508:         set_inbox_properties(Alice, Bob, [{archive, true}]),
  509:         % But then Alice keeps writing
  510:         inbox_helper:send_msg(Alice, Bob, Body),
  511:         % Then the conversation is automatically in the active and not in the archive box
  512:         inbox_helper:check_inbox(Alice, [], #{archive => true}),
  513:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = Alice, to = Bob, content = Body}],
  514:                                  #{archive => false})
  515:     end).
  516: 
  517: archive_active_unread_entry_gets_archived_and_still_unread(Config) ->
  518:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  519:         % Alice sends a message to Bob, but Bob archives it without reading it
  520:         Body = <<"Hi Bob">>,
  521:         inbox_helper:send_msg(Alice, Bob, Body),
  522:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  523:         set_inbox_properties(Bob, Alice, [{archive, true}]),
  524:         inbox_helper:check_inbox(Bob, [], #{archive => false}),
  525:         % Then Bob queries his archive and the conversation is there still unread
  526:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}],
  527:                                  #{archive => true})
  528:     end).
  529: 
  530: archive_full_archive_can_be_fetched(Config) ->
  531:     archive_full_archive_can_be_fetched(Config, undefined).
  532: archive_full_archive_can_be_fetched_queryid(Config) ->
  533:     archive_full_archive_can_be_fetched(Config, queryid).
  534: 
  535: archive_full_archive_can_be_fetched(Config, QueryId) ->
  536:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}], fun(Alice, Bob, Kate, Mike) ->
  537:         % Several people write to Alice, and Alice reads and archives all of them
  538:         inbox_helper:check_inbox(Alice, [], #{archive => true}),
  539:         #{Alice := AliceConvs} = inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  540:         inbox_helper:check_inbox(Alice, AliceConvs),
  541:         set_inbox_properties(Alice, Bob, [{archive, true}], inbox_helper:maybe_make_queryid(QueryId)),
  542:         set_inbox_properties(Alice, Kate, [{archive, true}], inbox_helper:maybe_make_queryid(QueryId)),
  543:         set_inbox_properties(Alice, Mike, [{archive, true}], inbox_helper:maybe_make_queryid(QueryId)),
  544:         % Then Alice queries her archive and the conversations are there and not in the active box
  545:         inbox_helper:check_inbox(Alice, [], #{archive => false}),
  546:         inbox_helper:check_inbox(Alice, AliceConvs, #{archive => true})
  547:     end).
  548: 
  549: % mute
  550: mute_unmuted_entry_gets_muted(Config) ->
  551:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  552:         % Alice sends a message to Bob and Bob reads it
  553:         Body = <<"Hi Bob">>,
  554:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  555:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  556:         % Then Bob decides to mute the conversation
  557:         set_inbox_properties(Bob, Alice, [{mute, 24*?HOUR}]),
  558:         % Alice keeps writing
  559:         inbox_helper:send_msg(Alice, Bob, Body),
  560:         % Bob's inbox has an unread message, but it's marked as muted
  561:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body,
  562:                                              verify = fun(_, _, Outer) -> muted_status(23*?HOUR, Outer) end}])
  563:     end).
  564: 
  565: mute_muted_entry_gets_unmuted(Config) ->
  566:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  567:         % Alice sends a message to Bob and Bob reads it
  568:         Body = <<"Hi Bob">>,
  569:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  570:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  571:         % Then Bob decides to mute the conversation
  572:         set_inbox_properties(Bob, Alice, [{mute, 24*?HOUR}]),
  573:         % Alice keeps writing
  574:         inbox_helper:send_msg(Alice, Bob, Body),
  575:         % And Bob unmutes it again because he's now interested
  576:         set_inbox_properties(Bob, Alice, [{mute, 0}]),
  577:         % Bob's inbox has an unread message immediately unmuted
  578:         inbox_helper:check_inbox(
  579:           Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body,
  580:                       verify = fun(_, _, Outer) -> muted_status(unmuted, Outer) end}])
  581:     end).
  582: 
  583: mute_after_timestamp_gets_unmuted(Config) ->
  584:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  585:         % Alice sends a message to Bob and Bob reads it
  586:         Body = <<"Hi Bob">>,
  587:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  588:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  589:         % Then Bob decides to mute the conversation (for a very short 1 second for testing purposes)
  590:         set_inbox_properties(Bob, Alice, [{mute, 1}]),
  591:         % Alice keeps writing
  592:         inbox_helper:send_msg(Alice, Bob, Body),
  593:         % Inbox eventually returns unmuted
  594:         Fun = fun() ->
  595:                       try inbox_helper:check_inbox(
  596:                             Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body,
  597:                                         verify = fun(_, _, Outer) -> muted_status(unmuted, Outer) end}]),
  598:                           ok
  599:                       catch _:_ -> not_unmuted_yet
  600:                       end
  601:               end,
  602:         mongoose_helper:wait_until(Fun, ok, #{name => verify_its_unmuted, sleep_time => 250})
  603:     end).
  604: 
  605: mute_muted_conv_restarts_timestamp(Config) ->
  606:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  607:         % Alice sends a message to Bob and Bob reads it
  608:         Body = <<"Hi Bob">>,
  609:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  610:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  611:         % Then Bob decides to mute the conversation
  612:         set_inbox_properties(Bob, Alice, [{mute, ?HOUR}]),
  613:         % Alice keeps writing
  614:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  615:         % Then Bob decides to mute the conversation for way longer
  616:         set_inbox_properties(Bob, Alice, [{mute, 24*?HOUR}]),
  617:         % Muted timestamp is way longer than before
  618:         inbox_helper:check_inbox(
  619:           Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body,
  620:                       verify = fun(_, _, Outer) -> muted_status(23*?HOUR, Outer) end}])
  621:     end).
  622: 
  623: % other
  624: returns_valid_properties_form(Config) ->
  625:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  626:         InboxConversationNS = inbox_helper:inbox_ns_conversation(),
  627:         escalus:send(Alice, escalus_stanza:iq_get(InboxConversationNS, [])),
  628:         ResIQ = escalus:wait_for_stanza(Alice),
  629:         #{field_count := 5} = Form = inbox_helper:parse_form_iq(ResIQ),
  630:         #{<<"FORM_TYPE">> := #{type := <<"hidden">>, value := InboxConversationNS}} = Form,
  631:         #{<<"archive">> := #{type := <<"boolean">>, value := <<"false">>}} = Form,
  632:         #{<<"box">> := #{type := <<"list-single">>, value := <<"all">>}} = Form,
  633:         #{<<"read">> := #{type := <<"boolean">>, value := <<"false">>}} = Form,
  634:         #{<<"mute">> := #{type := <<"text-single">>, value := <<"0">>}} = Form
  635:       end).
  636: 
  637: properties_can_be_get(Config) ->
  638:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  639:         % Alice sends a message to Bob
  640:         Body = <<"Hi Bob">>,
  641:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  642:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  643:         % Then Bob can just query the properties of this entry at will
  644:         query_properties(Bob, Alice, [{archive, false}, {read, true}, {mute, 0}])
  645:     end).
  646: 
  647: properties_full_entry_can_be_get(Config) ->
  648:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  649:         % Alice sends a message to Bob
  650:         Body = <<"Hi Bob">>,
  651:         inbox_helper:send_and_mark_msg(Alice, Bob, Body),
  652:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body}]),
  653:         % Then Bob can just query the properties of this entry at will
  654:         query_properties(Bob, Alice, [{archive, false}, {read, true}, {mute, 0}], full_entry)
  655:     end).
  656: 
  657: properties_many_can_be_set(Config) ->
  658:     properties_many_can_be_set(Config, undefined).
  659: properties_many_can_be_set_queryid(Config) ->
  660:     properties_many_can_be_set(Config, queryid).
  661: 
  662: properties_many_can_be_set(Config, QueryId) ->
  663:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  664:         % Alice sends a message to Bob, and Bob sets a bunch of properties about it
  665:         Body = <<"Hi Bob">>,
  666:         inbox_helper:send_msg(Alice, Bob, Body),
  667:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = Alice, to = Bob, content = Body}]),
  668:         set_inbox_properties(Bob, Alice, [{archive, true}, {read, true}, {mute, 24*?HOUR}],
  669:                              inbox_helper:maybe_make_queryid(QueryId)),
  670:         % Then Bob queries his boxes and everything is as expected
  671:         inbox_helper:check_inbox(Bob, [], #{box => inbox}),
  672:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = Alice, to = Bob, content = Body,
  673:                                        verify = fun(_, _, Outer) -> muted_status(23*?HOUR, Outer)
  674:                                                 end}], #{box => archive})
  675:     end).
  676: 
  677: pagination_error_conditions(Config) ->
  678:     escalus:fresh_story(Config, [{alice, 1}], fun(Alice) ->
  679:         % We set start and end to return Convs with Mike, but using RSM we override that
  680:         TS = erlang:system_time(millisecond),
  681:         AliceJid = escalus_utils:get_short_jid(Alice),
  682:         IdNotDividing = <<(integer_to_binary(TS))/binary, (base64:encode(AliceJid))/binary>>,
  683:         verify_returns_error(Alice, #{box => inbox, 'after' => IdNotDividing}, <<"bad-request">>),
  684:         BadInt = <<(integer_to_binary(-10))/binary, "/", (base64:encode(AliceJid))/binary>>,
  685:         verify_returns_error(Alice, #{box => inbox, 'after' => BadInt}, <<"bad-request">>),
  686:         verify_returns_error(Alice, #{box => inbox, index => 10}, <<"feature-not-implemented">>)
  687:     end).
  688: 
  689: pagination_has_priority_over_form_before_overrides_start(Config) ->
  690:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  691:                         fun(Alice, Bob, Kate, Mike) ->
  692:         % Several people write to Alice
  693:         #{Alice := [_ConvWithMike, ConvWithKate, ConvWithBob] = AliceConvs} =
  694:             inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  695:         % Alice has some messages in her inbox
  696:         inbox_helper:check_inbox(Alice, AliceConvs),
  697:         % Extract all the helper values
  698:         TimeAfterBob = ConvWithBob#conv.time_after,
  699:         TimeAfterKate = ConvWithKate#conv.time_after,
  700:         % We set start and end to return Convs with Kate,
  701:         % but override with Rsm to get Kate and Bob
  702:         Params = #{box => inbox, start => TimeAfterBob, 'end' => TimeAfterKate, before => <<>>},
  703:         inbox_helper:check_inbox(Alice, [ConvWithKate, ConvWithBob], Params)
  704:     end).
  705: 
  706: pagination_has_priority_over_form_after_overrides_end(Config) ->
  707:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  708:                         fun(Alice, Bob, Kate, Mike) ->
  709:         % Several people write to Alice
  710:         #{Alice := [ConvWithMike, ConvWithKate, ConvWithBob] = AliceConvs} =
  711:             inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  712:         % Alice has some messages in her inbox
  713:         inbox_helper:check_inbox(Alice, AliceConvs),
  714:         % Extract all the helper values
  715:         TimeAfterBob = ConvWithBob#conv.time_after,
  716:         TimeAfterKate = ConvWithKate#conv.time_after,
  717:         % We set start and end to return Convs with Kate,
  718:         % but override with Rsm to get Mike and Kate
  719:         Params = #{box => inbox, start => TimeAfterBob, 'end' => TimeAfterKate, 'after' => <<>>},
  720:         inbox_helper:check_inbox(Alice, [ConvWithMike, ConvWithKate], Params)
  721:     end).
  722: 
  723: can_paginate_forwards(Config) ->
  724:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  725:                         fun(Alice, Bob, Kate, Mike) ->
  726:         % Several people write to Alice
  727:         #{Alice := [ConvWithMike, ConvWithKate, ConvWithBob] = AliceConvs} =
  728:             inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  729:         % Alice has some messages in her inbox
  730:         inbox_helper:check_inbox(Alice, AliceConvs),
  731:         Params1 = #{box => inbox, limit => 1},
  732:         #{respond_iq := Iq} = inbox_helper:check_inbox(Alice, [ConvWithMike], Params1),
  733:         AfterPrevious = exml_query:path(Iq, [{element_with_ns, <<"fin">>, inbox_helper:inbox_ns()},
  734:                                              {element, <<"set">>}, {element, <<"last">>}, cdata]),
  735:         Params2 = #{box => inbox, 'after' => AfterPrevious, limit => 2},
  736:         inbox_helper:check_inbox(Alice, [ConvWithKate, ConvWithBob], Params2)
  737:     end).
  738: 
  739: can_paginate_backwards(Config) ->
  740:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  741:                         fun(Alice, Bob, Kate, Mike) ->
  742:         % Several people write to Alice
  743:         #{Alice := AliceConvs} =
  744:             inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  745:         % Alice has some messages in her inbox
  746:         inbox_helper:check_inbox(Alice, AliceConvs),
  747:         % Extract all the helper values
  748:         ConvWithBob = lists:keyfind(Bob, #conv.to, AliceConvs),
  749:         ConvWithKate = lists:keyfind(Kate, #conv.to, AliceConvs),
  750:         ConvWithMike = lists:keyfind(Mike, #conv.to, AliceConvs),
  751:         Params1 = #{box => inbox, limit => 1, before => <<>>},
  752:         #{respond_iq := Iq} = inbox_helper:check_inbox(Alice, [ConvWithBob], Params1),
  753:         BeforeLast = exml_query:path(Iq, [{element_with_ns, <<"fin">>, inbox_helper:inbox_ns()},
  754:                                           {element, <<"set">>}, {element, <<"first">>}, cdata]),
  755:         Params2 = #{box => inbox, before => BeforeLast},
  756:         inbox_helper:check_inbox(Alice, [ConvWithMike, ConvWithKate], Params2)
  757:     end).
  758: 
  759: max_queries_can_be_limited(Config) ->
  760:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  761:                         fun(Alice, Bob, Kate, Mike) ->
  762:         % Several people write to Alice
  763:         #{Alice := AliceConvs} =
  764:             inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  765:         % Alice has some messages in her inbox
  766:         inbox_helper:check_inbox(Alice, AliceConvs),
  767:         % Then Alice queries her inbox setting a limit to only one conversation,
  768:         % and she gets the newest one
  769:         ConvWithMike = lists:keyfind(Mike, #conv.to, AliceConvs),
  770:         Inbox1 = inbox_helper:check_inbox(Alice, [ConvWithMike], #{limit => 1, box => inbox}),
  771:         verify_rsm(Inbox1),
  772:         % And a limit to two also works fine
  773:         ConvWithKate = lists:keyfind(Kate, #conv.to, AliceConvs),
  774:         Inbox2 = inbox_helper:check_inbox(Alice, [ConvWithMike, ConvWithKate], #{limit => 2, box => inbox}),
  775:         verify_rsm(Inbox2)
  776:     end).
  777: 
  778: max_queries_can_fetch_ahead(Config) ->
  779:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}, {mike, 1}],
  780:                         fun(Alice, Bob, Kate, Mike) ->
  781:         #{Alice := AliceConvs} =
  782:             inbox_helper:given_conversations_between(Alice, [Bob, Kate, Mike]),
  783:         ConvWithBob = lists:keyfind(Bob, #conv.to, AliceConvs),
  784:         ConvWithKate = lists:keyfind(Kate, #conv.to, AliceConvs),
  785:         % ConvWithMike = lists:keyfind(Mike, #conv.to, AliceConvs),
  786:         TimeAfterKate = ConvWithKate#conv.time_after,
  787:         inbox_helper:check_inbox(Alice, [ConvWithKate, ConvWithBob],
  788:                   #{limit => 2, 'end' => TimeAfterKate, box => inbox})
  789:     end).
  790: 
  791: timestamp_is_not_reset_with_setting_properties(Config) ->
  792:     escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) ->
  793:         % Alice sends a message to Bob
  794:         inbox_helper:send_msg(Alice, Bob),
  795:         %% We capture the timestamp
  796:         #{respond_messages := [Item1]} = inbox_helper:get_inbox(Bob, #{count => 1}),
  797:         TStamp1 = inbox_helper:timestamp_from_item(Item1),
  798:         % Bob sets a bunch of properties
  799:         set_inbox_properties(Bob, Alice, [{read, true}, {mute, 24*?HOUR}]),
  800:         % Bob gets the inbox again, and timestamp should be the same
  801:         #{respond_messages := [Item2]} = inbox_helper:get_inbox(Bob, #{count => 1}),
  802:         TStamp2 = inbox_helper:timestamp_from_item(Item2),
  803:         ?assertEqual(TStamp1, TStamp2)
  804:   end).
  805: 
  806: % muclight
  807: groupchat_setunread_stanza_sets_inbox(Config) ->
  808:     escalus:story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) ->
  809:         %%% DATA
  810:         MsgBody = <<"marker time!">>,
  811:         AliceJid = inbox_helper:to_bare_lower(Alice),
  812:         BobJid = inbox_helper:to_bare_lower(Bob),
  813:         KateJid = inbox_helper:to_bare_lower(Kate),
  814:         RoomJid = muc_light_helper:room_bin_jid(?ROOM_MARKERS_RESET),
  815:         AliceRoomJid = <<RoomJid/binary,"/", AliceJid/binary>>,
  816:         %%% WHEN A MESSAGE IS SENT (two times the same message)
  817:         MsgStanza1 = escalus_stanza:set_id(escalus_stanza:groupchat_to(RoomJid, MsgBody), <<"id-1">>),
  818:         MsgStanza2 = escalus_stanza:set_id(escalus_stanza:groupchat_to(RoomJid, MsgBody), <<"id-2">>),
  819:         escalus:send(Alice, MsgStanza1),
  820:         inbox_helper:wait_for_groupchat_msg([Alice, Bob, Kate]),
  821:         escalus:send(Alice, MsgStanza2),
  822:         inbox_helper:wait_for_groupchat_msg([Alice, Bob, Kate]),
  823:         % verify that Bob has the message on inbox, reset it, and verify is still there but read
  824:         inbox_helper:check_inbox(Bob, [#conv{unread = 2, from = AliceRoomJid, to = BobJid, content = MsgBody}]),
  825:         set_inbox_properties(Bob, RoomJid, [{read, true}]),
  826:         inbox_helper:check_inbox(Bob, [#conv{unread = 0, from = AliceRoomJid, to = BobJid, content = MsgBody}]),
  827:         % Bob sets the inbox as unread again and has so in his inbox
  828:         set_inbox_properties(Bob, RoomJid, [{read, false}]),
  829:         inbox_helper:check_inbox(Bob, [#conv{unread = 1, from = AliceRoomJid, to = BobJid, content = MsgBody}]),
  830:         %% Alice has 0 unread messages because she was the sender
  831:         inbox_helper:check_inbox(Alice, [#conv{unread = 0, from = AliceRoomJid, to = AliceJid, content = MsgBody}]),
  832:         %% Kate still has unread messages, and setting the entry as unread keeps the count to two
  833:         set_inbox_properties(Kate, RoomJid, [{read, false}]),
  834:         inbox_helper:check_inbox(Kate, [#conv{unread = 2, from = AliceRoomJid, to = KateJid, content = MsgBody}]),
  835:         %% And nobody received any other stanza
  836:         inbox_helper:assert_has_no_stanzas([Alice, Bob, Kate])
  837:     end).
  838: 
  839: 
  840: %%--------------------------------------------------------------------
  841: %% Helpers
  842: %%--------------------------------------------------------------------
  843: 
  844: -type maybe_client() :: undefined | escalus:client().
  845: 
  846: verify_rsm(#{respond_iq := Iq}) ->
  847:     Set = exml_query:path(Iq, [{element_with_ns, <<"fin">>, inbox_helper:inbox_ns()},
  848:                                {element_with_ns, <<"set">>, ?NS_RSM}]),
  849:     ?assertNotEqual(undefined, exml_query:subelement(Set, <<"first">>)),
  850:     ?assertNotEqual(undefined, exml_query:subelement(Set, <<"last">>)).
  851: 
  852: to_int(Bin) ->
  853:     calendar:rfc3339_to_system_time(binary_to_list(Bin), [{unit, microsecond}]).
  854: 
  855: -spec query_properties(escalus:client(), escalus:client(), proplists:proplist()) -> [exml:element()].
  856: query_properties(From, To, Expected) ->
  857:     query_properties(From, To, Expected, none).
  858: 
  859: -spec query_properties(escalus:client(), escalus:client(), proplists:proplist(), none | full_entry) ->
  860:     [exml:element()].
  861: query_properties(From, To, Expected, FullEntry) ->
  862:     Stanza = make_inbox_get_properties(To, FullEntry),
  863:     escalus:send(From, Stanza),
  864:     Result = escalus:wait_for_stanza(From),
  865:     ?assert(escalus_pred:is_iq_result(Stanza, Result)),
  866:     [Props] = exml_query:subelements(Result, <<"query">>),
  867:     ?assertEqual(inbox_helper:inbox_ns_conversation(), exml_query:attr(Props, <<"xmlns">>)),
  868:     maybe_assert_full_entry(Props, FullEntry),
  869:     lists:foreach(fun({Key, Val}) -> assert_property(Props, Key, Val) end, Expected).
  870: 
  871: maybe_assert_full_entry(_, none) ->
  872:     ok;
  873: maybe_assert_full_entry(Props, full_entry) ->
  874:     ?assertNotEqual(undefined, exml_query:path(Props, [{element, <<"forwarded">>}])).
  875: 
  876: -spec make_inbox_get_properties(escalus:client(), boolean()) -> exml:element().
  877: make_inbox_get_properties(To, none) ->
  878:     Query = escalus_stanza:query_el(inbox_helper:inbox_ns_conversation(), jid_attr(To), []),
  879:     escalus_stanza:iq(<<"get">>, [Query]);
  880: make_inbox_get_properties(To, full_entry) ->
  881:     Attrs = [{<<"complete">>, <<"true">>} | jid_attr(To)],
  882:     Query = escalus_stanza:query_el(inbox_helper:inbox_ns_conversation(), Attrs, []),
  883:     escalus_stanza:iq(<<"get">>, [Query]).
  884: 
  885: -spec set_inbox_properties(escalus:client(), escalus:client(), proplists:proplist()) -> ok.
  886: set_inbox_properties(From, To, Properties) ->
  887:     set_inbox_properties(From, To, Properties, #{}).
  888: 
  889: -spec set_inbox_properties(escalus:client(), escalus:client(), proplists:proplist(), map()) -> ok.
  890: set_inbox_properties(From, To, Properties, QueryOpts) ->
  891:     Stanza = make_inbox_iq_request_with_query_id(To, Properties, QueryOpts),
  892:     escalus:send(From, Stanza),
  893:     check_message_with_properties(From, Stanza, Properties, QueryOpts),
  894:     check_iq_result_for_property(From, Stanza).
  895: 
  896: -spec check_message_with_properties(escalus:client(), exml:element(), proplists:proplist(), map()) -> ok.
  897: check_message_with_properties(From, Stanza, Properties, QueryOpts) ->
  898:     Message = escalus:wait_for_stanza(From),
  899:     ?assert(escalus_pred:is_message(Message)),
  900:     ?assert(has_same_id(Stanza, Message)),
  901:     [X] = exml_query:subelements(Message, <<"x">>),
  902:     ?assertEqual(inbox_helper:inbox_ns_conversation(), exml_query:attr(X, <<"xmlns">>)),
  903:     inbox_helper:maybe_check_queryid(X, QueryOpts),
  904:     % ?assertEqual(QueryId, exml_query:attr(X, <<"queryid">>)),
  905:     lists:foreach(fun({Key, Val}) -> assert_property(X, Key, Val) end, Properties).
  906: 
  907: -spec check_iq_result_for_property(escalus:client(), exml:element()) -> ok.
  908: check_iq_result_for_property(From, Stanza) ->
  909:     Result = escalus:wait_for_stanza(From),
  910:     ?assert(escalus_pred:is_iq_result(Stanza, Result)).
  911: 
  912: -spec make_inbox_iq_request(maybe_client(), atom(), atom()) -> exml:element().
  913: make_inbox_iq_request(ToClient, Key, Value) ->
  914:     make_inbox_iq_request(ToClient, [{Key, Value}]).
  915: 
  916: -spec make_inbox_iq_request(maybe_client(), proplists:proplist()) -> exml:element().
  917: make_inbox_iq_request(ToClient, Properties) when is_list(Properties) ->
  918:     make_inbox_iq_request_with_query_id(ToClient, Properties, #{}).
  919: 
  920: -spec make_inbox_iq_request_with_query_id(maybe_client(), proplists:proplist(), map()) -> exml:element().
  921: make_inbox_iq_request_with_query_id(ToClient, Properties, QueryId) ->
  922:     JidAttr = jid_attr(ToClient),
  923:     IqAttr = id_attr(QueryId),
  924:     Children = props_to_children(Properties),
  925:     Query = escalus_stanza:query_el(inbox_helper:inbox_ns_conversation(), JidAttr ++ IqAttr, Children),
  926:     inbox_iq(QueryId, Query).
  927: 
  928: inbox_iq(#{iq_id := IqId}, Query) ->
  929:     IQ = escalus_stanza:iq(<<"set">>, [Query]),
  930:     escalus_stanza:set_id(IQ, IqId);
  931: inbox_iq(_, Query) ->
  932:     escalus_stanza:iq(<<"set">>, [Query]).
  933: 
  934: assert_invalid_request(From, Stanza, Value) ->
  935:     inbox_helper:assert_invalid_form(From, Stanza, Value, Value).
  936: 
  937: -spec jid_attr(maybe_client()) -> proplists:proplist().
  938: jid_attr(undefined) -> [];
  939: jid_attr(Client) -> [{<<"jid">>, escalus_utils:get_short_jid(Client)}].
  940: 
  941: id_attr(#{queryid := QueryId}) -> [{<<"queryid">>, QueryId}];
  942: id_attr(_) -> [].
  943: 
  944: props_to_children(L) -> props_to_children(L, []).
  945: props_to_children([], Acc) -> Acc;
  946: props_to_children([{undefined, _} | Rest], Acc) ->
  947:     props_to_children(Rest, Acc);
  948: props_to_children([{Key, undefined} | Rest], Acc) ->
  949:     props_to_children(Rest, [#xmlel{name = Key} | Acc]);
  950: props_to_children([{Key, Value} | Rest], Acc) ->
  951:     props_to_children(Rest,
  952:       [#xmlel{name = to_bin(Key), children = [#xmlcdata{content = to_bin(Value)}]} | Acc]).
  953: 
  954: assert_property(X, read, Val) ->
  955:     ?assertEqual(to_bin(Val), exml_query:path(X, [{element, <<"read">>}, cdata]));
  956: assert_property(X, box, Val) ->
  957:     ?assertEqual(to_bin(Val), exml_query:path(X, [{element, <<"box">>}, cdata]));
  958: assert_property(X, archive, Val) ->
  959:     ?assertEqual(to_bin(Val), exml_query:path(X, [{element, <<"archive">>}, cdata]));
  960: assert_property(X, mute, 0) ->
  961:     ?assertEqual(0, binary_to_integer(exml_query:path(X, [{element, <<"mute">>}, cdata])));
  962: assert_property(X, mute, _) ->
  963:     Cal = binary_to_list(exml_query:path(X, [{element, <<"mute">>}, cdata])),
  964:     calendar:rfc3339_to_system_time(Cal, [{unit, microsecond}]).
  965: 
  966: -spec to_bin(term()) -> binary().
  967: to_bin(Value) when is_binary(Value) -> Value;
  968: to_bin(Value) when is_atom(Value) -> atom_to_binary(Value, utf8);
  969: to_bin(Value) when is_integer(Value) -> integer_to_binary(Value).
  970: 
  971: -spec has_same_id(exml:element(), exml:element()) -> boolean().
  972: has_same_id(OrigStanza, Stanza) ->
  973:     OrigId = exml_query:attr(OrigStanza, <<"id">>),
  974:     Id = exml_query:attr(Stanza, <<"id">>),
  975:     OrigId =:= Id.
  976: 
  977: muted_status(unmuted, Outer) ->
  978:     Res = exml_query:path(Outer, [{element, <<"result">>}, {element, <<"mute">>}, cdata]),
  979:     ?assertEqual(<<"0">>, Res);
  980: muted_status(MutedOrUnmuted, Outer) ->
  981:     GivenRfcTimestamp = exml_query:path(Outer, [{element, <<"result">>}, {element, <<"mute">>}, cdata]),
  982:     GivenMutedUntil = calendar:rfc3339_to_system_time(binary_to_list(GivenRfcTimestamp), [{offset, "Z"}, {unit, microsecond}]),
  983:     Now = erlang:system_time(microsecond),
  984:     case MutedOrUnmuted of
  985:         unmuted ->
  986:             ?assert(Now > GivenMutedUntil);
  987:         MutedDiff ->
  988:             Diff = erlang:convert_time_unit(MutedDiff, second, microsecond),
  989:             ?assert(Now + Diff < GivenMutedUntil)
  990:     end.
  991: 
  992: verify_returns_error(User, Params, Error) ->
  993:     Stanza = inbox_helper:make_inbox_stanza(Params),
  994:     escalus:send(User, Stanza),
  995:     [ResIQ] = escalus:wait_for_stanzas(User, 1),
  996:     escalus:assert(is_iq_error, [Stanza], ResIQ),
  997:     Type = exml_query:path(ResIQ, [{element, <<"error">>}, {element, Error}]),
  998:     ?assertNotEqual(undefined, Type).