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