1: -module(graphql_roster_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: 
    5: -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]).
    6: -import(graphql_helper, [execute/3, execute_auth/2, get_listener_port/1,
    7:                          get_listener_config/1, get_ok_value/2, get_err_msg/1,
    8:                          get_err_msg/2, make_creds/1]).
    9: 
   10: -include_lib("common_test/include/ct.hrl").
   11: -include_lib("eunit/include/eunit.hrl").
   12: -include_lib("exml/include/exml.hrl").
   13: -include_lib("escalus/include/escalus.hrl").
   14: -include_lib("../../include/mod_roster.hrl").
   15: 
   16: suite() ->
   17:     require_rpc_nodes([mim]) ++ escalus:suite().
   18: 
   19: all() ->
   20:     [{group, user_roster},
   21:      {group, admin_roster}].
   22: 
   23: groups() ->
   24:     [{user_roster, [], user_roster_handler()},
   25:      {admin_roster, [], admin_roster_handler()}].
   26: 
   27: user_roster_handler() ->
   28:     [user_add_and_delete_contact,
   29:      user_try_add_nonexistent_contact,
   30:      user_add_contacts,
   31:      user_try_delete_nonexistent_contact,
   32:      user_delete_contacts,
   33:      user_invite_accept_and_cancel_subscription,
   34:      user_decline_subscription_ask,
   35:      user_list_contacts,
   36:      user_get_contact,
   37:      user_get_nonexistent_contact
   38:     ].
   39: 
   40: admin_roster_handler() ->
   41:     [admin_add_and_delete_contact,
   42:      admin_try_add_nonexistent_contact,
   43:      admin_try_add_contact_to_nonexistent_user,
   44:      admin_try_add_contact_with_unknown_domain,
   45:      admin_add_contacts,
   46:      admin_try_delete_nonexistent_contact,
   47:      admin_try_delete_contact_with_unknown_domain,
   48:      admin_delete_contacts,
   49:      admin_invite_accept_and_cancel_subscription,
   50:      admin_decline_subscription_ask,
   51:      admin_try_subscribe_with_unknown_domain,
   52:      admin_set_mutual_subscription,
   53:      admin_set_mutual_subscription_try_connect_nonexistent_users,
   54:      admin_set_mutual_subscription_try_disconnect_nonexistent_users,
   55:      admin_subscribe_to_all,
   56:      admin_subscribe_to_all_with_wrong_user,
   57:      admin_subscribe_all_to_all,
   58:      admin_subscribe_all_to_all_with_wrong_user,
   59:      admin_list_contacts,
   60:      admin_list_contacts_wrong_user,
   61:      admin_get_contact,
   62:      admin_get_contact_wrong_user
   63:     ].
   64: 
   65: init_per_suite(Config) ->
   66:     Config2 = escalus:init_per_suite(Config),
   67:     dynamic_modules:save_modules(domain_helper:host_type(), Config2).
   68: 
   69: end_per_suite(Config) ->
   70:     dynamic_modules:restore_modules(Config),
   71:     escalus:end_per_suite(Config).
   72: 
   73: init_per_group(admin_roster, Config) ->
   74:     graphql_helper:init_admin_handler(Config);
   75: init_per_group(user_roster, Config) ->
   76:     [{schema_endpoint, user} | Config].
   77: 
   78: end_per_group(admin_roster, _Config) ->
   79:     escalus_fresh:clean();
   80: end_per_group(user_roster, _Config) ->
   81:     escalus_fresh:clean().
   82: 
   83: init_per_testcase(CaseName, Config) ->
   84:     escalus:init_per_testcase(CaseName, Config).
   85: 
   86: end_per_testcase(CaseName, Config) ->
   87:     escalus:end_per_testcase(CaseName, Config).
   88: 
   89: -define(ADD_CONTACT_PATH, [data, roster, addContact]).
   90: -define(ADD_CONTACTS_PATH, [data, roster, addContacts]).
   91: -define(DELETE_CONTACT_PATH, [data, roster, deleteContact]).
   92: -define(DELETE_CONTACTS_PATH, [data, roster, deleteContacts]).
   93: -define(LIST_CONTACTS_PATH, [data, roster, listContacts]).
   94: -define(GET_CONTACT_PATH, [data, roster, getContact]).
   95: -define(SUBSCRIBE_ALL_TO_ALL_PATH, [data, roster, subscribeAllToAll]).
   96: -define(SUBSCRIBE_TO_ALL_PATH, [data, roster, subscribeToAll]).
   97: -define(MUTUAL_SUBSCRIPTION_PATH, [data, roster, setMutualSubscription]).
   98: 
   99: -define(NONEXISTENT_DOMAIN_USER, <<"abc@abc">>).
  100: -define(NONEXISTENT_USER, <<"abc@", (domain_helper:domain())/binary>>).
  101: -define(NONEXISTENT_USER2, <<"abc2@", (domain_helper:domain())/binary>>).
  102: -define(DEFAULT_GROUPS, [<<"Family">>]).
  103: 
  104: %% Admin test cases
  105: 
  106: admin_add_and_delete_contact(Config) ->
  107:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  108:                                     fun admin_add_and_delete_contact_story/3).
  109: 
  110: admin_add_and_delete_contact_story(Config, Alice, Bob) ->
  111:     Res = admin_add_contact(Alice, Bob, Config),
  112:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?ADD_CONTACT_PATH, Res),
  113:                                           <<"successfully">>)),
  114:     check_contacts([Bob], Alice),
  115: 
  116:     Res2 = execute_auth(admin_delete_contact_body(Alice, Bob), Config),
  117:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?DELETE_CONTACT_PATH, Res2),
  118:                                           <<"successfully">>)),
  119:     check_contacts([], Alice).
  120: 
  121: admin_try_add_nonexistent_contact(Config) ->
  122:     escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_try_add_nonexistent_contact/2).
  123: 
  124: admin_try_add_nonexistent_contact(Config, Alice) ->
  125:     Contact = ?NONEXISTENT_DOMAIN_USER,
  126:     Res = admin_add_contact(Alice, Contact, Config),
  127:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), Contact)),
  128:     check_contacts([], Alice).
  129: 
  130: admin_try_add_contact_to_nonexistent_user(Config) ->
  131:     User = ?NONEXISTENT_USER,
  132:     Contact = ?NONEXISTENT_USER2,
  133:     Res = admin_add_contact(User, Contact, Config),
  134:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), User)),
  135:     check_contacts([], User).
  136: 
  137: admin_try_add_contact_with_unknown_domain(Config) ->
  138:     User = ?NONEXISTENT_DOMAIN_USER,
  139:     Contact = ?NONEXISTENT_USER2,
  140:     Res = admin_add_contact(User, Contact, Config),
  141:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  142: 
  143: admin_try_delete_nonexistent_contact(Config) ->
  144:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  145:                                     fun admin_try_delete_nonexistent_contact_story/3).
  146: 
  147: admin_try_delete_nonexistent_contact_story(Config, Alice, Bob) ->
  148:     Res = execute_auth(admin_delete_contact_body(Alice, Bob), Config),
  149:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not exist">>)).
  150: 
  151: admin_try_delete_contact_with_unknown_domain(Config) ->
  152:     User = ?NONEXISTENT_DOMAIN_USER,
  153:     Res = execute_auth(admin_delete_contact_body(User, User), Config),
  154:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  155: 
  156: admin_add_contacts(Config) ->
  157:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  158:                                     fun admin_add_contacts_story/3).
  159: 
  160: admin_add_contacts_story(Config, Alice, Bob) ->
  161:     Res = execute_auth(admin_add_contacts_body(Alice, [Bob, ?NONEXISTENT_DOMAIN_USER]), Config),
  162:     [R1, null] = get_ok_value(?ADD_CONTACTS_PATH, Res),
  163:     ?assertNotEqual(nomatch, binary:match(R1, <<"successfully">>)),
  164:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)).
  165: 
  166: admin_delete_contacts(Config) ->
  167:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  168:                                     fun admin_delete_contacts_story/3).
  169: 
  170: admin_delete_contacts_story(Config, Alice, Bob) ->
  171:     execute_auth(admin_add_contacts_body(Alice, [Bob]), Config),
  172:     Res = execute_auth(admin_delete_contacts_body(Alice, [Bob, ?NONEXISTENT_DOMAIN_USER]), Config),
  173:     [R1, null] = get_ok_value(?DELETE_CONTACTS_PATH, Res),
  174:     ?assertNotEqual(nomatch, binary:match(R1, <<"successfully">>)),
  175:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)).
  176: 
  177: admin_invite_accept_and_cancel_subscription(Config) ->
  178:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  179:                                     fun admin_invite_accept_and_cancel_subscription_story/3).
  180: 
  181: admin_invite_accept_and_cancel_subscription_story(Config, Alice, Bob) ->
  182:     % Add contacts
  183:     admin_add_contact(Alice, Bob, Config),
  184:     admin_add_contact(Bob, Alice, Config),
  185:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Bob, 1)),
  186:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice, 1)),
  187:     % Send invitation to subscribe 
  188:     execute_auth(admin_subscription_body(Alice, Bob, <<"INVITE">>), Config),
  189:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice)),
  190:     escalus:assert(is_presence_with_type, [<<"subscribe">>], escalus:wait_for_stanza(Bob)),
  191:     ?assertMatch(#roster{ask = out, subscription = none}, get_roster(Alice, Bob)),
  192:     % Accept invitation 
  193:     execute_auth(admin_subscription_body(Bob, Alice, <<"ACCEPT">>), Config),
  194:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Bob)),
  195:     IsSub = fun(S) -> escalus_pred:is_presence_with_type(<<"subscribed">>, S) end,
  196:     escalus:assert_many([is_roster_set, IsSub, is_presence],
  197:                         escalus:wait_for_stanzas(Alice, 3)),
  198:     ?assertMatch(#roster{ask = none, subscription = from}, get_roster(Bob, Alice)),
  199:     % Cancel subscription
  200:     execute_auth(admin_subscription_body(Alice, Bob, <<"CANCEL">>), Config),
  201:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice)),
  202:     ?assertMatch(#roster{ask = none, subscription = none}, get_roster(Alice, Bob)).
  203: 
  204: admin_decline_subscription_ask(Config) ->
  205:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  206:                                     fun admin_decline_subscription_ask_story/3).
  207: 
  208: admin_decline_subscription_ask_story(Config, Alice, Bob) ->
  209:     % Add contacts
  210:     execute_auth(admin_add_contact_body(Alice, Bob, null, null), Config),
  211:     execute_auth(admin_add_contact_body(Bob, Alice, null, null), Config),
  212:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Bob, 1)),
  213:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice, 1)),
  214:     % Send invitation to subscribe 
  215:     execute_auth(admin_subscription_body(Bob, Alice, <<"INVITE">>), Config),
  216:     ?assertMatch(#roster{ask = in, subscription = none}, get_roster(Alice, Bob)),
  217:     ?assertMatch(#roster{ask = out, subscription = none}, get_roster(Bob, Alice)),
  218:     % Decline the invitation 
  219:     execute_auth(admin_subscription_body(Alice, Bob, <<"DECLINE">>), Config),
  220:     ?assertMatch(does_not_exist, get_roster(Alice, Bob)),
  221:     ?assertMatch(#roster{ask = none, subscription = none}, get_roster(Bob, Alice)).
  222: 
  223: admin_try_subscribe_with_unknown_domain(Config) ->
  224:     Bob = ?NONEXISTENT_DOMAIN_USER,
  225:     Alice = ?NONEXISTENT_USER,
  226:     Res = execute_auth(admin_subscription_body(Bob, Alice, <<"INVITE">>), Config),
  227:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  228: 
  229: admin_set_mutual_subscription(Config) ->
  230:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  231:                                     fun admin_set_mutual_subscription_story/3).
  232: 
  233: admin_set_mutual_subscription_story(Config, Alice, Bob) ->
  234:     Res = execute_auth(admin_mutual_subscription_body(Alice, Bob, <<"CONNECT">>), Config),
  235:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?MUTUAL_SUBSCRIPTION_PATH, Res),
  236:                                           <<"successfully">>)),
  237:     ?assertMatch(#roster{ask = none, subscription = both}, get_roster(Alice, Bob)),
  238:     ?assertMatch(#roster{ask = none, subscription = both}, get_roster(Bob, Alice)),
  239: 
  240:     Res2 = execute_auth(admin_mutual_subscription_body(Alice, Bob, <<"DISCONNECT">>), Config),
  241:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?MUTUAL_SUBSCRIPTION_PATH, Res2),
  242:                                           <<"successfully">>)),
  243:     ?assertMatch(does_not_exist, get_roster(Alice, Bob)),
  244:     ?assertMatch(does_not_exist, get_roster(Bob, Alice)).
  245: 
  246: admin_set_mutual_subscription_try_connect_nonexistent_users(Config) ->
  247:     Alice = ?NONEXISTENT_DOMAIN_USER,
  248:     Bob = ?NONEXISTENT_USER,
  249:     Res = execute_auth(admin_mutual_subscription_body(Alice, Bob, <<"CONNECT">>), Config),
  250:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  251: 
  252: admin_set_mutual_subscription_try_disconnect_nonexistent_users(Config) ->
  253:     Alice = ?NONEXISTENT_DOMAIN_USER,
  254:     Bob = ?NONEXISTENT_USER,
  255:     Res = execute_auth(admin_mutual_subscription_body(Alice, Bob, <<"DISCONNECT">>), Config),
  256:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)).
  257: 
  258: admin_subscribe_to_all(Config) ->
  259:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}, {kate, 1}],
  260:                                     fun admin_subscribe_to_all_story/4).
  261: 
  262: admin_subscribe_to_all_story(Config, Alice, Bob, Kate) ->
  263:     Res = execute_auth(admin_subscribe_to_all_body(Alice, [Bob, Kate]), Config),
  264:     check_if_created_succ(?SUBSCRIBE_TO_ALL_PATH, Res),
  265: 
  266:     check_contacts([Bob, Kate], Alice),
  267:     check_contacts([Alice], Bob),
  268:     check_contacts([Alice], Kate).
  269: 
  270: admin_subscribe_to_all_with_wrong_user(Config) ->
  271:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  272:                                     fun admin_subscribe_to_all_with_wrong_user_story/3).
  273: 
  274: admin_subscribe_to_all_with_wrong_user_story(Config, Alice, Bob) ->
  275:     Kate = ?NONEXISTENT_DOMAIN_USER,
  276:     Res = execute_auth(admin_subscribe_to_all_body(Alice, [Bob, Kate]), Config),
  277:     check_if_created_succ(?SUBSCRIBE_TO_ALL_PATH, Res, [true, false]),
  278:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)),
  279: 
  280:     check_contacts([Bob], Alice),
  281:     check_contacts([Alice], Bob).
  282: 
  283: admin_subscribe_all_to_all(Config) ->
  284:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}, {kate, 1}],
  285:                                     fun admin_subscribe_all_to_all_story/4).
  286: 
  287: admin_subscribe_all_to_all_story(Config, Alice, Bob, Kate) ->
  288:     Res = execute_auth(admin_subscribe_all_to_all_body([Alice, Bob, Kate]), Config),
  289:     check_if_created_succ(?SUBSCRIBE_ALL_TO_ALL_PATH, Res),
  290: 
  291:     check_contacts([Bob, Kate], Alice),
  292:     check_contacts([Alice, Kate], Bob),
  293:     check_contacts([Alice, Bob], Kate).
  294: 
  295: admin_subscribe_all_to_all_with_wrong_user(Config) ->
  296:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  297:                                     fun admin_subscribe_all_to_all_with_wrong_user_story/3).
  298: 
  299: admin_subscribe_all_to_all_with_wrong_user_story(Config, Alice, Bob) ->
  300:     Kate = ?NONEXISTENT_DOMAIN_USER,
  301:     Res = execute_auth(admin_subscribe_all_to_all_body([Alice, Bob, Kate]), Config),
  302:     check_if_created_succ(?SUBSCRIBE_ALL_TO_ALL_PATH, Res, [true, false, false]),
  303:     ?assertNotEqual(nomatch, binary:match(get_err_msg(1, Res), <<"does not exist">>)),
  304:     ?assertNotEqual(nomatch, binary:match(get_err_msg(2, Res), <<"does not exist">>)),
  305: 
  306:     check_contacts([Bob], Alice),
  307:     check_contacts([Alice], Bob).
  308: 
  309: admin_list_contacts(Config) ->
  310:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  311:                                     fun admin_list_contacts_story/3).
  312: 
  313: admin_list_contacts_story(Config, Alice, Bob) ->
  314:     BobBin = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  315:     BobName = escalus_client:username(Bob),
  316:     admin_add_contact(Alice, Bob, Config),
  317:     Res = execute_auth(admin_list_contacts_body(Alice), Config),
  318:     [#{<<"subscription">> := <<"NONE">>, <<"ask">> := <<"NONE">>, <<"jid">> := BobBin,
  319:        <<"name">> := BobName, <<"groups">> := ?DEFAULT_GROUPS}] =
  320:         get_ok_value([data, roster, listContacts], Res).
  321: 
  322: admin_list_contacts_wrong_user(Config) ->
  323:     % User with a non-existent domain
  324:     Res = execute_auth(admin_list_contacts_body(?NONEXISTENT_DOMAIN_USER), Config),
  325:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)),
  326:     % Non-existent user with existent domain
  327:     Res2 = execute_auth(admin_list_contacts_body(?NONEXISTENT_USER), Config),
  328:     ?assertEqual([], get_ok_value(?LIST_CONTACTS_PATH, Res2)).
  329: 
  330: admin_get_contact(Config) ->
  331:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  332:                                     fun admin_get_contact_story/3).
  333: 
  334: admin_get_contact_story(Config, Alice, Bob) ->
  335:     BobBin = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  336:     BobName = escalus_client:username(Bob),
  337:     admin_add_contact(Alice, Bob, Config),
  338:     Res = execute_auth(admin_get_contact_body(Alice, Bob), Config),
  339:     #{<<"subscription">> := <<"NONE">>, <<"ask">> := <<"NONE">>, <<"jid">> := BobBin,
  340:       <<"name">> := BobName, <<"groups">> := ?DEFAULT_GROUPS} =
  341:         get_ok_value([data, roster, getContact], Res).
  342: 
  343: admin_get_contact_wrong_user(Config) ->
  344:     % User with a non-existent domain
  345:     Res = execute_auth(admin_get_contact_body(?NONEXISTENT_DOMAIN_USER, ?NONEXISTENT_USER), Config),
  346:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not found">>)),
  347:     % Non-existent user with existent domain
  348:     Res2 = execute_auth(admin_get_contact_body(?NONEXISTENT_USER, ?NONEXISTENT_USER), Config),
  349:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"does not exist">>)).
  350: 
  351: %% User test cases
  352: 
  353: user_add_and_delete_contact(Config) ->
  354:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  355:                                     fun user_add_and_delete_contact_story/3).
  356: 
  357: user_add_and_delete_contact_story(Config, Alice, Bob) ->
  358:     % Add a new contact
  359:     Res = user_add_contact(Alice, Bob, Config),
  360:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?ADD_CONTACT_PATH, Res),
  361:                                           <<"successfully">>)),
  362:     check_contacts([Bob], Alice),
  363:     % Delete a contact
  364:     Res2 = execute_user(user_delete_contact_body(Bob), Alice, Config),
  365:     ?assertNotEqual(nomatch, binary:match(get_ok_value(?DELETE_CONTACT_PATH, Res2),
  366:                                           <<"successfully">>)),
  367:     check_contacts([], Alice).
  368: 
  369: user_try_add_nonexistent_contact(Config) ->
  370:     escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_try_add_nonexistent_contact/2).
  371: 
  372: user_try_add_nonexistent_contact(Config, Alice) ->
  373:     Contact = ?NONEXISTENT_DOMAIN_USER,
  374:     Res = user_add_contact(Alice, Contact, Config),
  375:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), Contact)),
  376:     check_contacts([], Alice).
  377: 
  378: user_add_contacts(Config) ->
  379:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  380:                                     fun user_add_contacts_story/3).
  381: 
  382: user_add_contacts_story(Config, Alice, Bob) ->
  383:     Res = execute_user(user_add_contacts_body([Bob, ?NONEXISTENT_DOMAIN_USER]), Alice, Config),
  384:     [R1, null] = get_ok_value(?ADD_CONTACTS_PATH, Res),
  385:     ?assertNotEqual(nomatch, binary:match(R1, <<"successfully">>)),
  386:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)).
  387: 
  388: user_try_delete_nonexistent_contact(Config) ->
  389:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  390:                                     fun user_try_delete_nonexistent_contact_story/3).
  391: 
  392: user_try_delete_nonexistent_contact_story(Config, Alice, Bob) ->
  393:     Res = execute_user(user_delete_contact_body(Bob), Alice, Config),
  394:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"not exist">>)).
  395: 
  396: user_delete_contacts(Config) ->
  397:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  398:                                     fun user_delete_contacts_story/3).
  399: 
  400: user_delete_contacts_story(Config, Alice, Bob) ->
  401:     user_add_contact(Alice, Bob, Config),
  402: 
  403:     Res = execute_user(user_delete_contacts_body([Bob, ?NONEXISTENT_DOMAIN_USER]), Alice, Config),
  404:     [R1, null] = get_ok_value(?DELETE_CONTACTS_PATH, Res),
  405:     ?assertNotEqual(nomatch, binary:match(R1, <<"successfully">>)),
  406:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)).
  407: 
  408: user_invite_accept_and_cancel_subscription(Config) ->
  409:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  410:                                     fun user_invite_accept_and_cancel_subscription_story/3).
  411: 
  412: user_invite_accept_and_cancel_subscription_story(Config, Alice, Bob) ->
  413:     % Add contacts
  414:     user_add_contact(Alice, Bob, Config),
  415:     user_add_contact(Bob, Alice, Config),
  416:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Bob, 1)),
  417:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice, 1)),
  418:     % Send invitation to subscribe 
  419:     execute_user(user_subscription_body(Bob, <<"INVITE">>), Alice, Config),
  420:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice)),
  421:     escalus:assert(is_presence_with_type, [<<"subscribe">>], escalus:wait_for_stanza(Bob)),
  422:     ?assertMatch(#roster{ask = out, subscription = none}, get_roster(Alice, Bob)),
  423:     % Accept invitation 
  424:     execute_user(user_subscription_body(Alice, <<"ACCEPT">>), Bob, Config),
  425:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Bob)),
  426:     IsSub = fun(S) -> escalus_pred:is_presence_with_type(<<"subscribed">>, S) end,
  427:     escalus:assert_many([is_roster_set, IsSub, is_presence],
  428:                         escalus:wait_for_stanzas(Alice, 3)),
  429:     ?assertMatch(#roster{ask = none, subscription = from}, get_roster(Bob, Alice)),
  430:     % Cancel subscription
  431:     execute_user(user_subscription_body(Bob, <<"CANCEL">>), Alice, Config),
  432:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice)),
  433:     ?assertMatch(#roster{ask = none, subscription = none}, get_roster(Alice, Bob)).
  434: 
  435: user_decline_subscription_ask(Config) ->
  436:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  437:                                     fun user_decline_subscription_ask_story/3).
  438: 
  439: user_decline_subscription_ask_story(Config, Alice, Bob) ->
  440:     % Add contacts
  441:     user_add_contact(Alice, Bob, Config),
  442:     user_add_contact(Bob, Alice, Config),
  443:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Bob, 1)),
  444:     escalus:assert(is_roster_set, escalus:wait_for_stanza(Alice, 1)),
  445:     % Send invitation to subscribe 
  446:     execute_user(user_subscription_body(Alice, <<"INVITE">>), Bob, Config),
  447:     ?assertMatch(#roster{ask = in, subscription = none}, get_roster(Alice, Bob)),
  448:     ?assertMatch(#roster{ask = out, subscription = none}, get_roster(Bob, Alice)),
  449:     % Decline the invitation 
  450:     execute_user(user_subscription_body(Bob, <<"DECLINE">>), Alice, Config),
  451:     ?assertMatch(does_not_exist, get_roster(Alice, Bob)),
  452:     ?assertMatch(#roster{ask = none, subscription = none}, get_roster(Bob, Alice)).
  453: 
  454: user_list_contacts(Config) ->
  455:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  456:                                     fun user_list_contacts_story/3).
  457: 
  458: user_list_contacts_story(Config, Alice, Bob) ->
  459:     BobBin = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  460:     Name = <<"Bobek">>,
  461:     execute_user(user_add_contact_body(Bob, Name, ?DEFAULT_GROUPS), Alice, Config),
  462:     Res = execute_user(user_list_contacts_body(), Alice, Config),
  463:     [#{<<"subscription">> := <<"NONE">>, <<"ask">> := <<"NONE">>, <<"jid">> := BobBin,
  464:        <<"name">> := Name, <<"groups">> := ?DEFAULT_GROUPS}] =
  465:         get_ok_value(?LIST_CONTACTS_PATH, Res).
  466: 
  467: user_get_contact(Config) ->
  468:     escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}],
  469:                                     fun user_get_contact_story/3).
  470: 
  471: user_get_contact_story(Config, Alice, Bob) ->
  472:     BobBin = escalus_utils:jid_to_lower(escalus_client:short_jid(Bob)),
  473:     Name = <<"Bobek">>,
  474:     execute_user(user_add_contact_body(Bob, Name, ?DEFAULT_GROUPS), Alice, Config),
  475:     Res = execute_user(user_get_contact_body(Bob), Alice, Config),
  476:     #{<<"subscription">> := <<"NONE">>, <<"ask">> := <<"NONE">>, <<"jid">> := BobBin,
  477:       <<"name">> := Name, <<"groups">> := ?DEFAULT_GROUPS} =
  478:         get_ok_value(?GET_CONTACT_PATH, Res).
  479: 
  480: user_get_nonexistent_contact(Config) ->
  481:     escalus:fresh_story_with_config(Config, [{alice, 1}],
  482:                                     fun user_get_nonexistent_contact_story/2).
  483: 
  484: user_get_nonexistent_contact_story(Config, Alice) ->
  485:     Res = execute_user(user_get_contact_body(?NONEXISTENT_DOMAIN_USER), Alice, Config),
  486:     ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"does not exist">>)).
  487: 
  488: % Helpers
  489: 
  490: admin_add_contact(User, Contact, Config) ->
  491:     Name = escalus_utils:get_username(Contact),
  492:     execute_auth(admin_add_contact_body(User, Contact, Name, ?DEFAULT_GROUPS), Config).
  493: 
  494: user_add_contact(User, Contact, Config) ->
  495:     Name = escalus_utils:get_username(Contact),
  496:     execute_user(user_add_contact_body(Contact, Name, ?DEFAULT_GROUPS), User, Config).
  497: 
  498: execute_user(Body, User, Config) ->
  499:     Ep = ?config(schema_endpoint, Config),
  500:     Creds = make_creds(User),
  501:     execute(Ep, Body, Creds).
  502: 
  503: check_contacts(ContactClients, User) ->
  504:     Expected = [escalus_utils:jid_to_lower(escalus_client:short_jid(Client))
  505:                 || Client <- ContactClients],
  506:     ExpectedNames = [escalus_client:username(Client) || Client <- ContactClients],
  507:     ActualContacts = get_roster(User),
  508:     Actual = [ jid:to_binary(JID) || #roster{jid = JID} <- ActualContacts],
  509:     ActualNames = [ Name || #roster{name = Name} <- ActualContacts],
  510:     ?assertEqual(lists:sort(Expected), lists:sort(Actual)),
  511:     ?assertEqual(lists:sort(ExpectedNames), lists:sort(ActualNames)),
  512:     [?assertEqual(?DEFAULT_GROUPS, Groups) || #roster{groups = Groups} <- ActualContacts].
  513: 
  514: check_if_created_succ(Path, Res) ->
  515:     check_if_created_succ(Path, Res, null).
  516: 
  517: check_if_created_succ(Path, Res, ExpectedOkValue) ->
  518:     OkList = get_ok_value(Path, Res),
  519: 
  520:     OkList2 = case ExpectedOkValue of
  521:                   null ->
  522:                       [{Msg, true} || Msg <- OkList];
  523:                   _ when is_list(ExpectedOkValue) ->
  524:                       lists:zip(OkList, ExpectedOkValue)
  525:               end,
  526:     [?assertNotEqual(nomatch, binary:match(Msg, <<"created successfully">>))
  527:      || {Msg, ShouldHaveValue} <- OkList2, ShouldHaveValue].
  528: 
  529: get_roster(User) ->
  530:     {ok, Roster} = rpc(mim(), mod_roster_api, list_contacts, [user_to_jid(User)]),
  531:     Roster.
  532: 
  533: get_roster(User, Contact) ->
  534:     rpc(mim(), mod_roster, get_roster_entry,
  535:         [domain_helper:host_type(),
  536:          user_to_jid(User),
  537:          jid:to_lower(user_to_jid(Contact)),
  538:          full]).
  539: 
  540: user_to_bin(#client{jid = JID} = Client) -> escalus_client:short_jid(Client);
  541: user_to_bin(Bin) when is_binary(Bin) -> Bin.
  542: 
  543: user_to_jid(#client{jid = JID}) -> jid:to_bare(jid:from_binary(JID));
  544: user_to_jid(Bin) when is_binary(Bin) -> jid:from_binary(Bin).
  545: 
  546: make_contact(Users) when is_list(Users) ->
  547:     [make_contact(U) || U <- Users];
  548: make_contact(U) ->
  549:     #{jid => user_to_bin(U), name => escalus_utils:get_username(U), groups => ?DEFAULT_GROUPS}.
  550: 
  551: %% Request bodies
  552: 
  553: admin_add_contact_body(User, Contact, Name, Groups) ->
  554:     Query = <<"mutation M1($user: JID!, $contact: JID!, $name: String, $groups: [String!])
  555:               { roster { addContact(user: $user, contact: $contact, name: $name, groups: $groups) } }">>,
  556:     OpName = <<"M1">>,
  557:     Vars = #{user => user_to_bin(User), contact => user_to_bin(Contact),
  558:              name => Name, groups => Groups},
  559:     #{query => Query, operationName => OpName, variables => Vars}.
  560: 
  561: admin_add_contacts_body(User, Contacts) ->
  562:     Query = <<"mutation M1($user: JID!, $contacts: [ContactInput!]!)
  563:               { roster { addContacts(user: $user, contacts: $contacts) } }">>,
  564:     OpName = <<"M1">>,
  565:     Vars = #{user => user_to_bin(User), contacts => make_contact(Contacts)},
  566:     #{query => Query, operationName => OpName, variables => Vars}.
  567: 
  568: admin_delete_contact_body(User, Contact) ->
  569:     Query = <<"mutation M1($user: JID!, $contact: JID!)
  570:               { roster { deleteContact(user: $user, contact: $contact) } }">>,
  571:     OpName = <<"M1">>,
  572:     Vars = #{user => user_to_bin(User), contact => user_to_bin(Contact)},
  573:     #{query => Query, operationName => OpName, variables => Vars}.
  574: 
  575: admin_delete_contacts_body(User, Contacts) ->
  576:     Query = <<"mutation M1($user: JID!, $contacts: [JID!]!)
  577:               { roster { deleteContacts(user: $user, contacts: $contacts) } }">>,
  578:     OpName = <<"M1">>,
  579:     Vars = #{user => user_to_bin(User), contacts => [user_to_bin(C) || C <- Contacts]},
  580:     #{query => Query, operationName => OpName, variables => Vars}.
  581: 
  582: admin_subscription_body(User, Contact, Action) ->
  583:     Query = <<"mutation M1($user: JID!, $contact: JID!, $action: SubAction!)
  584:               { roster { subscription(user: $user, contact: $contact, action: $action) } }">>,
  585:     OpName = <<"M1">>,
  586:     Vars = #{user => user_to_bin(User), contact => user_to_bin(Contact), action => Action},
  587:     #{query => Query, operationName => OpName, variables => Vars}.
  588: 
  589: admin_mutual_subscription_body(User, Contact, Action) ->
  590:     Query = <<"mutation M1($userA: JID!, $userB: JID!, $action: MutualSubAction!)
  591:               { roster { setMutualSubscription(userA: $userA, userB: $userB, action: $action) } }">>,
  592:     OpName = <<"M1">>,
  593:     Vars = #{userA => user_to_bin(User), userB => user_to_bin(Contact), action => Action},
  594:     #{query => Query, operationName => OpName, variables => Vars}.
  595: 
  596: admin_subscribe_to_all_body(User, Contacts) ->
  597:     Query = <<"mutation M1($user: ContactInput!, $contacts: [ContactInput!]!)
  598:               { roster { subscribeToAll(user: $user, contacts: $contacts) } }">>,
  599:     OpName = <<"M1">>,
  600:     Vars = #{user => make_contact(User), contacts => make_contact(Contacts)},
  601:     #{query => Query, operationName => OpName, variables => Vars}.
  602: 
  603: admin_subscribe_all_to_all_body(Users) ->
  604:     Query = <<"mutation M1($contacts: [ContactInput!]!)
  605:               { roster { subscribeAllToAll(contacts: $contacts) } }">>,
  606:     OpName = <<"M1">>,
  607:     Vars = #{contacts => make_contact(Users)},
  608:     #{query => Query, operationName => OpName, variables => Vars}.
  609: 
  610: admin_list_contacts_body(User) ->
  611:     Query = <<"query Q1($user: JID!)
  612:               { roster { listContacts(user: $user)
  613:               { jid subscription ask name groups} } }">>,
  614:     OpName = <<"Q1">>,
  615:     Vars = #{user => user_to_bin(User)},
  616:     #{query => Query, operationName => OpName, variables => Vars}.
  617: 
  618: admin_get_contact_body(User, Contact) ->
  619:     Query = <<"query Q1($user: JID!, $contact: JID!)
  620:               { roster { getContact(user: $user, contact: $contact)
  621:               { jid subscription ask name groups} } }">>,
  622:     OpName = <<"Q1">>,
  623:     Vars = #{user => user_to_bin(User), contact => user_to_bin(Contact)},
  624:     #{query => Query, operationName => OpName, variables => Vars}.
  625: 
  626: user_add_contact_body(Contact, Name, Groups) ->
  627:     Query = <<"mutation M1($contact: JID!, $name: String, $groups: [String!])
  628:               { roster { addContact(contact: $contact, name: $name, groups: $groups) } }">>,
  629:     OpName = <<"M1">>,
  630:     Vars = #{contact => user_to_bin(Contact), name => Name, groups => Groups},
  631:     #{query => Query, operationName => OpName, variables => Vars}.
  632: 
  633: user_add_contacts_body(Contacts) ->
  634:     Query = <<"mutation M1($contacts: [ContactInput!]!)
  635:               { roster { addContacts(contacts: $contacts) } }">>,
  636:     OpName = <<"M1">>,
  637:     Vars = #{contacts => make_contact(Contacts)},
  638:     #{query => Query, operationName => OpName, variables => Vars}.
  639: 
  640: user_delete_contact_body(Contact) ->
  641:     Query = <<"mutation M1($contact: JID!)
  642:               { roster { deleteContact(contact: $contact) } }">>,
  643:     OpName = <<"M1">>,
  644:     Vars = #{contact => user_to_bin(Contact)},
  645:     #{query => Query, operationName => OpName, variables => Vars}.
  646: 
  647: user_delete_contacts_body(Contacts) ->
  648:     Query = <<"mutation M1($contacts: [JID!]!)
  649:               { roster { deleteContacts(contacts: $contacts) } }">>,
  650:     OpName = <<"M1">>,
  651:     Vars = #{contacts => [user_to_bin(C) || C <- Contacts]},
  652:     #{query => Query, operationName => OpName, variables => Vars}.
  653: 
  654: user_subscription_body(Contact, Action) ->
  655:     Query = <<"mutation M1($contact: JID!, $action: SubAction!)
  656:               { roster { subscription(contact: $contact, action: $action) } }">>,
  657:     OpName = <<"M1">>,
  658:     Vars = #{contact => user_to_bin(Contact), action => Action},
  659:     #{query => Query, operationName => OpName, variables => Vars}.
  660: 
  661: user_list_contacts_body() ->
  662:     Query = <<"query Q1 { roster { listContacts
  663:               { jid subscription ask name groups} } }">>,
  664:     #{query => Query, operationName => <<"Q1">>, variables => #{}}.
  665: 
  666: user_get_contact_body(Contact) ->
  667:     Query = <<"query Q1($contact: JID!)
  668:               { roster { getContact(contact: $contact)
  669:               { jid subscription ask name groups} } }">>,
  670:     OpName = <<"Q1">>,
  671:     Vars = #{contact => user_to_bin(Contact)},
  672:     #{query => Query, operationName => OpName, variables => Vars}.