1: -module(graphql_stanza_SUITE).
    2: 
    3: -include_lib("common_test/include/ct.hrl").
    4: -include_lib("eunit/include/eunit.hrl").
    5: -include_lib("exml/include/exml.hrl").
    6: 
    7: -compile([export_all, nowarn_export_all]).
    8: 
    9: -import(distributed_helper, [require_rpc_nodes/1]).
   10: -import(graphql_helper, [execute_auth/2]).
   11: 
   12: suite() ->
   13:     require_rpc_nodes([mim]) ++ escalus:suite().
   14: 
   15: all() ->
   16:     [{group, admin_stanza_category}].
   17: 
   18: groups() ->
   19:     [{admin_stanza_category, [parallel], admin_stanza_category()}].
   20: 
   21: admin_stanza_category() ->
   22:     [send_message,
   23:      send_message_to_unparsable_jid,
   24:      send_message_headline,
   25:      send_stanza,
   26:      send_unparsable_stanza,
   27:      send_stanza_from_unknown_user,
   28:      send_stanza_from_unknown_domain,
   29:      get_last_messages,
   30:      get_last_messages_for_unknown_user,
   31:      get_last_messages_with,
   32:      get_last_messages_limit,
   33:      get_last_messages_limit_enforced,
   34:      get_last_messages_before].
   35: 
   36: init_per_suite(Config) ->
   37:     Config1 = escalus:init_per_suite(Config),
   38:     dynamic_modules:save_modules(domain_helper:host_type(), Config1).
   39: 
   40: end_per_suite(Config) ->
   41:     escalus_fresh:clean(),
   42:     dynamic_modules:restore_modules(Config),
   43:     escalus:end_per_suite(Config).
   44: 
   45: init_per_group(admin_stanza_category, Config) ->
   46:     Config2 = graphql_helper:init_admin_handler(Config),
   47:     init_mam(Config2);
   48: init_per_group(_, Config) ->
   49:     Config.
   50: 
   51: end_per_group(admin_stanza_category, _Config) ->
   52:     dynamic_modules:ensure_stopped(domain_helper:host_type(), [mod_mam_meta]);
   53: end_per_group(_, _Config) ->
   54:     ok.
   55: 
   56: init_per_testcase(CaseName, Config) ->
   57:     escalus:init_per_testcase(CaseName, Config).
   58: 
   59: end_per_testcase(CaseName, Config) ->
   60:     escalus:end_per_testcase(CaseName, Config).
   61: 
   62: init_mam(Config) when is_list(Config) ->
   63:     case mam_helper:backend() of
   64:         disabled ->
   65:             {skip, no_backend};
   66:         Backend ->
   67:             Mods = [{mod_mam_meta, [{backend, Backend}, {pm, []}]}],
   68:             dynamic_modules:ensure_modules(domain_helper:host_type(), Mods),
   69:             Config
   70:     end;
   71: init_mam(Other) ->
   72:     Other.
   73: 
   74: %% Test Cases
   75: 
   76: send_message(Config) ->
   77:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
   78:                                     fun send_message_story/3).
   79: 
   80: send_message_story(Config, Alice, Bob) ->
   81:     Body = <<"Hi!">>,
   82:     Vars = #{from => escalus_client:full_jid(Alice),
   83:              to => escalus_client:short_jid(Bob),
   84:              body => Body},
   85:     Res = ok_result(<<"stanza">>, <<"sendMessage">>,
   86:                     execute_send_message(Vars, Config)),
   87:     #{<<"id">> := MamID} = Res,
   88:     assert_not_empty(MamID).
   89: 
   90: send_message_to_unparsable_jid(Config) ->
   91:     escalus:fresh_story_with_config(Config, [{alice, 1}],
   92:                                     fun send_message_to_unparsable_jid_story/2).
   93: 
   94: send_message_to_unparsable_jid_story(Config, Alice) ->
   95:     Body = <<"Hi!">>,
   96:     Vars = #{from => escalus_client:full_jid(Alice),
   97:              to => <<"test@">>,
   98:              body => Body},
   99:     Res = execute_send_message(Vars, Config),
  100:     {{<<"400">>, <<"Bad Request">>}, #{<<"errors">> := Errors}} = Res,
  101:     [#{<<"extensions">> := #{<<"code">> := <<"input_coercion">>},
  102:        <<"message">> := ErrMsg, <<"path">> := ErrPath}] = Errors,
  103:     ?assertEqual([<<"M1">>, <<"to">>], ErrPath),
  104:     ?assertEqual(<<"Input coercion failed for type JID with value "
  105:                    "<<\"test@\">>. The reason it failed is: failed_to_parse_jid">>,
  106:                  ErrMsg).
  107: 
  108: send_message_headline(Config) ->
  109:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  110:                                     fun send_message_headline_story/3).
  111: 
  112: send_message_headline_story(Config, Alice, Bob) ->
  113:     Subject = <<"Welcome">>,
  114:     Body = <<"Hi!">>,
  115:     Vars = #{from => escalus_client:full_jid(Alice),
  116:              to => escalus_client:short_jid(Bob),
  117:              subject => Subject, body => Body},
  118:     Res = ok_result(<<"stanza">>, <<"sendMessageHeadLine">>,
  119:                     execute_send_message_headline(Vars, Config)),
  120:     #{<<"id">> := MamID} = Res,
  121:     %% Headlines are not stored in MAM
  122:     <<>> = MamID.
  123: 
  124: send_stanza(Config) ->
  125:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  126:                                     fun send_stanza_story/3).
  127: 
  128: send_stanza_story(Config, Alice, Bob) ->
  129:     Body = <<"Hi!">>,
  130:     Stanza = escalus_stanza:from(escalus_stanza:chat_to_short_jid(Bob, Body), Alice),
  131:     Vars = #{stanza => exml:to_binary(Stanza)},
  132:     Res = ok_result(<<"stanza">>, <<"sendStanza">>, execute_send_stanza(Vars, Config)),
  133:     #{<<"id">> := MamID} = Res,
  134:     assert_not_empty(MamID).
  135: 
  136: send_unparsable_stanza(Config) ->
  137:     Vars = #{stanza => <<"<test">>},
  138:     Res = execute_send_stanza(Vars, Config),
  139:     {{<<"400">>, <<"Bad Request">>}, #{<<"errors">> := Errors}} = Res,
  140:     [#{<<"extensions">> := #{<<"code">> := <<"input_coercion">>},
  141:        <<"message">> := ErrMsg, <<"path">> := ErrPath}] = Errors,
  142:     ?assertEqual(<<"Input coercion failed for type Stanza with value <<\"<test\">>. "
  143:                    "The reason it failed is: \"expected >\"">>, ErrMsg),
  144:     ?assertEqual([<<"M1">>, <<"stanza">>], ErrPath).
  145: 
  146: send_stanza_from_unknown_user(Config) ->
  147:     escalus:fresh_story_with_config(Config, [{bob, 1}],
  148:                                     fun send_stanza_from_unknown_user_story/2).
  149: 
  150: send_stanza_from_unknown_user_story(Config, Bob) ->
  151:     Body = <<"Hi!">>,
  152:     Server = escalus_client:server(Bob),
  153:     From = <<"YeeeAH@", Server/binary>>,
  154:     Stanza = escalus_stanza:from(escalus_stanza:chat_to_short_jid(Bob, Body), From),
  155:     Vars = #{stanza => exml:to_binary(Stanza)},
  156:     Res = execute_send_stanza(Vars, Config),
  157:     {{<<"200">>,<<"OK">>},
  158:          #{<<"data">> := #{<<"stanza">> := #{<<"sendStanza">> := null}},
  159:            <<"errors">> := Errors}} = Res,
  160:     [#{<<"extensions">> := #{<<"code">> := <<"unknown_user">>,
  161:                              <<"jid">> := From},
  162:        <<"message">> := ErrMsg, <<"path">> := ErrPath}] = Errors,
  163:     ?assertEqual(<<"Given user does not exist">>, ErrMsg),
  164:     ?assertEqual([<<"stanza">>, <<"sendStanza">>], ErrPath).
  165: 
  166: send_stanza_from_unknown_domain(Config) ->
  167:     escalus:fresh_story_with_config(Config, [{bob, 1}],
  168:                                     fun send_stanza_from_unknown_domain_story/2).
  169: 
  170: send_stanza_from_unknown_domain_story(Config, Bob) ->
  171:     Body = <<"Hi!">>,
  172:     From = <<"YeeeAH@oopsie">>,
  173:     Stanza = escalus_stanza:from(escalus_stanza:chat_to_short_jid(Bob, Body), From),
  174:     Vars = #{stanza => exml:to_binary(Stanza)},
  175:     Res = execute_send_stanza(Vars, Config),
  176:     {{<<"200">>, <<"OK">>},
  177:      #{<<"data">> := #{<<"stanza">> := #{<<"sendStanza">> := null}},
  178:        <<"errors">> := Errors}} = Res,
  179:     [#{<<"extensions">> := #{<<"code">> := <<"unknown_domain">>,
  180:                              <<"domain">> := <<"oopsie">>},
  181:        <<"message">> := ErrMsg, <<"path">> := ErrPath}] = Errors,
  182:     ?assertEqual([<<"stanza">>, <<"sendStanza">>], ErrPath),
  183:     ?assertEqual(<<"Given domain does not exist">>, ErrMsg).
  184: 
  185: get_last_messages(Config) ->
  186:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  187:                                     fun get_last_messages_story/3).
  188: 
  189: get_last_messages_story(Config, Alice, Bob) ->
  190:     send_message_story(Config, Alice, Bob),
  191:     mam_helper:wait_for_archive_size(Alice, 1),
  192:     mam_helper:wait_for_archive_size(Bob, 1),
  193:     Vars1 = #{caller => escalus_client:full_jid(Alice)},
  194:     Vars2 = #{caller => escalus_client:full_jid(Bob)},
  195:     Res1 = ok_result(<<"stanza">>, <<"getLastMessages">>,
  196:                      execute_get_last_messages(Vars1, Config)),
  197:     #{<<"stanzas">> := [M1], <<"limit">> := 50} = Res1,
  198:     check_stanza_map(M1, Alice),
  199:     Res2 = ok_result(<<"stanza">>, <<"getLastMessages">>,
  200:                      execute_get_last_messages(Vars2, Config)),
  201:     #{<<"stanzas">> := [M2], <<"limit">> := 50} = Res2,
  202:     check_stanza_map(M2, Alice).
  203: 
  204: get_last_messages_for_unknown_user(Config) ->
  205:     Domain = domain_helper:domain(),
  206:     Jid = <<"maybemaybebutnot@", Domain/binary>>,
  207:     Vars = #{caller => Jid},
  208:     Res = execute_get_last_messages(Vars, Config),
  209:     {{<<"200">>, <<"OK">>},
  210:      #{<<"data">> := #{<<"stanza">> := #{<<"getLastMessages">> := null}},
  211:        <<"errors">> := Errors}} = Res,
  212:     [#{<<"extensions">> := #{<<"code">> := <<"unknown_user">>,
  213:                              <<"jid">> := Jid},
  214:        <<"message">> := ErrMsg, <<"path">> := ErrPath}] = Errors,
  215:     ?assertEqual([<<"stanza">>, <<"getLastMessages">>], ErrPath),
  216:     ?assertEqual(<<"Given user does not exist">>, ErrMsg).
  217: 
  218: get_last_messages_with(Config) ->
  219:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}, {kate, 1}],
  220:                                     fun get_last_messages_with_story/4).
  221: 
  222: get_last_messages_with_story(Config, Alice, Bob, Kate) ->
  223:     send_message_story(Config, Alice, Bob),
  224:     mam_helper:wait_for_archive_size(Alice, 1),
  225:     send_message_story(Config, Kate, Alice),
  226:     mam_helper:wait_for_archive_size(Alice, 2),
  227:     Vars = #{caller => escalus_client:full_jid(Alice),
  228:              with => escalus_client:short_jid(Bob)},
  229:     Res = ok_result(<<"stanza">>, <<"getLastMessages">>,
  230:                     execute_get_last_messages(Vars, Config)),
  231:     #{<<"stanzas">> := [M1], <<"limit">> := 50} = Res,
  232:     check_stanza_map(M1, Alice).
  233: 
  234: get_last_messages_limit(Config) ->
  235:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  236:                                     fun get_last_messages_limit_story/3).
  237: 
  238: get_last_messages_limit_story(Config, Alice, Bob) ->
  239:     send_message_story(Config, Alice, Bob),
  240:     mam_helper:wait_for_archive_size(Alice, 1),
  241:     send_message_story(Config, Bob, Alice),
  242:     mam_helper:wait_for_archive_size(Alice, 2),
  243:     Vars = #{caller => escalus_client:full_jid(Alice), limit => 1},
  244:     Res = ok_result(<<"stanza">>, <<"getLastMessages">>,
  245:                     execute_get_last_messages(Vars, Config)),
  246:     #{<<"stanzas">> := [M1], <<"limit">> := 1} = Res,
  247:     check_stanza_map(M1, Bob).
  248: 
  249: get_last_messages_limit_enforced(Config) ->
  250:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  251:                                     fun get_last_messages_limit_enforced_story/3).
  252: 
  253: get_last_messages_limit_enforced_story(Config, Alice, Bob) ->
  254:     send_message_story(Config, Alice, Bob),
  255:     mam_helper:wait_for_archive_size(Alice, 1),
  256:     Vars = #{caller => escalus_client:full_jid(Alice), limit => 1000},
  257:     Res = ok_result(<<"stanza">>, <<"getLastMessages">>,
  258:                     execute_get_last_messages(Vars, Config)),
  259:     %% The actual limit is returned
  260:     #{<<"stanzas">> := [M1], <<"limit">> := 500} = Res,
  261:     check_stanza_map(M1, Alice).
  262: 
  263: get_last_messages_before(Config) ->
  264:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  265:                                     fun get_last_messages_before_story/3).
  266: 
  267: get_last_messages_before_story(Config, Alice, Bob) ->
  268:     send_message_story(Config, Alice, Bob),
  269:     mam_helper:wait_for_archive_size(Alice, 1),
  270:     send_message_story(Config, Bob, Alice),
  271:     mam_helper:wait_for_archive_size(Alice, 2),
  272:     send_message_story(Config, Bob, Alice),
  273:     mam_helper:wait_for_archive_size(Alice, 3),
  274:     Vars1 = #{caller => escalus_client:full_jid(Alice)},
  275:     Res1 = ok_result(<<"stanza">>, <<"getLastMessages">>,
  276:                      execute_get_last_messages(Vars1, Config)),
  277:     #{<<"stanzas">> := [M1, M2, _M3], <<"limit">> := 50} = Res1,
  278:     Vars2 = #{caller => escalus_client:full_jid(Alice),
  279:               before => maps:get(<<"timestamp">>, M2)},
  280:     Res2 = ok_result(<<"stanza">>, <<"getLastMessages">>,
  281:                      execute_get_last_messages(Vars2, Config)),
  282:     #{<<"stanzas">> := [M1, M2], <<"limit">> := 50} = Res2.
  283: 
  284: %% Helpers
  285: 
  286: execute_send_message(Vars, Config) ->
  287:     Q = <<"mutation M1($from: JID!, $to: JID!, $body: String!) "
  288:           "{ stanza { sendMessage(from: $from, to: $to, body: $body) { id } } }">>,
  289:     execute_auth(#{query => Q, variables => Vars,
  290:                    operationName => <<"M1">>}, Config).
  291: 
  292: execute_send_message_headline(Vars, Config) ->
  293:     Q = <<"mutation M1($from: JID!, $to: JID!, $subject: String, $body: String) "
  294:           "{ stanza { sendMessageHeadLine("
  295:             "from: $from, to: $to, subject: $subject, body: $body) { id } } }">>,
  296:     execute_auth(#{query => Q, variables => Vars,
  297:                    operationName => <<"M1">>}, Config).
  298: 
  299: execute_send_stanza(Vars, Config) ->
  300:     Q = <<"mutation M1($stanza: Stanza!) "
  301:           "{ stanza { sendStanza(stanza: $stanza) { id } } }">>,
  302:     execute_auth(#{query => Q, variables => Vars,
  303:                    operationName => <<"M1">>}, Config).
  304: 
  305: execute_get_last_messages(Vars, Config) ->
  306:     Q = <<"query Q1($caller: JID!, $with: JID, $limit: Int, $before: DateTime) "
  307:           "{ stanza { getLastMessages(caller: $caller, with: $with, "
  308:                  " limit: $limit, before: $before) "
  309:                      "{ stanzas { stanza_id stanza sender timestamp } limit } } }">>,
  310:     execute_auth(#{query => Q, variables => Vars,
  311:                    operationName => <<"Q1">>}, Config).
  312: 
  313: assert_not_empty(Bin) when byte_size(Bin) > 0 -> ok.
  314: 
  315: ok_result(What1, What2, {{<<"200">>, <<"OK">>}, #{<<"data">> := Data}}) ->
  316:     maps:get(What2, maps:get(What1, Data)).
  317: 
  318: check_stanza_map(#{<<"sender">> := SenderJID,
  319:                    <<"stanza">> := XML,
  320:                    <<"stanza_id">> := StanzaID,
  321:                    <<"timestamp">> := TS}, SenderClient) ->
  322:     ?assertEqual(escalus_utils:jid_to_lower(escalus_client:full_jid(SenderClient)),
  323:                  escalus_utils:jid_to_lower(SenderJID)),
  324:      true = byte_size(StanzaID) > 6,
  325:      true = is_integer(calendar:rfc3339_to_system_time(binary_to_list(TS))),
  326:      {ok, #xmlel{name = <<"message">>}} = exml:parse(XML).