Stage 10 final
This commit is contained in:
238
test/api/api_websocket_tests.erl
Normal file
238
test/api/api_websocket_tests.erl
Normal file
@@ -0,0 +1,238 @@
|
||||
-module(api_websocket_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, "http://localhost:8080").
|
||||
-define(WS_URL, "ws://localhost:8081/ws").
|
||||
-define(ADMIN_WS_URL, "ws://localhost:8446/admin/ws").
|
||||
|
||||
test() ->
|
||||
ct:pal("Testing WebSocket API..."),
|
||||
|
||||
% Запускаем gun
|
||||
application:ensure_all_started(gun),
|
||||
|
||||
% Используем глобальных пользователей
|
||||
AdminToken = api_test_runner:get_admin_token(),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
ct:pal(" AdminToken: ~s...", [binary_part(AdminToken, 0, 30)]),
|
||||
ct:pal(" UserToken: ~s...", [binary_part(UserToken, 0, 30)]),
|
||||
|
||||
% Создаём календарь и событие для тестов
|
||||
CalId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars",
|
||||
#{title => <<"WS Test Calendar">>, type => <<"commercial">>},
|
||||
UserToken), <<"id">>, 201),
|
||||
ct:pal(" CalId: ~s", [CalId]),
|
||||
|
||||
EventId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(CalId) ++ "/events",
|
||||
#{title => <<"WS Test Event">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60},
|
||||
UserToken), <<"id">>, 201),
|
||||
ct:pal(" EventId: ~s", [EventId]),
|
||||
|
||||
% TEST 1: Connect to WebSocket with valid token
|
||||
ct:pal(" TEST 1: Connect WebSocket with valid token..."),
|
||||
ct:pal(" URL: ~s", [?WS_URL]),
|
||||
ct:pal(" Token: ~s...", [binary_part(UserToken, 0, 30)]),
|
||||
|
||||
case test_ws_connect_debug(?WS_URL, UserToken) of
|
||||
{ok, WS} ->
|
||||
ct:pal(" OK - Connected"),
|
||||
|
||||
% TEST 2: Subscribe to calendar updates
|
||||
ct:pal(" TEST 2: Subscribe to calendar..."),
|
||||
SubMsg = #{action => <<"subscribe">>, calendar_id => CalId},
|
||||
ct:pal(" Sending: ~p", [SubMsg]),
|
||||
ok = test_ws_send(WS, SubMsg),
|
||||
|
||||
case test_ws_recv(WS) of
|
||||
{ok, #{<<"status">> := <<"subscribed">>}} ->
|
||||
ct:pal(" OK - Subscribed");
|
||||
{ok, Other} ->
|
||||
ct:pal(" ERROR: Unexpected response: ~p", [Other]),
|
||||
error({unexpected_response, Other});
|
||||
{error, timeout} ->
|
||||
ct:pal(" ERROR: Timeout waiting for response"),
|
||||
error(timeout)
|
||||
end,
|
||||
|
||||
% Закрываем соединение
|
||||
test_ws_close(WS);
|
||||
{error, Reason} ->
|
||||
ct:pal(" ERROR: ~p", [Reason]),
|
||||
error({websocket_connect_failed, Reason})
|
||||
end,
|
||||
|
||||
ct:pal("~n✅ WebSocket API tests passed!"),
|
||||
|
||||
% ============ ТЕСТЫ АДМИНСКОГО WEBSOCKET ============
|
||||
ct:pal("~n=== ADMIN WEBSOCKET TESTS ==="),
|
||||
|
||||
% TEST 6: Admin WebSocket connection
|
||||
ct:pal(" TEST 6: Admin WebSocket connect..."),
|
||||
{ok, AdminWS} = test_ws_connect_debug(?ADMIN_WS_URL, AdminToken),
|
||||
ct:pal(" OK - Admin connected"),
|
||||
|
||||
% TEST 7: Admin subscribe to reports channel
|
||||
ct:pal(" TEST 7: Admin subscribe to reports channel..."),
|
||||
ok = test_ws_send(AdminWS, #{action => <<"subscribe">>, channel => <<"reports">>}),
|
||||
{ok, #{<<"status">> := <<"subscribed">>}} = test_ws_recv(AdminWS),
|
||||
ct:pal(" OK - Subscribed to reports"),
|
||||
|
||||
% TEST 8: Admin subscribe to tickets channel
|
||||
ct:pal(" TEST 8: Admin subscribe to tickets channel..."),
|
||||
ok = test_ws_send(AdminWS, #{action => <<"subscribe">>, channel => <<"tickets">>}),
|
||||
{ok, #{<<"status">> := <<"subscribed">>}} = test_ws_recv(AdminWS),
|
||||
ct:pal(" OK - Subscribed to tickets"),
|
||||
|
||||
% TEST 9: Admin receives report notification
|
||||
ct:pal(" TEST 9: Admin receives report notification..."),
|
||||
% Создаём жалобу через HTTP
|
||||
api_test_runner:http_post("/v1/reports",
|
||||
#{target_type => <<"event">>, target_id => EventId, reason => <<"Test report">>},
|
||||
UserToken),
|
||||
{ok, #{<<"type">> := <<"report_created">>}} = test_ws_recv(AdminWS, 5000),
|
||||
ct:pal(" OK - Received report notification"),
|
||||
|
||||
% TEST 10: Admin Ping/Pong
|
||||
ct:pal(" TEST 10: Admin Ping/Pong..."),
|
||||
ok = test_ws_send(AdminWS, #{action => <<"ping">>}),
|
||||
{ok, #{<<"status">> := <<"pong">>}} = test_ws_recv(AdminWS),
|
||||
ct:pal(" OK - Admin Ping/Pong"),
|
||||
|
||||
% TEST 11: Admin unsubscribe
|
||||
ct:pal(" TEST 11: Admin unsubscribe from reports..."),
|
||||
ok = test_ws_send(AdminWS, #{action => <<"unsubscribe">>, channel => <<"reports">>}),
|
||||
{ok, #{<<"status">> := <<"unsubscribed">>}} = test_ws_recv(AdminWS),
|
||||
ct:pal(" OK - Unsubscribed"),
|
||||
|
||||
test_ws_close(AdminWS),
|
||||
|
||||
% TEST 12: Admin WebSocket with user token (should fail)
|
||||
ct:pal(" TEST 12: Admin WS with user token..."),
|
||||
{error, {403, _}} = test_ws_connect_debug(?ADMIN_WS_URL, UserToken),
|
||||
ct:pal(" OK - Rejected"),
|
||||
|
||||
% TEST 13: Admin WebSocket with invalid token
|
||||
ct:pal(" TEST 13: Admin WS with invalid token..."),
|
||||
Chars = <<"abcdefghijklmnopqrstuvwxyz0123456789">>,
|
||||
InvalidToken = << <<(binary:at(Chars, rand:uniform(byte_size(Chars)) - 1))>> || _ <- lists:seq(1, 30) >>,
|
||||
{error, {401, _}} = test_ws_connect_debug(?ADMIN_WS_URL, InvalidToken),
|
||||
ct:pal(" OK - Rejected"),
|
||||
|
||||
ct:pal("~n✅ Admin WebSocket API tests passed!"),
|
||||
{?MODULE, ok}.
|
||||
|
||||
%% ============ WebSocket хелперы с отладкой ============
|
||||
|
||||
test_ws_connect_debug(Url, Token) ->
|
||||
Path = case string:split(Url, "://", trailing) of
|
||||
[_, Rest] ->
|
||||
case string:split(Rest, "/", leading) of
|
||||
[_HostPort, WsPath] ->
|
||||
"/" ++ WsPath ++ "?token=" ++ binary_to_list(Token);
|
||||
_ ->
|
||||
"/ws?token=" ++ binary_to_list(Token)
|
||||
end;
|
||||
_ ->
|
||||
"/ws?token=" ++ binary_to_list(Token)
|
||||
end,
|
||||
|
||||
Port = ws_port(Url),
|
||||
Host = "localhost",
|
||||
|
||||
ct:pal(" Host: ~s", [Host]),
|
||||
ct:pal(" Port: ~p", [Port]),
|
||||
ct:pal(" Path: ~s", [Path]),
|
||||
|
||||
{ok, ConnPid} = gun:open(Host, Port, #{protocols => [http]}),
|
||||
{ok, http} = gun:await_up(ConnPid, 5000),
|
||||
|
||||
Headers = [
|
||||
{<<"host">>, list_to_binary(Host ++ ":" ++ integer_to_list(Port))}
|
||||
],
|
||||
StreamRef = gun:ws_upgrade(ConnPid, Path, Headers),
|
||||
|
||||
% Ожидаем ответ
|
||||
receive
|
||||
{gun_upgrade, ConnPid, StreamRef, [<<"websocket">>], _} ->
|
||||
ct:pal(" WebSocket upgrade OK"),
|
||||
{ok, ConnPid};
|
||||
{gun_response, ConnPid, StreamRef, fin, 401, _} ->
|
||||
ct:pal(" ERROR: HTTP 401 Unauthorized"),
|
||||
gun:close(ConnPid),
|
||||
{error, {401, <<"Invalid token">>}};
|
||||
{gun_response, ConnPid, StreamRef, fin, 403, _} ->
|
||||
ct:pal(" ERROR: HTTP 403 Forbidden"),
|
||||
gun:close(ConnPid),
|
||||
{error, {403, <<"Admin access required">>}};
|
||||
{gun_response, ConnPid, StreamRef, nofin, 403, _} ->
|
||||
ct:pal(" ERROR: HTTP 403 Forbidden (nofin)"),
|
||||
gun:close(ConnPid),
|
||||
{error, {403, <<"Admin access required">>}};
|
||||
{gun_response, ConnPid, StreamRef, fin, Status, _} ->
|
||||
ct:pal(" ERROR: HTTP ~p", [Status]),
|
||||
gun:close(ConnPid),
|
||||
{error, {Status, <<"WebSocket upgrade failed">>}};
|
||||
{gun_response, ConnPid, StreamRef, nofin, Status, _} ->
|
||||
ct:pal(" ERROR: HTTP ~p (nofin)", [Status]),
|
||||
gun:close(ConnPid),
|
||||
{error, {Status, <<"WebSocket upgrade failed">>}};
|
||||
{gun_error, ConnPid, Reason} ->
|
||||
ct:pal(" ERROR: ~p", [Reason]),
|
||||
gun:close(ConnPid),
|
||||
{error, Reason}
|
||||
after 5000 ->
|
||||
ct:pal(" ERROR: Timeout"),
|
||||
gun:close(ConnPid),
|
||||
{error, timeout}
|
||||
end.
|
||||
|
||||
test_ws_send(ConnPid, Data) ->
|
||||
Msg = jsx:encode(Data),
|
||||
ct:pal(" Sending: ~s", [Msg]),
|
||||
case catch gun:ws_send(ConnPid, {text, Msg}) of
|
||||
ok -> ok;
|
||||
{'EXIT', {undef, _}} ->
|
||||
% Пробуем альтернативный синтаксис
|
||||
gun:ws_send(ConnPid, fin, {text, Msg});
|
||||
Other ->
|
||||
ct:pal(" ERROR sending: ~p", [Other]),
|
||||
error({ws_send_failed, Other})
|
||||
end.
|
||||
|
||||
test_ws_recv(ConnPid) ->
|
||||
test_ws_recv(ConnPid, 3000).
|
||||
|
||||
test_ws_recv(ConnPid, Timeout) ->
|
||||
receive
|
||||
{gun_ws, ConnPid, _StreamRef, {text, Msg}} ->
|
||||
ct:pal(" Received (with StreamRef): ~s", [Msg]),
|
||||
{ok, jsx:decode(Msg, [return_maps])};
|
||||
{gun_ws, ConnPid, {text, Msg}} ->
|
||||
ct:pal(" Received: ~s", [Msg]),
|
||||
{ok, jsx:decode(Msg, [return_maps])};
|
||||
{gun_ws, ConnPid, _StreamRef, Frame} ->
|
||||
ct:pal(" Received frame: ~p", [Frame]),
|
||||
{ok, Frame};
|
||||
{gun_ws, ConnPid, Frame} ->
|
||||
ct:pal(" Received: ~p", [Frame]),
|
||||
{ok, Frame};
|
||||
{gun_error, ConnPid, Reason} ->
|
||||
ct:pal(" ERROR: gun_error ~p", [Reason]),
|
||||
{error, Reason};
|
||||
Other ->
|
||||
ct:pal(" Received unexpected: ~p", [Other]),
|
||||
test_ws_recv(ConnPid, Timeout)
|
||||
after Timeout ->
|
||||
{error, timeout}
|
||||
end.
|
||||
|
||||
test_ws_close(ConnPid) ->
|
||||
gun:close(ConnPid).
|
||||
|
||||
ws_port("ws://localhost:8081" ++ _) -> 8081;
|
||||
ws_port("ws://localhost:8446" ++ _) -> 8446.
|
||||
Reference in New Issue
Block a user