diff --git a/big_tests/tests/graphql_helper.erl b/big_tests/tests/graphql_helper.erl index 09d00ea3d98..791a49c339d 100644 --- a/big_tests/tests/graphql_helper.erl +++ b/big_tests/tests/graphql_helper.erl @@ -17,20 +17,40 @@ execute(EpName, Body, Creds) -> -spec execute(node(), atom(), binary(), {binary(), binary()} | undefined) -> {Status :: tuple(), Data :: map()}. execute(Node, EpName, Body, Creds) -> - Request = - #{port => get_listener_port(Node, EpName), - role => {graphql, EpName}, - method => <<"POST">>, - return_maps => true, - creds => Creds, - path => "/graphql", - body => Body}, + Request = build_request(Node, EpName, Body, Creds), rest_helper:make_request(Request). +build_request(Node, EpName, Body, Creds) -> + #{port => get_listener_port(Node, EpName), + role => {graphql, EpName}, + method => <<"POST">>, + return_maps => true, + creds => Creds, + path => "/graphql", + body => Body}. + +execute_sse(EpName, Params, Creds) -> + #{node := Node} = mim(), + execute_sse(Node, EpName, Params, Creds). + +execute_sse(Node, EpName, Params, Creds) -> + Port = get_listener_port(Node, EpName), + Path = "/api/graphql/sse", + QS = uri_string:compose_query([{atom_to_binary(K), encode_sse_value(V)} + || {K, V} <- maps:to_list(Params)]), + sse_helper:connect_to_sse(Port, [Path, "?", QS], Creds, #{}). + +encode_sse_value(M) when is_map(M) -> jiffy:encode(M); +encode_sse_value(V) when is_binary(V) -> V. + execute_user_command(Category, Command, User, Args, Config) -> - #{Category := #{commands := #{Command := #{doc := Doc}}}} = get_specs(), + Doc = get_doc(Category, Command), execute_user(#{query => Doc, variables => Args}, User, Config). +execute_user_command_sse(Category, Command, User, Args, Config) -> + Doc = get_doc(Category, Command), + execute_user_sse(#{query => Doc, variables => Args}, User, Config). + execute_command(Category, Command, Args, Config) -> #{node := Node} = mim(), Protocol = ?config(protocol, Config), @@ -40,9 +60,13 @@ execute_command(Node, Category, Command, Args, Config) -> Protocol = ?config(protocol, Config), execute_command(Node, Category, Command, Args, Config, Protocol). +execute_command_sse(Category, Command, Args, Config) -> + Doc = get_doc(Category, Command), + execute_auth_sse(#{query => Doc, variables => Args}, Config). + %% Admin commands can be executed as GraphQL over HTTP or with CLI (mongooseimctl) execute_command(Node, Category, Command, Args, Config, http) -> - #{Category := #{commands := #{Command := #{doc := Doc}}}} = get_specs(), + Doc = get_doc(Category, Command), execute_auth(Node, #{query => Doc, variables => Args}, Config); execute_command(Node, Category, Command, Args, Config, cli) -> CLIArgs = encode_cli_args(Args), @@ -50,6 +74,10 @@ execute_command(Node, Category, Command, Args, Config, cli) -> = mongooseimctl_helper:mongooseimctl(Node, Category, [Command | CLIArgs], Config), {{exit_status, Code}, rest_helper:decode(Result, #{return_maps => true})}. +get_doc(Category, Command) -> + #{Category := #{commands := #{Command := #{doc := Doc}}}} = get_specs(), + Doc. + encode_cli_args(Args) -> lists:flatmap(fun({Name, Value}) -> encode_cli_arg(Name, Value) end, maps:to_list(Args)). encode_cli_arg(_Name, null) -> @@ -71,14 +99,22 @@ execute_auth(Body, Config) -> execute_auth(Node, Body, Config). execute_auth(Node, Body, Config) -> - case Ep = ?config(schema_endpoint, Config) of - admin -> - #{username := Username, password := Password} = get_listener_opts(Ep), - execute(Node, Ep, Body, {Username, Password}); - domain_admin -> - Creds = ?config(domain_admin, Config), - execute(Node, Ep, Body, Creds) - end. + Ep = ?config(schema_endpoint, Config), + execute(Node, Ep, Body, make_admin_creds(Ep, Config)). + +execute_auth_sse(Body, Config) -> + #{node := Node} = mim(), + execute_auth_sse(Node, Body, Config). + +execute_auth_sse(Node, Body, Config) -> + Ep = ?config(schema_endpoint, Config), + execute_sse(Node, Ep, Body, make_admin_creds(Ep, Config)). + +make_admin_creds(admin = Ep, _Config) -> + #{username := Username, password := Password} = get_listener_opts(Ep), + {Username, Password}; +make_admin_creds(domain_admin, Config) -> + ?config(domain_admin, Config). execute_user(Body, User, Config) -> Ep = ?config(schema_endpoint, Config), @@ -86,6 +122,12 @@ execute_user(Body, User, Config) -> #{node := Node} = mim(), execute(Node, Ep, Body, Creds). +execute_user_sse(Body, User, Config) -> + Ep = ?config(schema_endpoint, Config), + Creds = make_creds(User), + #{node := Node} = mim(), + execute_sse(Node, Ep, Body, Creds). + -spec get_listener_port(binary()) -> integer(). get_listener_port(EpName) -> #{node := Node} = mim(), @@ -173,6 +215,9 @@ get_unauthorized({Code, #{<<"errors">> := Errors}}) -> get_bad_request({Code, _Msg}) -> assert_response_code(bad_request, Code). +get_method_not_allowed({Code, _Msg}) -> + assert_response_code(method_not_allowed, Code). + get_coercion_err_msg({Code, #{<<"errors">> := [Error]}}) -> assert_response_code(bad_request, Code), ?assertEqual(<<"input_coercion">>, get_value([extensions, code], Error)), @@ -196,8 +241,14 @@ get_ok_value(Path, {Code, Data}) -> assert_response_code(bad_request, {<<"400">>, <<"Bad Request">>}) -> ok; assert_response_code(unauthorized, {<<"401">>, <<"Unauthorized">>}) -> ok; +assert_response_code(method_not_allowed, {<<"405">>, <<"Method Not Allowed">>}) -> ok; assert_response_code(error, {<<"200">>, <<"OK">>}) -> ok; assert_response_code(ok, {<<"200">>, <<"OK">>}) -> ok; +assert_response_code(bad_request, 400) -> ok; +assert_response_code(unauthorized, 401) -> ok; +assert_response_code(method_not_allowed, 405) -> ok; +assert_response_code(error, 200) -> ok; +assert_response_code(ok, 200) -> ok; assert_response_code(bad_request, {exit_status, 1}) -> ok; assert_response_code(error, {exit_status, 1}) -> ok; assert_response_code(ok, {exit_status, 0}) -> ok; diff --git a/big_tests/tests/graphql_stanza_SUITE.erl b/big_tests/tests/graphql_stanza_SUITE.erl index 88ca4fef9bc..203ee5ca1c6 100644 --- a/big_tests/tests/graphql_stanza_SUITE.erl +++ b/big_tests/tests/graphql_stanza_SUITE.erl @@ -1,6 +1,5 @@ -module(graphql_stanza_SUITE). --include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("exml/include/exml.hrl"). @@ -8,7 +7,9 @@ -import(distributed_helper, [mim/0, require_rpc_nodes/1]). -import(graphql_helper, [execute_user_command/5, execute_command/4, - get_ok_value/2, get_err_code/1, get_err_msg/1, get_coercion_err_msg/1, + execute_user_command_sse/5, execute_command_sse/4, + get_ok_value/2, get_value/2, + get_err_code/1, get_err_msg/1, get_coercion_err_msg/1, get_unauthorized/1, get_not_loaded/1]). suite() -> @@ -22,19 +23,25 @@ all() -> groups() -> [{admin_stanza_http, [], [{group, admin_mam}, - {group, admin_no_mam}]}, + {group, admin_no_mam_http}]}, {admin_stanza_cli, [], [{group, admin_mam}, - {group, admin_no_mam}]}, + {group, admin_no_mam_cli}]}, {domain_admin_stanza, [], [{group, admin_mam}, % same as for admin {group, domain_admin_no_mam}]}, {user_stanza, [], [{group, user_mam}, {group, user_no_mam}]}, {admin_mam, [parallel], admin_mam_cases()}, - {admin_no_mam, [parallel], admin_stanza_cases() ++ admin_no_mam_cases()}, + {admin_no_mam_http, [parallel], admin_stanza_cases(http) ++ admin_no_mam_cases()}, + {admin_no_mam_cli, [], admin_stanza_cases(cli) ++ admin_no_mam_cases()}, {domain_admin_no_mam, [parallel], domain_admin_stanza_cases() ++ admin_no_mam_cases()}, {user_mam, [parallel], user_mam_cases()}, {user_no_mam, [parallel], user_stanza_cases() ++ user_no_mam_cases()}]. +admin_stanza_cases(cli) -> + admin_stanza_cases(); +admin_stanza_cases(http) -> + admin_stanza_cases() ++ admin_sse_cases(). + admin_stanza_cases() -> [admin_send_message, admin_send_message_to_unparsable_jid, @@ -44,6 +51,10 @@ admin_stanza_cases() -> admin_send_stanza_from_unknown_user, admin_send_stanza_from_unknown_domain]. +admin_sse_cases() -> + [admin_subscribe_for_messages, + admin_subscribe_for_messages_to_unknown_user]. + admin_mam_cases() -> [admin_get_last_messages, admin_get_last_messages_for_unknown_user, @@ -69,6 +80,7 @@ domain_admin_stanza_cases() -> user_stanza_cases() -> [user_send_message, + user_subscribe_for_messages, user_send_message_without_from, user_send_message_with_spoofed_from, user_send_message_headline, @@ -89,7 +101,6 @@ init_per_suite(Config) -> dynamic_modules:save_modules(domain_helper:host_type(), Config2). end_per_suite(Config) -> - escalus_fresh:clean(), dynamic_modules:restore_modules(Config), escalus:end_per_suite(Config). @@ -105,7 +116,8 @@ init_per_group(GN, Config) when GN =:= admin_mam; GN =:= domain_admin_mam; GN =:= user_mam -> init_mam(Config); -init_per_group(GN, Config) when GN =:= admin_no_mam; +init_per_group(GN, Config) when GN =:= admin_no_mam_http; + GN =:= admin_no_mam_cli; GN =:= domain_admin_no_mam; GN =:= user_no_mam -> Mods = [{mod_mam, stopped}], @@ -118,7 +130,7 @@ end_per_group(GN, _Config) when GN =:= admin_stanza_http; GN =:= user_stanza -> graphql_helper:clean(); end_per_group(_GN, _Config) -> - ok. + escalus_fresh:clean(). init_per_testcase(CaseName, Config) -> escalus:init_per_testcase(CaseName, Config). @@ -153,6 +165,31 @@ admin_send_message_story(Config, Alice, Bob) -> assert_not_empty(StanzaId), escalus:assert(is_message, escalus:wait_for_stanza(Bob)). +admin_subscribe_for_messages_to_unknown_user(Config) -> + Domain = domain_helper:domain(), + Res = subscribe_for_messages(<<"baduser@", Domain/binary>>, Config), + ?assertEqual(<<"unknown_user">>, get_err_code(Res)), + ?assertEqual(<<"User does not exist">>, get_err_msg(Res)). + +admin_subscribe_for_messages(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], + fun admin_subscribe_for_messages_story/3). + +admin_subscribe_for_messages_story(Config, Alice, Bob) -> + From = escalus_client:full_jid(Alice), + To = escalus_client:short_jid(Bob), + {200, Stream} = subscribe_for_messages(To, Config), + %% Presence should be skipped + escalus:send(Alice, escalus_stanza:presence_direct(To, <<"available">>)), + %% Message should be delivered + escalus:send(Alice, escalus_stanza:chat(From, To, <<"Hi!">>)), + Event = sse_helper:wait_for_event(Stream), + #{<<"stanza">> := StanzaBin, <<"sender">> := From} = + graphql_helper:get_value([data, stanza, subscribeForMessages], Event), + {ok, Stanza} = exml:parse(StanzaBin), + escalus:assert(is_message, Stanza), + sse_helper:stop_sse(Stream). + user_send_message(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun user_send_message_story/3). @@ -165,6 +202,22 @@ user_send_message_story(Config, Alice, Bob) -> assert_not_empty(StanzaId), escalus:assert(is_message, escalus:wait_for_stanza(Bob)). +user_subscribe_for_messages(Config) -> + escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], + fun user_subscribe_for_messages_story/3). + +user_subscribe_for_messages_story(Config, Alice, Bob) -> + From = escalus_client:full_jid(Alice), + To = escalus_client:short_jid(Bob), + {200, Stream} = user_subscribe_for_messages(Bob, Config), + escalus:send(Alice, escalus_stanza:chat(From, To, <<"Hi!">>)), + Event = sse_helper:wait_for_event(Stream), + #{<<"stanza">> := StanzaBin, <<"sender">> := From} = + graphql_helper:get_value([data, stanza, subscribeForMessages], Event), + {ok, Stanza} = exml:parse(StanzaBin), + escalus:assert(is_message, Stanza), + sse_helper:stop_sse(Stream). + user_send_message_without_from(Config) -> escalus:fresh_story_with_config(Config, [{alice, 1}, {bob, 1}], fun user_send_message_without_from_story/3). @@ -527,6 +580,13 @@ user_send_stanza(User, Stanza, Config) -> Vars = #{stanza => Stanza}, execute_user_command(<<"stanza">>, <<"sendStanza">>, User, Vars, Config). +subscribe_for_messages(Caller, Config) -> + Vars = #{caller => Caller}, + execute_command_sse(<<"stanza">>, <<"subscribeForMessages">>, Vars, Config). + +user_subscribe_for_messages(User, Config) -> + execute_user_command_sse(<<"stanza">>, <<"subscribeForMessages">>, User, #{}, Config). + get_last_messages(Caller, With, Before, Config) -> Vars = #{caller => Caller, with => With, before => Before}, execute_command(<<"stanza">>, <<"getLastMessages">>, Vars, Config).