1: %% @doc Tests for the SSE handling of GraphQL subscriptions 2: -module(graphql_sse_SUITE). 3: 4: -compile([export_all, nowarn_export_all]). 5: 6: -import(distributed_helper, [mim/0, require_rpc_nodes/1, rpc/4]). 7: -import(graphql_helper, [get_bad_request/1, get_unauthorized/1, get_method_not_allowed/1, 8: build_request/4, make_creds/1, execute_auth/2, 9: execute_sse/3, execute_user_sse/3, execute_auth_sse/2]). 10: 11: %% common_test callbacks 12: 13: suite() -> 14: require_rpc_nodes([mim]) ++ escalus:suite(). 15: 16: all() -> 17: [{group, admin}, 18: {group, user}, 19: {group, timeout}]. 20: 21: groups() -> 22: [{admin, [parallel], admin_tests()}, 23: {user, [parallel], user_tests()}, 24: {timeout, [], [sse_should_not_get_timeout]}]. 25: 26: init_per_suite(Config) -> 27: Config1 = escalus:init_per_suite(Config), 28: application:ensure_all_started(gun), 29: Config1. 30: 31: end_per_suite(Config) -> 32: escalus:end_per_suite(Config). 33: 34: init_per_group(user, Config) -> 35: graphql_helper:init_user(Config); 36: init_per_group(admin, Config) -> 37: graphql_helper:init_admin_handler(Config); 38: init_per_group(timeout, Config) -> 39: % Change the default idle_timeout for the listener to 1s to test if sse will override it 40: Listener = get_graphql_user_listener(), 41: mongoose_helper:change_listener_idle_timeout(Listener, 1000), 42: graphql_helper:init_user(Config). 43: 44: end_per_group(user, _Config) -> 45: escalus_fresh:clean(), 46: graphql_helper:clean(); 47: end_per_group(admin, _Config) -> 48: graphql_helper:clean(); 49: end_per_group(timeout, _Config) -> 50: Listener = get_graphql_user_listener(), 51: mongoose_helper:restart_listener(mim(), Listener), 52: escalus_fresh:clean(), 53: graphql_helper:clean(). 54: 55: init_per_testcase(CaseName, Config) -> 56: escalus:init_per_testcase(CaseName, Config). 57: 58: end_per_testcase(CaseName, Config) -> 59: escalus:end_per_testcase(CaseName, Config). 60: 61: admin_tests() -> 62: [admin_missing_query, 63: admin_invalid_query_string, 64: admin_missing_creds, 65: admin_invalid_creds, 66: admin_invalid_method, 67: admin_invalid_operation_type]. 68: 69: user_tests() -> 70: [user_missing_query, 71: user_invalid_query_string, 72: user_missing_creds, 73: user_invalid_creds, 74: user_invalid_method, 75: user_invalid_operation_type]. 76: 77: %% Test cases and stories 78: 79: admin_missing_query(Config) -> 80: get_bad_request(execute_auth_sse(#{}, Config)). 81: 82: user_missing_query(Config) -> 83: escalus:fresh_story_with_config(Config, [{alice, 1}], fun user_missing_query_story/2). 84: 85: user_missing_query_story(Config, Alice) -> 86: get_bad_request(execute_user_sse(#{}, Alice, Config)). 87: 88: admin_invalid_query_string(_Config) -> 89: Port = graphql_helper:get_listener_port(admin), 90: get_bad_request(sse_helper:connect_to_sse(Port, "/api/graphql/sse?=invalid", undefined, #{})). 91: 92: user_invalid_query_string(Config) -> 93: escalus:fresh_story(Config, [{alice, 1}], fun user_invalid_query_string_story/1). 94: 95: user_invalid_query_string_story(Alice) -> 96: Port = graphql_helper:get_listener_port(user), 97: Creds = make_creds(Alice), 98: get_bad_request(sse_helper:connect_to_sse(Port, "/api/graphql/sse?=invalid", Creds, #{})). 99: 100: admin_missing_creds(_Config) -> 101: get_unauthorized(execute_sse(admin, #{query => doc(), variables => args()}, undefined)). 102: 103: user_missing_creds(_Config) -> 104: get_unauthorized(execute_sse(user, #{query => doc()}, undefined)). 105: 106: admin_invalid_creds(_Config) -> 107: Creds = {<<"invalid">>, <<"creds">>}, 108: get_unauthorized(execute_sse(admin, #{query => doc(), variables => args()}, Creds)). 109: 110: user_invalid_creds(_Config) -> 111: get_unauthorized(execute_sse(user, #{query => doc()}, {<<"invalid">>, <<"creds">>})). 112: 113: admin_invalid_method(_Config) -> 114: #{node := Node} = mim(), 115: Request = build_request(Node, admin, #{query => doc(), variables => args()}, undefined), 116: %% POST was used, while SSE accepts only GET 117: get_method_not_allowed(rest_helper:make_request(Request#{path => "/graphql/sse"})). 118: 119: user_invalid_method(Config) -> 120: escalus:fresh_story(Config, [{alice, 1}], fun user_invalid_method_story/1). 121: 122: user_invalid_method_story(Alice) -> 123: #{node := Node} = mim(), 124: Request = build_request(Node, user, #{query => doc()}, make_creds(Alice)), 125: %% POST was used, while SSE accepts only GET 126: get_method_not_allowed(rest_helper:make_request(Request#{path => "/graphql/sse"})). 127: 128: admin_invalid_operation_type(Config) -> 129: Creds = graphql_helper:make_admin_creds(admin, Config), 130: get_bad_request(execute_sse(admin, #{query => query_doc(), variables => args()}, Creds)). 131: 132: user_invalid_operation_type(Config) -> 133: escalus:fresh_story(Config, [{alice, 1}], fun user_invalid_operation_type_story/1). 134: 135: user_invalid_operation_type_story(Alice) -> 136: get_bad_request(execute_sse(user, #{query => query_doc()}, make_creds(Alice))). 137: 138: sse_should_not_get_timeout(Config) -> 139: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun (Alice, Bob) -> 140: From = escalus_client:full_jid(Bob), 141: To = escalus_client:short_jid(Alice), 142: {200, Stream} = graphql_helper:execute_user_command_sse(<<"stanza">>, <<"subscribeForMessages">>, Alice, #{}, Config), 143: escalus:send(Bob, escalus_stanza:chat(From, To, <<"Hello!">>)), 144: sse_helper:wait_for_event(Stream), 145: timer:sleep(2000), 146: escalus:send(Bob, escalus_stanza:chat(From, To, <<"Hello again!">>)), 147: sse_helper:wait_for_event(Stream), 148: sse_helper:stop_sse(Stream) 149: end). 150: 151: %% Helpers 152: 153: get_graphql_user_listener() -> 154: Handler = #{module => mongoose_graphql_handler, schema_endpoint => user}, 155: ListenerOpts = #{handlers => [Handler]}, 156: [Listener] = mongoose_helper:get_listeners(mim(), ListenerOpts), 157: Listener. 158: 159: %% Subscription - works only with the SSE handler 160: doc() -> 161: graphql_helper:get_doc(<<"stanza">>, <<"subscribeForMessages">>). 162: 163: %% Query - works only with the REST handler 164: query_doc() -> 165: graphql_helper:get_doc(<<"stanza">>, <<"getLastMessages">>). 166: 167: %% Same args used by both operations - only for Admin 168: args() -> 169: #{caller => <<"alice@localhost">>}.