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