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">>}.