1: -module(graphql_session_SUITE). 2: 3: -include_lib("common_test/include/ct.hrl"). 4: -include_lib("eunit/include/eunit.hrl"). 5: -include_lib("escalus/include/escalus.hrl"). 6: -include_lib("exml/include/exml.hrl"). 7: 8: -compile([export_all, nowarn_export_all]). 9: 10: -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). 11: -import(graphql_helper, [execute/3, execute_auth/2, get_listener_port/1, 12: get_listener_config/1, get_ok_value/2, get_err_msg/1]). 13: 14: suite() -> 15: require_rpc_nodes([mim]) ++ escalus:suite(). 16: 17: all() -> 18: [{group, user_session}, 19: {group, admin_session}]. 20: 21: groups() -> 22: [{user_session, [parallel], user_session_handler()}, 23: {admin_session, [], admin_session_handler()}]. 24: 25: user_session_handler() -> 26: [user_list_resources, 27: user_count_resources, 28: user_sessions_info]. 29: 30: admin_session_handler() -> 31: [admin_list_sessions, 32: admin_count_sessions, 33: admin_list_user_sessions, 34: admin_count_user_resources, 35: admin_get_user_resource, 36: admin_list_users_with_status, 37: admin_count_users_with_status, 38: admin_kick_session, 39: admin_set_presence, 40: admin_set_presence_away, 41: admin_set_presence_unavailable]. 42: 43: init_per_suite(Config) -> 44: Config2 = escalus:init_per_suite(Config), 45: dynamic_modules:save_modules(domain_helper:host_type(), Config2). 46: 47: end_per_suite(Config) -> 48: dynamic_modules:restore_modules(Config), 49: escalus:end_per_suite(Config). 50: 51: init_per_group(admin_session, Config) -> 52: Config1 = escalus:create_users(Config, escalus:get_users([alice, alice_bis, bob])), 53: graphql_helper:init_admin_handler(Config1); 54: init_per_group(user_session, Config) -> 55: [{schema_endpoint, user} | Config]. 56: 57: end_per_group(admin_session, Config) -> 58: escalus_fresh:clean(), 59: escalus:delete_users(Config, escalus:get_users([alice, alice_bis, bob])); 60: end_per_group(user_session, _Config) -> 61: escalus_fresh:clean(). 62: 63: init_per_testcase(CaseName, Config) -> 64: escalus:init_per_testcase(CaseName, Config). 65: 66: end_per_testcase(CaseName, Config) -> 67: escalus:end_per_testcase(CaseName, Config). 68: 69: %% Test cases 70: 71: user_list_resources(Config) -> 72: escalus:fresh_story_with_config(Config, [{alice, 2}], fun user_list_resources_story/3). 73: 74: user_list_resources_story(Config, Alice, Alice2) -> 75: Ep = ?config(schema_endpoint, Config), 76: Creds = make_creds(Alice), 77: 78: Query = <<"query{ session { listResources } }">>, 79: Result = execute(Ep, #{query => Query}, Creds), 80: 81: Path = [data, session, listResources], 82: ExpectedRes = [escalus_client:resource(Alice), escalus_client:resource(Alice2)], 83: ?assertMatch(ExpectedRes, lists:sort(get_ok_value(Path, Result))). 84: 85: user_count_resources(Config) -> 86: escalus:fresh_story_with_config(Config, [{alice, 3}], fun user_count_resources_story/4). 87: 88: user_count_resources_story(Config, Alice, _Alice2, _Alice3) -> 89: Ep = ?config(schema_endpoint, Config), 90: Creds = make_creds(Alice), 91: 92: Query = <<"query{ session { countResources } }">>, 93: Result = execute(Ep, #{query => Query}, Creds), 94: 95: Path = [data, session, countResources], 96: ?assertEqual(3, get_ok_value(Path, Result)). 97: 98: user_sessions_info(Config) -> 99: escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_sessions_info_story/2). 100: 101: user_sessions_info_story(Config, Alice) -> 102: Ep = ?config(schema_endpoint, Config), 103: Creds = make_creds(Alice), 104: 105: Query = <<"query{ session { listSessions { user } } }">>, 106: Result = execute(Ep, #{query => Query}, Creds), 107: ExpectedUser = escalus_utils:jid_to_lower(escalus_client:full_jid(Alice)), 108: 109: Path = [data, session, listSessions], 110: ?assertMatch([#{<<"user">> := ExpectedUser}], get_ok_value(Path, Result)). 111: 112: admin_list_sessions(Config) -> 113: escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}, {bob, 1}], 114: fun admin_list_sessions_story/4). 115: 116: admin_list_sessions_story(Config, _Alice, AliceB, _Bob) -> 117: BisDomain = escalus_client:server(AliceB), 118: Path = [data, session, listSessions], 119: % List all sessions 120: Res = execute_auth(list_sessions_body(null), Config), 121: Sessions = get_ok_value(Path, Res), 122: ?assertEqual(3, length(Sessions)), 123: % List sessions for a domain 124: Res2 = execute_auth(list_sessions_body(BisDomain), Config), 125: Sessions2 = get_ok_value(Path, Res2), 126: ?assertEqual(1, length(Sessions2)). 127: 128: admin_count_sessions(Config) -> 129: escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}, {bob, 1}], 130: fun admin_count_sessions_story/4). 131: 132: admin_count_sessions_story(Config, _Alice, AliceB, _Bob) -> 133: BisDomain = escalus_client:server(AliceB), 134: Path = [data, session, countSessions], 135: % Count all sessions 136: Res = execute_auth(count_sessions_body(null), Config), 137: Number = get_ok_value(Path, Res), 138: ?assertEqual(3, Number), 139: % Count sessions for a domain 140: Res2 = execute_auth(count_sessions_body(BisDomain), Config), 141: Number2 = get_ok_value(Path, Res2), 142: ?assertEqual(1, Number2). 143: 144: admin_list_user_sessions(Config) -> 145: escalus:fresh_story_with_config(Config, [{alice, 2}, {bob, 1}], 146: fun admin_list_user_sessions_story/4). 147: 148: admin_list_user_sessions_story(Config, Alice, Alice2, _Bob) -> 149: S1JID = escalus_client:full_jid(Alice), 150: S2JID = escalus_client:full_jid(Alice2), 151: Path = [data, session, listUserSessions], 152: Res = execute_auth(list_user_sessions_body(S1JID), Config), 153: ExpectedRes = lists:map(fun escalus_utils:jid_to_lower/1, [S1JID, S2JID]), 154: Sessions = get_ok_value(Path, Res), 155: ?assertEqual(2, length(Sessions)), 156: ?assert(users_match(ExpectedRes, Sessions)). 157: 158: admin_count_user_resources(Config) -> 159: escalus:fresh_story_with_config(Config, [{alice, 3}], fun admin_count_user_resources_story/4). 160: 161: admin_count_user_resources_story(Config, Alice, _Alice2, _Alice3) -> 162: Path = [data, session, countUserResources], 163: JID = escalus_client:full_jid(Alice), 164: Res = execute_auth(count_user_resources_body(JID), Config), 165: ?assertEqual(3, get_ok_value(Path, Res)). 166: 167: admin_get_user_resource(Config) -> 168: escalus:fresh_story_with_config(Config, [{alice, 3}], fun admin_get_user_resource_story/4). 169: 170: admin_get_user_resource_story(Config, Alice, Alice2, _Alice3) -> 171: Path = [data, session, getUserResource], 172: JID = escalus_client:short_jid(Alice), 173: % Provide a correct resource number 174: Res = execute_auth(get_user_resource_body(JID, 2), Config), 175: ?assertEqual(escalus_client:resource(Alice2), get_ok_value(Path, Res)), 176: % Provide a wrong resource number 177: Res2 = execute_auth(get_user_resource_body(JID, 4), Config), 178: ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"Wrong resource number">>)). 179: 180: admin_count_users_with_status(Config) -> 181: escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}], 182: fun admin_count_users_with_status_story/3). 183: 184: admin_count_users_with_status_story(Config, Alice, AliceB) -> 185: Path = [data, session, countUsersWithStatus], 186: AwayStatus = <<"away">>, 187: AwayPresence = escalus_stanza:presence_show(AwayStatus), 188: DndStatus = <<"dnd">>, 189: DndPresence = escalus_stanza:presence_show(DndStatus), 190: % Count users with away status globally 191: escalus_client:send(Alice, AwayPresence), 192: escalus_client:send(AliceB, AwayPresence), 193: Res = execute_auth(count_users_with_status_body(null, AwayStatus), Config), 194: ?assertEqual(2, get_ok_value(Path, Res)), 195: % Count users with away status for a domain 196: Res2 = execute_auth(count_users_with_status_body(domain_helper:domain(), AwayStatus), Config), 197: ?assertEqual(1, get_ok_value(Path, Res2)), 198: % Count users with dnd status globally 199: escalus_client:send(AliceB, DndPresence), 200: Res3 = execute_auth(count_users_with_status_body(null, DndStatus), Config), 201: ?assertEqual(1, get_ok_value(Path, Res3)). 202: 203: admin_list_users_with_status(Config) -> 204: escalus:fresh_story_with_config(Config, [{alice, 1}, {alice_bis, 1}], 205: fun admin_list_users_with_status_story/3). 206: 207: admin_list_users_with_status_story(Config, Alice, AliceB) -> 208: AliceJID = escalus_client:full_jid(Alice), 209: AliceBJID = escalus_client:full_jid(AliceB), 210: Path = [data, session, listUsersWithStatus], 211: AwayStatus = <<"away">>, 212: AwayPresence = escalus_stanza:presence_show(AwayStatus), 213: DndStatus = <<"dnd">>, 214: DndPresence = escalus_stanza:presence_show(DndStatus), 215: % List users with away status globally 216: escalus_client:send(Alice, AwayPresence), 217: escalus_client:send(AliceB, AwayPresence), 218: Res = execute_auth(list_users_with_status_body(null, AwayStatus), Config), 219: StatusUsers = get_ok_value(Path, Res), 220: ?assertEqual(2, length(StatusUsers)), 221: ?assert(users_match([AliceJID, AliceBJID], StatusUsers)), 222: % List users with away status for a domain 223: Res2 = execute_auth(list_users_with_status_body(domain_helper:domain(), AwayStatus), Config), 224: StatusUsers2 = get_ok_value(Path, Res2), 225: ?assertEqual(1, length(StatusUsers2)), 226: ?assert(users_match([AliceJID], StatusUsers2)), 227: % List users with dnd status globally 228: escalus_client:send(AliceB, DndPresence), 229: Res3 = execute_auth(list_users_with_status_body(null, DndStatus), Config), 230: StatusUsers3 = get_ok_value(Path, Res3), 231: ?assertEqual(1, length(StatusUsers3)), 232: ?assert(users_match([AliceBJID], StatusUsers3)). 233: 234: admin_kick_session(Config) -> 235: escalus:fresh_story_with_config(Config, [{alice, 2}], fun admin_kick_session_story/3). 236: 237: admin_kick_session_story(Config, Alice1, Alice2) -> 238: JIDA1 = escalus_client:full_jid(Alice1), 239: JIDA2 = escalus_client:full_jid(Alice2), 240: Reason = <<"Test kick">>, 241: Path = [data, session, kickUser, message], 242: Res = execute_auth(kick_session_body(JIDA1, Reason), Config), 243: % Kick an active session 244: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res), <<"kicked">>)), 245: ?assertEqual(JIDA1, get_ok_value([data, session, kickUser, jid], Res)), 246: % Try to kick an offline session 247: Res2 = execute_auth(kick_session_body(JIDA1, Reason), Config), 248: ?assertNotEqual(nomatch, binary:match(get_err_msg(Res2), <<"No active session">>)), 249: % Try to kick a session with JID without a resource 250: Res3 = execute_auth(kick_session_body(escalus_client:short_jid(Alice2), Reason), Config), 251: ?assertNotEqual(nomatch, binary:match(get_err_msg(Res3), <<"No active session">>)), 252: % Kick another active session 253: Res4 = execute_auth(kick_session_body(JIDA2, Reason), Config), 254: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res4), <<"kicked">>)). 255: 256: admin_set_presence(Config) -> 257: escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_set_presence_story/2). 258: 259: admin_set_presence_story(Config, Alice) -> 260: JID = escalus_client:full_jid(Alice), 261: ShortJID = escalus_client:short_jid(Alice), 262: Type = <<"AVAILABLE">>, 263: Show = <<"ONLINE">>, 264: Status = <<"Be right back">>, 265: Priority = 1, 266: % Send short JID 267: Res = execute_auth(set_presence_body(ShortJID, Type, Show, Status, Priority), Config), 268: ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), <<"resource is empty">>)), 269: % Send full JID 270: Path = [data, session, setPresence, message], 271: Res2 = execute_auth(set_presence_body(JID, Type, Show, Status, Priority), Config), 272: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res2), <<"set successfully">>)), 273: Presence = escalus:wait_for_stanza(Alice), 274: ?assertNot(escalus_pred:is_presence_with_show(<<"online">>, Presence)), 275: escalus:assert(is_presence_with_type, [<<"available">>], Presence), 276: escalus:assert(is_presence_with_status, [Status], Presence), 277: escalus:assert(is_presence_with_priority, [<<"1">>], Presence). 278: 279: admin_set_presence_away(Config) -> 280: escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_set_presence_away_story/2). 281: 282: admin_set_presence_away_story(Config, Alice) -> 283: JID = escalus_client:full_jid(Alice), 284: Type = <<"AVAILABLE">>, 285: Show = <<"AWAY">>, 286: Path = [data, session, setPresence, message], 287: Res2 = execute_auth(set_presence_body(JID, Type, Show, null, null), Config), 288: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res2), <<"set successfully">>)), 289: Presence = escalus:wait_for_stanza(Alice), 290: escalus:assert(is_presence_with_type, [<<"available">>], Presence), 291: escalus:assert(is_presence_with_show, [<<"away">>], Presence). 292: 293: admin_set_presence_unavailable(Config) -> 294: escalus:fresh_story_with_config(Config, [{alice, 2}], fun admin_set_presence_unavailable_story/3). 295: 296: admin_set_presence_unavailable_story(Config, Alice, Alice2) -> 297: JID = escalus_client:full_jid(Alice), 298: Type = <<"UNAVAILABLE">>, 299: Status = <<"I'm sleeping">>, 300: Path = [data, session, setPresence, message], 301: Res2 = execute_auth(set_presence_body(JID, Type, null, Status, null), Config), 302: ?assertNotEqual(nomatch, binary:match(get_ok_value(Path, Res2), <<"set successfully">>)), 303: Presence = escalus:wait_for_stanza(Alice2), 304: escalus:assert(is_presence_with_type, [<<"unavailable">>], Presence), 305: escalus:assert(is_presence_with_status, [Status], Presence). 306: 307: %% Helpers 308: 309: -spec users_match([jid:literal_jid()], [#{user := jid:literal_jid()}]) -> boolean(). 310: users_match(Expected, Actual) -> 311: lists:all(fun(#{<<"user">> := JID}) -> lists:member(JID, Expected) end, Actual). 312: 313: make_creds(#client{props = Props} = Client) -> 314: JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Client)), 315: Password = proplists:get_value(password, Props), 316: {JID, Password}. 317: 318: %% Request bodies 319: 320: list_sessions_body(Domain) -> 321: Query = <<"query Q1($domain: String) 322: { session { listSessions(domain: $domain) { user } } }">>, 323: OpName = <<"Q1">>, 324: Vars = #{<<"domain">> => Domain}, 325: #{query => Query, operationName => OpName, variables => Vars}. 326: 327: count_sessions_body(Domain) -> 328: Query = <<"query Q1($domain: String) 329: { session { countSessions(domain: $domain) } }">>, 330: OpName = <<"Q1">>, 331: Vars = #{<<"domain">> => Domain}, 332: #{query => Query, operationName => OpName, variables => Vars}. 333: 334: list_user_sessions_body(JID) -> 335: Query = <<"query Q1($user: JID!) 336: { session { listUserSessions(user: $user) { user } } }">>, 337: OpName = <<"Q1">>, 338: Vars = #{<<"user">> => JID}, 339: #{query => Query, operationName => OpName, variables => Vars}. 340: 341: count_user_resources_body(JID) -> 342: Query = <<"query Q1($user: JID!) 343: { session { countUserResources(user: $user) } }">>, 344: OpName = <<"Q1">>, 345: Vars = #{<<"user">> => JID}, 346: #{query => Query, operationName => OpName, variables => Vars}. 347: 348: get_user_resource_body(JID, Number) -> 349: Query = <<"query Q1($user: JID!, $number: Int!) 350: { session { getUserResource(user: $user, number: $number) } }">>, 351: OpName = <<"Q1">>, 352: Vars = #{<<"user">> => JID, <<"number">> => Number}, 353: #{query => Query, operationName => OpName, variables => Vars}. 354: 355: list_users_with_status_body(Domain, Status) -> 356: Query = <<"query Q1($domain: String, $status: String!) 357: { session { listUsersWithStatus(domain: $domain, status: $status) { user } } }">>, 358: OpName = <<"Q1">>, 359: Vars = #{<<"domain">> => Domain, <<"status">> => Status}, 360: #{query => Query, operationName => OpName, variables => Vars}. 361: 362: count_users_with_status_body(Domain, Status) -> 363: Query = <<"query Q1($domain: String, $status: String!) 364: { session { countUsersWithStatus(domain: $domain, status: $status) } }">>, 365: OpName = <<"Q1">>, 366: Vars = #{<<"domain">> => Domain, <<"status">> => Status}, 367: #{query => Query, operationName => OpName, variables => Vars}. 368: 369: kick_session_body(JID, Reason) -> 370: Query = <<"mutation M1($user: JID!, $reason: String!) 371: { session { kickUser(user: $user, reason: $reason) { jid message }} }">>, 372: OpName = <<"M1">>, 373: Vars = #{<<"user">> => JID, <<"reason">> => Reason}, 374: #{query => Query, operationName => OpName, variables => Vars}. 375: 376: set_presence_body(JID, Type, Show, Status, Priority) -> 377: Query = <<"mutation M1($user: JID!, $type: PresenceType!, $show: PresenceShow, $status: String, $priority: Int) 378: { session { setPresence(user: $user, type: $type, show: $show, status: $status, priority: $priority) 379: { message jid } } }">>, 380: OpName = <<"M1">>, 381: Vars = #{<<"user">> => JID, <<"type">> => Type, <<"show">> => Show, <<"status">> => Status, <<"priority">> => Priority}, 382: #{query => Query, operationName => OpName, variables => Vars}.