1: -module(graphql_last_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_user/3, execute_auth/2, user_to_bin/1, user_to_jid/1, 7: get_ok_value/2, get_err_msg/1, get_err_code/1]). 8: 9: -include_lib("eunit/include/eunit.hrl"). 10: 11: -define(assertErrMsg(Res, ContainsPart), assert_err_msg(ContainsPart, Res)). 12: -define(assertErrCode(Res, Code), assert_err_code(Code, Res)). 13: 14: -define(NONEXISTENT_JID, <<"user@user.com">>). 15: -define(DEFAULT_DT, <<"2022-04-17T12:58:30.000000Z">>). 16: 17: suite() -> 18: require_rpc_nodes([mim]) ++ escalus:suite(). 19: 20: all() -> 21: [{group, user_last}, 22: {group, admin_last}, 23: {group, admin_last_old_users}]. 24: 25: groups() -> 26: [{user_last, [parallel], user_last_handler()}, 27: {admin_last, [parallel], admin_last_handler()}, 28: {admin_last_old_users, [], admin_old_users_handler()}]. 29: 30: user_last_handler() -> 31: [user_set_last, 32: user_get_last, 33: user_get_other_user_last]. 34: 35: admin_last_handler() -> 36: [admin_set_last, 37: admin_try_set_nonexistent_user_last, 38: admin_get_last, 39: admin_get_nonexistent_user_last, 40: admin_try_get_nonexistent_last, 41: admin_count_active_users, 42: admin_try_count_nonexistent_domain_active_users]. 43: 44: admin_old_users_handler() -> 45: [admin_list_old_users_domain, 46: admin_try_list_old_users_nonexistent_domain, 47: admin_list_old_users_global, 48: admin_remove_old_users_domain, 49: admin_try_remove_old_users_nonexistent_domain, 50: admin_remove_old_users_global, 51: admin_user_without_last_info_is_old_user, 52: admin_logged_user_is_not_old_user]. 53: 54: init_per_suite(Config) -> 55: HostType = domain_helper:host_type(), 56: SecHostType = domain_helper:secondary_host_type(), 57: Config1 = escalus:init_per_suite(Config), 58: Config2 = dynamic_modules:save_modules([HostType, SecHostType], Config1), 59: Backend = mongoose_helper:get_backend_mnesia_rdbms_riak(HostType), 60: SecBackend = mongoose_helper:get_backend_mnesia_rdbms_riak(SecHostType), 61: dynamic_modules:ensure_modules(HostType, required_modules(Backend)), 62: dynamic_modules:ensure_modules(SecHostType, required_modules(SecBackend)), 63: escalus:init_per_suite(Config2). 64: 65: end_per_suite(Config) -> 66: dynamic_modules:restore_modules(Config), 67: escalus:end_per_suite(Config). 68: 69: init_per_group(admin_last, Config) -> 70: graphql_helper:init_admin_handler(Config); 71: init_per_group(admin_last_old_users, Config) -> 72: AuthMods = mongoose_helper:auth_modules(), 73: case lists:member(ejabberd_auth_ldap, AuthMods) of 74: true -> {skip, not_fully_supported_with_ldap}; 75: false -> graphql_helper:init_admin_handler(Config) 76: end; 77: init_per_group(user_last, Config) -> 78: [{schema_endpoint, user} | Config]. 79: 80: end_per_group(_, _Config) -> 81: escalus_fresh:clean(). 82: 83: init_per_testcase(C, Config) when C =:= admin_remove_old_users_domain; 84: C =:= admin_remove_old_users_global; 85: C =:= admin_list_old_users_domain; 86: C =:= admin_list_old_users_global; 87: C =:= admin_user_without_last_info_is_old_user -> 88: Config1 = escalus:create_users(Config, escalus:get_users([alice, bob, alice_bis])), 89: escalus:init_per_testcase(C, Config1); 90: init_per_testcase(CaseName, Config) -> 91: escalus:init_per_testcase(CaseName, Config). 92: 93: end_per_testcase(C, Config) when C =:= admin_remove_old_users_domain; 94: C =:= admin_remove_old_users_global; 95: C =:= admin_list_old_users_domain; 96: C =:= admin_list_old_users_global; 97: C =:= admin_user_without_last_info_is_old_user -> 98: escalus:delete_users(Config, escalus:get_users([alice, bob, alice_bis])), 99: escalus:end_per_testcase(C, Config); 100: end_per_testcase(CaseName, Config) -> 101: escalus:end_per_testcase(CaseName, Config). 102: 103: required_modules(riak) -> 104: [{mod_last, #{backend => riak, 105: iqdisc => one_queue, 106: riak => #{bucket_type => <<"last">>}}}]; 107: required_modules(Backend) -> 108: [{mod_last, #{backend => Backend, 109: iqdisc => one_queue}}]. 110: 111: %% Admin test cases 112: 113: admin_set_last(Config) -> 114: escalus:fresh_story_with_config(Config, [{alice, 1}], 115: fun admin_set_last/2). 116: 117: admin_set_last(Config, Alice) -> 118: Status = <<"First status">>, 119: JID = escalus_utils:jid_to_lower(user_to_bin(Alice)), 120: % With timestamp provided 121: Res = execute_auth(admin_set_last_body(Alice, Status, ?DEFAULT_DT), Config), 122: #{<<"user">> := JID, <<"status">> := Status, <<"timestamp">> := ?DEFAULT_DT} = 123: get_ok_value(p(setLast), Res), 124: % Without timestamp provided 125: Status2 = <<"Second status">>, 126: Res2 = execute_auth(admin_set_last_body(Alice, Status2, null), Config), 127: #{<<"user">> := JID, <<"status">> := Status2, <<"timestamp">> := DateTime2} = 128: get_ok_value(p(setLast), Res2), 129: ?assert(os:system_time(second) - dt_to_unit(DateTime2, second) < 2). 130: 131: admin_try_set_nonexistent_user_last(Config) -> 132: Res = execute_auth(admin_set_last_body(?NONEXISTENT_JID, <<"status">>, null), Config), 133: ?assertErrMsg(Res, <<"not exist">>), 134: ?assertErrCode(Res, user_does_not_exist). 135: 136: admin_get_last(Config) -> 137: escalus:fresh_story_with_config(Config, [{alice, 1}], 138: fun admin_get_last/2). 139: 140: admin_get_last(Config, Alice) -> 141: Status = <<"I love ducks">>, 142: JID = escalus_utils:jid_to_lower(user_to_bin(Alice)), 143: execute_auth(admin_set_last_body(Alice, Status, ?DEFAULT_DT), Config), 144: Res = execute_auth(admin_get_last_body(Alice), Config), 145: #{<<"user">> := JID, <<"status">> := Status, <<"timestamp">> := ?DEFAULT_DT} = 146: get_ok_value(p(getLast), Res). 147: 148: admin_get_nonexistent_user_last(Config) -> 149: Res = execute_auth(admin_get_last_body(?NONEXISTENT_JID), Config), 150: ?assertErrMsg(Res, <<"not exist">>), 151: ?assertErrCode(Res, user_does_not_exist). 152: 153: admin_try_get_nonexistent_last(Config) -> 154: escalus:fresh_story_with_config(Config, [{alice, 1}], 155: fun admin_try_get_nonexistent_last/2). 156: 157: admin_try_get_nonexistent_last(Config, Alice) -> 158: Res = execute_auth(admin_get_last_body(Alice), Config), 159: ?assertErrMsg(Res, <<"not found">>), 160: ?assertErrCode(Res, last_not_found). 161: 162: admin_count_active_users(Config) -> 163: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], 164: fun admin_count_active_users/3). 165: 166: admin_count_active_users(Config, Alice, Bob) -> 167: Domain = domain_helper:domain(), 168: set_last(Alice, now_dt_with_offset(5), Config), 169: set_last(Bob, now_dt_with_offset(10), Config), 170: Res = execute_auth(admin_count_active_users_body(Domain, null), Config), 171: ?assertEqual(2, get_ok_value(p(countActiveUsers), Res)), 172: Res2 = execute_auth(admin_count_active_users_body(Domain, now_dt_with_offset(30)), Config), 173: ?assertEqual(0, get_ok_value(p(countActiveUsers), Res2)). 174: 175: admin_try_count_nonexistent_domain_active_users(Config) -> 176: Res = execute_auth(admin_count_active_users_body(<<"unknown-domain.com">>, null), Config), 177: ?assertErrMsg(Res, <<"not found">>), 178: ?assertErrCode(Res, domain_not_found). 179: 180: %% Admin old users test cases 181: 182: admin_remove_old_users_domain(Config) -> 183: jids_with_config(Config, [alice, alice_bis, bob], fun admin_remove_old_users_domain/4). 184: 185: admin_remove_old_users_domain(Config, Alice, AliceBis, Bob) -> 186: Domain = domain_helper:domain(), 187: ToRemoveDateTime = now_dt_with_offset(100), 188: 189: set_last(Bob, ToRemoveDateTime, Config), 190: set_last(AliceBis, ToRemoveDateTime, Config), 191: set_last(Alice, now_dt_with_offset(200), Config), 192: 193: Resp = execute_auth(admin_remove_old_users_body(Domain, now_dt_with_offset(150)), Config), 194: [#{<<"jid">> := Bob, <<"timestamp">> := BobDateTimeRes}] = get_ok_value(p(removeOldUsers), Resp), 195: ?assertEqual(dt_to_unit(ToRemoveDateTime, second), dt_to_unit(BobDateTimeRes, second)), 196: ?assertMatch({user_does_not_exist, _}, check_account(Bob)), 197: ?assertMatch({ok, _}, check_account(Alice)), 198: ?assertMatch({ok, _}, check_account(AliceBis)). 199: 200: admin_try_remove_old_users_nonexistent_domain(Config) -> 201: Res = execute_auth(admin_remove_old_users_body(<<"nonexistent">>, now_dt_with_offset(0)), Config), 202: ?assertErrMsg(Res, <<"not found">>), 203: ?assertErrCode(Res, domain_not_found). 204: 205: admin_remove_old_users_global(Config) -> 206: jids_with_config(Config, [alice, alice_bis, bob], fun admin_remove_old_users_global/4). 207: 208: admin_remove_old_users_global(Config, Alice, AliceBis, Bob) -> 209: ToRemoveDateTime = now_dt_with_offset(100), 210: ToRemoveTimestamp = dt_to_unit(ToRemoveDateTime, second), 211: 212: set_last(Bob, ToRemoveDateTime, Config), 213: set_last(AliceBis, ToRemoveDateTime, Config), 214: set_last(Alice, now_dt_with_offset(200), Config), 215: 216: Resp = execute_auth(admin_remove_old_users_body(null, now_dt_with_offset(150)), Config), 217: [#{<<"jid">> := AliceBis, <<"timestamp">> := AliceBisDateTime}, 218: #{<<"jid">> := Bob, <<"timestamp">> := BobDateTime}] = 219: lists:sort(get_ok_value(p(removeOldUsers), Resp)), 220: ?assertEqual(ToRemoveTimestamp, dt_to_unit(BobDateTime, second)), 221: ?assertEqual(ToRemoveTimestamp, dt_to_unit(AliceBisDateTime, second)), 222: ?assertMatch({user_does_not_exist, _}, check_account(Bob)), 223: ?assertMatch({user_does_not_exist, _}, check_account(AliceBis)), 224: ?assertMatch({ok, _}, check_account(Alice)). 225: 226: admin_list_old_users_domain(Config) -> 227: jids_with_config(Config, [alice, bob], fun admin_list_old_users_domain/3). 228: 229: admin_list_old_users_domain(Config, Alice, Bob) -> 230: Domain = domain_helper:domain(), 231: OldDateTime = now_dt_with_offset(100), 232: 233: set_last(Bob, OldDateTime, Config), 234: set_last(Alice, now_dt_with_offset(200), Config), 235: 236: Res = execute_auth(admin_list_old_users_body(Domain, now_dt_with_offset(150)), Config), 237: [#{<<"jid">> := Bob, <<"timestamp">> := BobDateTime}] = get_ok_value(p(listOldUsers), Res), 238: ?assertEqual(dt_to_unit(OldDateTime, second), dt_to_unit(BobDateTime, second)). 239: 240: admin_try_list_old_users_nonexistent_domain(Config) -> 241: Res = execute_auth(admin_list_old_users_body(<<"nonexistent">>, now_dt_with_offset(0)), Config), 242: ?assertErrMsg(Res, <<"not found">>), 243: ?assertErrCode(Res, domain_not_found). 244: 245: admin_list_old_users_global(Config) -> 246: jids_with_config(Config, [alice, alice_bis, bob], fun admin_list_old_users_global/4). 247: 248: admin_list_old_users_global(Config, Alice, AliceBis, Bob) -> 249: OldDateTime = now_dt_with_offset(100), 250: 251: set_last(Bob, OldDateTime, Config), 252: set_last(AliceBis, OldDateTime, Config), 253: set_last(Alice, now_dt_with_offset(200), Config), 254: 255: Res = execute_auth(admin_list_old_users_body(null, now_dt_with_offset(150)), Config), 256: [#{<<"jid">> := AliceBis, <<"timestamp">> := AliceBisDateTime}, 257: #{<<"jid">> := Bob, <<"timestamp">> := BobDateTime}] = 258: lists:sort(get_ok_value(p(listOldUsers), Res)), 259: ?assertEqual(dt_to_unit(OldDateTime, second), dt_to_unit(BobDateTime, second)), 260: ?assertEqual(dt_to_unit(OldDateTime, second), dt_to_unit(AliceBisDateTime, second)). 261: 262: admin_user_without_last_info_is_old_user(Config) -> 263: Res = execute_auth(admin_list_old_users_body(null, now_dt_with_offset(150)), Config), 264: OldUsers = get_ok_value(p(listOldUsers), Res), 265: ?assertEqual(3, length(OldUsers)), 266: [?assertEqual(null, TS) || #{<<"timestamp">> := TS} <- OldUsers]. 267: 268: admin_logged_user_is_not_old_user(Config) -> 269: escalus:fresh_story_with_config(Config, [{alice, 1}], fun admin_logged_user_is_not_old_user/2). 270: 271: admin_logged_user_is_not_old_user(Config, _Alice) -> 272: Res = execute_auth(admin_list_old_users_body(null, now_dt_with_offset(100)), Config), 273: ?assertEqual([], get_ok_value(p(listOldUsers), Res)). 274: 275: %% User test cases 276: 277: user_set_last(Config) -> 278: escalus:fresh_story_with_config(Config, [{alice, 1}], 279: fun user_set_last/2). 280: 281: user_set_last(Config, Alice) -> 282: Status = <<"My first status">>, 283: JID = escalus_utils:jid_to_lower(user_to_bin(Alice)), 284: Res = execute_user(user_set_last_body(Status, ?DEFAULT_DT), Alice, Config), 285: #{<<"user">> := JID, <<"status">> := Status, <<"timestamp">> := ?DEFAULT_DT} = 286: get_ok_value(p(setLast), Res), 287: Status2 = <<"Quack Quack">>, 288: Res2 = execute_user(user_set_last_body(Status2, null), Alice, Config), 289: #{<<"user">> := JID, <<"status">> := Status2, <<"timestamp">> := DateTime2} = 290: get_ok_value(p(setLast), Res2), 291: ?assert(os:system_time(second) - dt_to_unit(DateTime2, second) < 2). 292: 293: user_get_last(Config) -> 294: escalus:fresh_story_with_config(Config, [{alice, 1}], 295: fun user_get_last/2). 296: user_get_last(Config, Alice) -> 297: Status = <<"I love ducks">>, 298: JID = escalus_utils:jid_to_lower(user_to_bin(Alice)), 299: execute_user(user_set_last_body(Status, ?DEFAULT_DT), Alice, Config), 300: Res = execute_user(user_get_last_body(null), Alice, Config), 301: #{<<"user">> := JID, <<"status">> := Status, <<"timestamp">> := ?DEFAULT_DT} = 302: get_ok_value(p(getLast), Res). 303: 304: user_get_other_user_last(Config) -> 305: escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], 306: fun user_get_other_user_last/3). 307: 308: user_get_other_user_last(Config, Alice, Bob) -> 309: Status = <<"In good mood">>, 310: JID = escalus_utils:jid_to_lower(user_to_bin(Bob)), 311: execute_user(user_set_last_body(Status, ?DEFAULT_DT), Bob, Config), 312: Res = execute_user(admin_get_last_body(Bob), Alice, Config), 313: #{<<"user">> := JID, <<"status">> := Status, <<"timestamp">> := ?DEFAULT_DT} = 314: get_ok_value(p(getLast), Res). 315: 316: %% Helpers 317: 318: jids_with_config(Config, Users, Fun) -> 319: Args = [escalus_utils:jid_to_lower(escalus_users:get_jid(Config, User)) || User <- Users], 320: apply(Fun, [Config | Args]). 321: 322: set_last(UserJID, DateTime, Config) -> 323: execute_auth(admin_set_last_body(UserJID, <<>>, DateTime), Config). 324: 325: check_account(User) -> 326: {Username, LServer} = jid:to_lus(user_to_jid(User)), 327: rpc(mim(), mongoose_account_api, check_account, [Username, LServer]). 328: 329: assert_err_msg(Contains, Res) -> 330: ?assertNotEqual(nomatch, binary:match(get_err_msg(Res), Contains)). 331: 332: assert_err_code(Code, Res) -> 333: ?assertEqual(atom_to_binary(Code), get_err_code(Res)). 334: 335: p(Cmd) when is_atom(Cmd) -> 336: [data, last, Cmd]; 337: p(Path) when is_list(Path) -> 338: [data, last] ++ Path. 339: 340: now_dt_with_offset(SecondsOffset) -> 341: Seconds = erlang:system_time(second) + SecondsOffset, 342: list_to_binary(calendar:system_time_to_rfc3339(Seconds, [{unit, second}, {offset, "Z"}])). 343: 344: dt_to_unit(ISODateTime, Unit) -> 345: calendar:rfc3339_to_system_time(binary_to_list(ISODateTime), [{unit, Unit}]). 346: 347: %% Request bodies 348: 349: admin_set_last_body(User, Status, DateTime) -> 350: Query = <<"mutation M1($user: JID!, $timestamp: DateTime, $status: String!) 351: { last { setLast (user: $user, timestamp: $timestamp, status: $status) 352: { user timestamp status } } }">>, 353: OpName = <<"M1">>, 354: Vars = #{user => user_to_bin(User), timestamp => DateTime, status => Status}, 355: #{query => Query, operationName => OpName, variables => Vars}. 356: 357: admin_get_last_body(User) -> 358: Query = <<"query Q1($user: JID!) 359: { last { getLast(user: $user) 360: { user timestamp status } } }">>, 361: OpName = <<"Q1">>, 362: Vars = #{user => user_to_bin(User)}, 363: #{query => Query, operationName => OpName, variables => Vars}. 364: 365: admin_count_active_users_body(Domain, Timestamp) -> 366: Query = <<"query Q1($domain: String!, $timestamp: DateTime) 367: { last { countActiveUsers(domain: $domain, timestamp: $timestamp) } }">>, 368: OpName = <<"Q1">>, 369: Vars = #{domain => Domain, timestamp => Timestamp}, 370: #{query => Query, operationName => OpName, variables => Vars}. 371: 372: admin_remove_old_users_body(Domain, Timestamp) -> 373: Query = <<"mutation M1($domain: String, $timestamp: DateTime!) 374: { last { removeOldUsers(domain: $domain, timestamp: $timestamp) { jid timestamp } } }">>, 375: OpName = <<"M1">>, 376: Vars = #{domain => Domain, timestamp => Timestamp}, 377: #{query => Query, operationName => OpName, variables => Vars}. 378: 379: admin_list_old_users_body(Domain, Timestamp) -> 380: Query = <<"query Q1($domain: String, $timestamp: DateTime!) 381: { last { listOldUsers(domain: $domain, timestamp: $timestamp) { jid timestamp } } }">>, 382: OpName = <<"Q1">>, 383: Vars = #{domain => Domain, timestamp => Timestamp}, 384: #{query => Query, operationName => OpName, variables => Vars}. 385: 386: user_set_last_body(Status, DateTime) -> 387: Query = <<"mutation M1($timestamp: DateTime, $status: String!) 388: { last { setLast (timestamp: $timestamp, status: $status) 389: { user timestamp status } } }">>, 390: OpName = <<"M1">>, 391: Vars = #{timestamp => DateTime, status => Status}, 392: #{query => Query, operationName => OpName, variables => Vars}. 393: 394: user_get_last_body(User) -> 395: Query = <<"query Q1($user: JID) 396: { last { getLast(user: $user) 397: { user timestamp status } } }">>, 398: OpName = <<"Q1">>, 399: Vars = #{user => user_to_bin(User)}, 400: #{query => Query, operationName => OpName, variables => Vars}.