Рефакторинг обработчиков. Часть 3 #21
This commit is contained in:
180
test/api/admins/admin_admins_tests.erl
Normal file
180
test/api/admins/admin_admins_tests.erl
Normal file
@@ -0,0 +1,180 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для управления администраторами.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/admins
|
||||
%%% POST /v1/admin/admins
|
||||
%%% GET /v1/admin/admins/:id
|
||||
%%% PUT /v1/admin/admins/:id
|
||||
%%% DELETE /v1/admin/admins/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение списка администраторов (только суперадмин)
|
||||
%%% - создание нового администратора
|
||||
%%% - получение администратора по ID
|
||||
%%% - обновление администратора
|
||||
%%% - удаление (блокировку) администратора
|
||||
%%% - ошибки 403 для обычного администратора
|
||||
%%% - ошибки 409 (дубликат email) и 400 (неверная роль) при создании
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_admins_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Admin Admins Tests ==="),
|
||||
SuperToken = api_test_runner:get_superadmin_token(),
|
||||
|
||||
% Создаём нового администратора для проверки CRUD (у него будет свой ID)
|
||||
AdminEmail = api_test_runner:unique_email(<<"newadmin.admins.tests">>),
|
||||
AdminPassword = <<"AdminPass123">>,
|
||||
#{<<"id">> := AdminId} = create_admin(SuperToken, AdminEmail, AdminPassword, <<"admin">>),
|
||||
|
||||
% Токен обычного администратора (уже существующего admin@eventhub.local)
|
||||
AdminToken = api_test_runner:get_admin_token(),
|
||||
|
||||
% Тесты с правами суперадмина
|
||||
test_list_admins(SuperToken),
|
||||
test_get_admin(SuperToken, AdminId),
|
||||
test_update_admin(SuperToken, AdminId),
|
||||
test_delete_admin(SuperToken, AdminId),
|
||||
|
||||
% Тесты ограничений для обычного админа (используем готовый токен)
|
||||
test_list_admins_forbidden(AdminToken),
|
||||
test_create_admin_forbidden(AdminToken),
|
||||
test_get_admin_forbidden(AdminToken, AdminId),
|
||||
test_update_admin_forbidden(AdminToken, AdminId),
|
||||
test_delete_admin_forbidden(AdminToken, AdminId),
|
||||
|
||||
% Тесты валидации при создании
|
||||
test_create_admin_duplicate_email(SuperToken),
|
||||
test_create_admin_invalid_role(SuperToken),
|
||||
|
||||
ct:pal("=== All admin admins tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/admin/admins – список администраторов.
|
||||
-spec test_list_admins(binary()) -> ok.
|
||||
test_list_admins(Token) ->
|
||||
ct:pal(" TEST: List all admins"),
|
||||
Admins = api_test_runner:admin_get(<<"/v1/admin/admins">>, Token),
|
||||
?assert(is_list(Admins)),
|
||||
?assert(length(Admins) >= 1),
|
||||
ct:pal(" OK: ~p admins", [length(Admins)]).
|
||||
|
||||
%% @doc GET /v1/admin/admins/:id – получение администратора.
|
||||
-spec test_get_admin(binary(), binary()) -> ok.
|
||||
test_get_admin(Token, AdminId) ->
|
||||
ct:pal(" TEST: Get admin by ID"),
|
||||
Path = <<"/v1/admin/admins/", AdminId/binary>>,
|
||||
Admin = api_test_runner:admin_get(Path, Token),
|
||||
?assertEqual(AdminId, maps:get(<<"id">>, Admin)),
|
||||
ct:pal(" OK: ~s", [maps:get(<<"email">>, Admin)]).
|
||||
|
||||
%% @doc PUT /v1/admin/admins/:id – обновление администратора.
|
||||
-spec test_update_admin(binary(), binary()) -> ok.
|
||||
test_update_admin(Token, AdminId) ->
|
||||
ct:pal(" TEST: Update admin"),
|
||||
Path = <<"/v1/admin/admins/", AdminId/binary>>,
|
||||
Updated = api_test_runner:admin_put(Path, Token, #{nickname => <<"UpdatedAdmin">>}),
|
||||
?assertEqual(<<"UpdatedAdmin">>, maps:get(<<"nickname">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc DELETE /v1/admin/admins/:id – удаление (блокировка).
|
||||
-spec test_delete_admin(binary(), binary()) -> ok.
|
||||
test_delete_admin(Token, AdminId) ->
|
||||
ct:pal(" TEST: Delete (block) admin"),
|
||||
Path = <<"/v1/admin/admins/", AdminId/binary>>,
|
||||
Result = api_test_runner:admin_request(delete, Path, Token),
|
||||
{ok, 200, _, _} = Result,
|
||||
ct:pal(" OK: admin blocked (or deleted)"),
|
||||
% Проверяем, что админ больше не в списке
|
||||
Admins = api_test_runner:admin_get(<<"/v1/admin/admins">>, Token),
|
||||
?assertNot(lists:any(fun(A) -> maps:get(<<"id">>, A) =:= AdminId end, Admins)).
|
||||
|
||||
%% ── Тесты ограничений ──
|
||||
|
||||
-spec test_list_admins_forbidden(binary()) -> ok.
|
||||
test_list_admins_forbidden(Token) ->
|
||||
ct:pal(" TEST: List admins as non-superadmin (403)"),
|
||||
Resp = api_test_runner:admin_request(get, <<"/v1/admin/admins">>, Token),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
-spec test_create_admin_forbidden(binary()) -> ok.
|
||||
test_create_admin_forbidden(Token) ->
|
||||
ct:pal(" TEST: Create admin as non-superadmin (403)"),
|
||||
Resp = api_test_runner:admin_request(post, <<"/v1/admin/admins">>, Token,
|
||||
jsx:encode(#{email => <<"x@x.com">>, password => <<"p">>, role => <<"moderator">>})),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
-spec test_get_admin_forbidden(binary(), binary()) -> ok.
|
||||
test_get_admin_forbidden(Token, AdminId) ->
|
||||
ct:pal(" TEST: Get admin by ID as non-superadmin (403)"),
|
||||
Path = <<"/v1/admin/admins/", AdminId/binary>>,
|
||||
Resp = api_test_runner:admin_request(get, Path, Token),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
-spec test_update_admin_forbidden(binary(), binary()) -> ok.
|
||||
test_update_admin_forbidden(Token, AdminId) ->
|
||||
ct:pal(" TEST: Update admin as non-superadmin (403)"),
|
||||
Path = <<"/v1/admin/admins/", AdminId/binary>>,
|
||||
Resp = api_test_runner:admin_request(put, Path, Token, jsx:encode(#{nickname => <<"fail">>})),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
-spec test_delete_admin_forbidden(binary(), binary()) -> ok.
|
||||
test_delete_admin_forbidden(Token, AdminId) ->
|
||||
ct:pal(" TEST: Delete admin as non-superadmin (403)"),
|
||||
Path = <<"/v1/admin/admins/", AdminId/binary>>,
|
||||
Resp = api_test_runner:admin_request(delete, Path, Token),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
%% ── Валидация создания ──
|
||||
|
||||
-spec test_create_admin_duplicate_email(binary()) -> ok.
|
||||
test_create_admin_duplicate_email(SuperToken) ->
|
||||
ct:pal(" TEST: Create admin with duplicate email (409)"),
|
||||
Email = api_test_runner:unique_email(<<"dupadmin">>),
|
||||
% Создаём первого администратора
|
||||
{ok, 201, _, _} = api_test_runner:admin_request(post, <<"/v1/admin/admins">>, SuperToken,
|
||||
jsx:encode(#{email => Email, password => <<"Pass1234">>, role => <<"admin">>})),
|
||||
% Пытаемся создать второго с тем же email
|
||||
Resp = api_test_runner:admin_request(post, <<"/v1/admin/admins">>, SuperToken,
|
||||
jsx:encode(#{email => Email, password => <<"Pass1234">>, role => <<"admin">>})),
|
||||
?assertMatch({ok, 409, _, _}, Resp),
|
||||
ct:pal(" OK: got 409").
|
||||
|
||||
-spec test_create_admin_invalid_role(binary()) -> ok.
|
||||
test_create_admin_invalid_role(SuperToken) ->
|
||||
ct:pal(" TEST: Create admin with invalid role (400)"),
|
||||
Resp = api_test_runner:admin_request(post, <<"/v1/admin/admins">>, SuperToken,
|
||||
jsx:encode(#{email => <<"badrole@test.local">>, password => <<"Pass1234">>, role => <<"superhero">>})),
|
||||
?assertMatch({ok, 400, _, _}, Resp),
|
||||
ct:pal(" OK: got 400").
|
||||
|
||||
%%%===================================================================
|
||||
%%% Вспомогательные функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @private Создаёт администратора и возвращает его данные.
|
||||
-spec create_admin(binary(), binary(), binary(), binary()) -> map().
|
||||
create_admin(Token, Email, Password, Role) ->
|
||||
ct:pal(" Creating test admin ~s...", [Email]),
|
||||
{ok, 201, _, Body} = api_test_runner:admin_request(post, <<"/v1/admin/admins">>, Token,
|
||||
jsx:encode(#{email => Email, password => Password, role => Role})),
|
||||
jsx:decode(list_to_binary(Body), [return_maps]).
|
||||
79
test/api/admins/admin_audit_tests.erl
Normal file
79
test/api/admins/admin_audit_tests.erl
Normal file
@@ -0,0 +1,79 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для аудита.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/audit
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение списка записей аудита
|
||||
%%% - фильтрацию по admin_id
|
||||
%%% - пагинацию
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_audit_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Admin Audit Tests ==="),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
#{<<"id">> := UserId} = api_test_runner:client_get(<<"/v1/user/me">>, UserToken),
|
||||
AdminToken = api_test_runner:get_admin_token(),
|
||||
SuperToken = api_test_runner:get_superadmin_token(),
|
||||
|
||||
% Создаём тестовую запись аудита
|
||||
Me = api_test_runner:admin_get(<<"/v1/admin/me">>, AdminToken),
|
||||
AdminId = maps:get(<<"id">>, Me),
|
||||
|
||||
Path = <<"/v1/admin/user/", UserId/binary>>,
|
||||
Body = #{<<"action">> => <<"block">>, <<"reason">> => <<"Test">>},
|
||||
api_test_runner:admin_put(Path, AdminToken, Body),
|
||||
ct:sleep(200),
|
||||
Body2 = #{<<"action">> => <<"unblock">>, <<"reason">> => <<"Test">>},
|
||||
api_test_runner:admin_put(Path, AdminToken, Body2),
|
||||
|
||||
test_list_audit(SuperToken),
|
||||
test_filter_audit(SuperToken, AdminId),
|
||||
test_audit_pagination(SuperToken),
|
||||
test_list_admin_forbidden(AdminToken),
|
||||
|
||||
ct:pal("=== All admin audit tests passed ==="),
|
||||
ok.
|
||||
|
||||
test_list_audit(Token) ->
|
||||
ct:pal(" TEST: List all audit records"),
|
||||
Records = api_test_runner:admin_get(<<"/v1/admin/audit">>, Token),
|
||||
?assert(is_list(Records)),
|
||||
?assert(length(Records) >= 1),
|
||||
ct:pal(" OK: ~p records", [length(Records)]).
|
||||
|
||||
test_filter_audit(Token, AdminId) ->
|
||||
ct:pal(" TEST: Filter audit by admin_id"),
|
||||
Records = api_test_runner:admin_get(<<"/v1/admin/audit?admin_id=", AdminId/binary>>, Token),
|
||||
?assert(is_list(Records)),
|
||||
[?assertEqual(AdminId, maps:get(<<"admin_id">>, R)) || R <- Records],
|
||||
ct:pal(" OK: ~p records", [length(Records)]).
|
||||
|
||||
test_audit_pagination(Token) ->
|
||||
ct:pal(" TEST: Audit pagination"),
|
||||
Page1 = api_test_runner:admin_get(<<"/v1/admin/audit?limit=1&offset=0">>, Token),
|
||||
?assert(length(Page1) >= 1),
|
||||
Page2 = api_test_runner:admin_get(<<"/v1/admin/audit?limit=1&offset=1">>, Token),
|
||||
?assert(length(Page2) >= 0),
|
||||
case {Page1, Page2} of
|
||||
{[First|_], [Second|_]} ->
|
||||
Id1 = maps:get(<<"id">>, First),
|
||||
Id2 = maps:get(<<"id">>, Second),
|
||||
?assertNotEqual(Id1, Id2);
|
||||
_ -> ok
|
||||
end,
|
||||
ct:pal(" OK").
|
||||
|
||||
-spec test_list_admin_forbidden(binary()) -> ok.
|
||||
test_list_admin_forbidden(Token) ->
|
||||
ct:pal(" TEST: List audit as non-superadmin (403)"),
|
||||
Resp = api_test_runner:admin_request(get, <<"/v1/admin/audit">>, Token),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
94
test/api/admins/admin_banned_words_tests.erl
Normal file
94
test/api/admins/admin_banned_words_tests.erl
Normal file
@@ -0,0 +1,94 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для управления бан-словами.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/banned-words
|
||||
%%% POST /v1/admin/banned-words
|
||||
%%% DELETE /v1/admin/banned-words/:word
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение списка слов
|
||||
%%% - добавление нового слова
|
||||
%%% - удаление слова
|
||||
%%% - пагинацию
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_banned_words_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Admin Banned Words Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
|
||||
% Добавляем два исходных слова
|
||||
Unique = integer_to_binary(erlang:system_time()),
|
||||
Word1 = <<"badword1_", Unique/binary>>,
|
||||
Word2 = <<"badword2_", Unique/binary>>,
|
||||
api_test_runner:admin_post(<<"/v1/admin/banned-words">>, Token, #{<<"word">> => Word1}),
|
||||
api_test_runner:admin_post(<<"/v1/admin/banned-words">>, Token, #{<<"word">> => Word2}),
|
||||
|
||||
test_list_words(Token, Word1, Word2),
|
||||
test_add_word(Token),
|
||||
test_words_pagination(Token),
|
||||
test_delete_word(Token, Word1),
|
||||
test_delete_word(Token, Word2),
|
||||
|
||||
ct:pal("=== All admin banned words tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/admin/banned-words – список всех слов.
|
||||
-spec test_list_words(binary(), binary(), binary()) -> ok.
|
||||
test_list_words(Token, Word1, Word2) ->
|
||||
ct:pal(" TEST: List all banned words"),
|
||||
Words = api_test_runner:admin_get(<<"/v1/admin/banned-words">>, Token),
|
||||
?assert(is_list(Words)),
|
||||
?assert(length(Words) >= 2),
|
||||
?assert(lists:any(fun(W) -> maps:get(<<"word">>, W) =:= Word1 orelse maps:get(<<"word">>, W) =:= Word2 end, Words)),
|
||||
ct:pal(" OK: ~p words", [length(Words)]).
|
||||
|
||||
%% @doc POST /v1/admin/banned-words – добавление нового слова.
|
||||
-spec test_add_word(binary()) -> ok.
|
||||
test_add_word(Token) ->
|
||||
ct:pal(" TEST: Add a new banned word"),
|
||||
Unique = integer_to_binary(erlang:system_time()),
|
||||
NewWord = <<"newbadword", Unique/binary>>,
|
||||
Result = api_test_runner:admin_post(<<"/v1/admin/banned-words">>, Token, #{<<"word">> => NewWord}),
|
||||
?assertEqual(<<"added">>, maps:get(<<"status">>, Result)),
|
||||
Words = api_test_runner:admin_get(<<"/v1/admin/banned-words">>, Token),
|
||||
?assert(lists:any(fun(W) -> maps:get(<<"word">>, W) =:= NewWord end, Words)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc DELETE /v1/admin/banned-words/:word – удаление слова.
|
||||
-spec test_delete_word(binary(), binary()) -> ok.
|
||||
test_delete_word(Token, Word) ->
|
||||
ct:pal(" TEST: Delete a banned word"),
|
||||
Path = <<"/v1/admin/banned-words/", Word/binary>>,
|
||||
Result = api_test_runner:admin_delete(Path, Token),
|
||||
?assertEqual(<<"deleted">>, maps:get(<<"status">>, Result)),
|
||||
Words = api_test_runner:admin_get(<<"/v1/admin/banned-words">>, Token),
|
||||
?assertNot(lists:any(fun(W) -> maps:get(<<"word">>, W) =:= Word end, Words)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc GET /v1/admin/banned-words?limit=...&offset=... – пагинация.
|
||||
-spec test_words_pagination(binary()) -> ok.
|
||||
test_words_pagination(Token) ->
|
||||
ct:pal(" TEST: Banned words pagination"),
|
||||
Page1 = api_test_runner:admin_get(<<"/v1/admin/banned-words?limit=1&offset=0">>, Token),
|
||||
?assert(length(Page1) >= 1),
|
||||
Page2 = api_test_runner:admin_get(<<"/v1/admin/banned-words?limit=1&offset=1">>, Token),
|
||||
?assert(length(Page2) >= 1),
|
||||
Id1 = maps:get(<<"id">>, hd(Page1)),
|
||||
Id2 = maps:get(<<"id">>, hd(Page2)),
|
||||
?assertNotEqual(Id1, Id2),
|
||||
ct:pal(" OK").
|
||||
119
test/api/admins/admin_events_tests.erl
Normal file
119
test/api/admins/admin_events_tests.erl
Normal file
@@ -0,0 +1,119 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для управления событиями.
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/events
|
||||
%%% GET /v1/admin/events/:id
|
||||
%%% PUT /v1/admin/events/:id
|
||||
%%% DELETE /v1/admin/events/:id
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_events_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% EUnit test generator
|
||||
%%%===================================================================
|
||||
|
||||
test() ->
|
||||
ct:pal("=== Admin Events Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
%% Создаём тестовый календарь и два события
|
||||
CalId = api_test_runner:create_calendar(UserToken, #{title => <<"EventsTestCal">>}),
|
||||
Event1Id = api_test_runner:create_event(UserToken, CalId, #{
|
||||
title => <<"Test Event Alpha">>,
|
||||
start_time => api_test_runner:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
Event2Id = api_test_runner:create_event(UserToken, CalId, #{
|
||||
title => <<"Beta Event">>,
|
||||
start_time => api_test_runner:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
|
||||
%% Выполняем тесты
|
||||
test_list_events(Token, CalId, Event1Id, Event2Id),
|
||||
test_get_event(Token, Event1Id),
|
||||
test_update_event(Token, Event1Id),
|
||||
test_delete_event(Token, Event2Id),
|
||||
test_filter_events(Token, CalId),
|
||||
test_search_events(Token, CalId),
|
||||
test_event_pagination(Token, CalId),
|
||||
test_delete_event(Token, Event1Id),
|
||||
|
||||
ct:pal("=== All admin events tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/admin/events – список событий (базовая проверка).
|
||||
test_list_events(Token, _CalId, _Event1Id, _Event2Id) ->
|
||||
ct:pal(" TEST: List all events"),
|
||||
Events = api_test_runner:admin_get(<<"/v1/admin/events">>, Token),
|
||||
?assert(is_list(Events)),
|
||||
?assert(length(Events) >= 2),
|
||||
ct:pal(" OK: ~p events found", [length(Events)]).
|
||||
|
||||
%% @doc GET /v1/admin/events/:id – получение события по ID.
|
||||
test_get_event(Token, EventId) ->
|
||||
ct:pal(" TEST: Get event by ID"),
|
||||
Path = <<"/v1/admin/events/", EventId/binary>>,
|
||||
Event = api_test_runner:admin_get(Path, Token),
|
||||
?assertEqual(EventId, maps:get(<<"id">>, Event)),
|
||||
?assertEqual(<<"active">>, maps:get(<<"status">>, Event)),
|
||||
ct:pal(" OK: ~s", [maps:get(<<"title">>, Event)]).
|
||||
|
||||
%% @doc PUT /v1/admin/events/:id – обновление события.
|
||||
test_update_event(Token, EventId) ->
|
||||
ct:pal(" TEST: Update event"),
|
||||
Path = <<"/v1/admin/events/", EventId/binary>>,
|
||||
Updates = #{<<"title">> => <<"Updated by admin">>, <<"description">> => <<"Test update">>},
|
||||
Updated = api_test_runner:admin_put(Path, Token, Updates),
|
||||
?assertEqual(<<"Updated by admin">>, maps:get(<<"title">>, Updated)),
|
||||
?assertEqual(<<"Test update">>, maps:get(<<"description">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc DELETE /v1/admin/events/:id – удаление события.
|
||||
test_delete_event(Token, EventId) ->
|
||||
ct:pal(" TEST: Delete event"),
|
||||
Path = <<"/v1/admin/events/", EventId/binary>>,
|
||||
Deleted = api_test_runner:admin_delete(Path, Token),
|
||||
?assertEqual(<<"deleted">>, maps:get(<<"status">>, Deleted)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc GET /v1/admin/events?status=active – фильтрация по статусу.
|
||||
test_filter_events(Token, CalId) ->
|
||||
ct:pal(" TEST: Filter events by status=active"),
|
||||
Path = <<"/v1/admin/events?calendar_id=", CalId/binary, "&status=active">>,
|
||||
Events = api_test_runner:admin_get(Path, Token),
|
||||
?assert(is_list(Events)),
|
||||
[?assertEqual(<<"active">>, maps:get(<<"status">>, E)) || E <- Events],
|
||||
ct:pal(" OK: ~p events", [length(Events)]).
|
||||
|
||||
%% @doc GET /v1/admin/events?q=... – поиск по подстроке.
|
||||
test_search_events(Token, CalId) ->
|
||||
ct:pal(" TEST: Search events by title substring"),
|
||||
Path = <<"/v1/admin/events?calendar_id=", CalId/binary, "&q=Updated">>,
|
||||
Events = api_test_runner:admin_get(Path, Token),
|
||||
?assert(is_list(Events)),
|
||||
?assert(length(Events) >= 1),
|
||||
[?assertNotEqual(nomatch, binary:match(maps:get(<<"title">>, E), <<"Updated">>)) || E <- Events],
|
||||
ct:pal(" OK: ~p results", [length(Events)]).
|
||||
|
||||
%% @doc GET /v1/admin/events?limit=...&offset=... – пагинация.
|
||||
test_event_pagination(Token, CalId) ->
|
||||
ct:pal(" TEST: Event pagination"),
|
||||
Base = <<"/v1/admin/events?calendar_id=", CalId/binary, "&status=all&sort=title&order=asc">>,
|
||||
Page1 = api_test_runner:admin_get(<<Base/binary, "&limit=1&offset=0">>, Token),
|
||||
?assertEqual(1, length(Page1)),
|
||||
Page2 = api_test_runner:admin_get(<<Base/binary, "&limit=1&offset=1">>, Token),
|
||||
?assertEqual(1, length(Page2)),
|
||||
Id1 = maps:get(<<"id">>, hd(Page1)),
|
||||
Id2 = maps:get(<<"id">>, hd(Page2)),
|
||||
?assertNotEqual(Id1, Id2),
|
||||
ct:pal(" OK").
|
||||
36
test/api/admins/admin_me_tests.erl
Normal file
36
test/api/admins/admin_me_tests.erl
Normal file
@@ -0,0 +1,36 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для профиля текущего администратора.
|
||||
%%% Покрывает GET /v1/admin/me и PUT /v1/admin/me.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_me_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Admin Me Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
|
||||
test_get_me(Token),
|
||||
test_update_me(Token),
|
||||
|
||||
ct:pal("=== All admin me tests passed ==="),
|
||||
ok.
|
||||
|
||||
test_get_me(Token) ->
|
||||
ct:pal(" TEST: Get current admin profile"),
|
||||
Me = api_test_runner:admin_get(<<"/v1/admin/me">>, Token),
|
||||
?assert(is_map(Me)),
|
||||
?assert(maps:is_key(<<"id">>, Me)),
|
||||
?assert(maps:is_key(<<"email">>, Me)),
|
||||
ct:pal(" OK: got profile for ~s", [maps:get(<<"email">>, Me)]).
|
||||
|
||||
test_update_me(Token) ->
|
||||
ct:pal(" TEST: Update current admin profile"),
|
||||
Updated = api_test_runner:admin_put(<<"/v1/admin/me">>, Token,
|
||||
#{nickname => <<"TestNick">>, timezone => <<"UTC">>}),
|
||||
?assertEqual(<<"TestNick">>, maps:get(<<"nickname">>, Updated)),
|
||||
?assertEqual(<<"UTC">>, maps:get(<<"timezone">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
133
test/api/admins/admin_moderation_tests.erl
Normal file
133
test/api/admins/admin_moderation_tests.erl
Normal file
@@ -0,0 +1,133 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для модерации сущностей.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% PUT /v1/admin/:target_type/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - заморозку/разморозку календаря
|
||||
%%% - заморозку/разморозку события
|
||||
%%% - скрытие/раскрытие отзыва
|
||||
%%% - блокировку/разблокировку пользователя
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_moderation_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Admin Moderation Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
% Создаём календарь и событие
|
||||
CalId = api_test_runner:create_calendar(UserToken, #{title => <<"ModTestCal">>}),
|
||||
EventId = api_test_runner:create_event(UserToken, CalId, #{
|
||||
title => <<"Event to moderate">>,
|
||||
start_time => api_test_runner:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
|
||||
% Создаём пользователя для блокировки
|
||||
UserEmail = api_test_runner:unique_email(<<"moduser">>),
|
||||
UserTok = api_test_runner:register_and_login(UserEmail, <<"pass">>),
|
||||
#{<<"id">> := UserId} = api_test_runner:client_get(<<"/v1/user/me">>, UserTok),
|
||||
|
||||
% Создаём отзыв (требуется участие)
|
||||
#{<<"id">> := BookingId} = api_test_runner:client_post(
|
||||
<<"/v1/events/", EventId/binary, "/bookings">>, UserTok, #{}),
|
||||
api_test_runner:client_put(<<"/v1/bookings/", BookingId/binary>>, UserToken,
|
||||
#{action => <<"confirm">>}),
|
||||
#{<<"id">> := ReviewId} = api_test_runner:client_post(<<"/v1/reviews">>, UserTok, #{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 3,
|
||||
comment => <<"Moderate me">>
|
||||
}),
|
||||
|
||||
% Тесты модерации
|
||||
test_freeze_calendar(Token, CalId),
|
||||
test_unfreeze_calendar(Token, CalId),
|
||||
test_freeze_event(Token, EventId),
|
||||
test_unfreeze_event(Token, EventId),
|
||||
test_hide_review(Token, ReviewId),
|
||||
test_unhide_review(Token, ReviewId),
|
||||
test_block_user(Token, UserId),
|
||||
test_unblock_user(Token, UserId),
|
||||
|
||||
ct:pal("=== All admin moderation tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
test_freeze_calendar(Token, Id) ->
|
||||
ct:pal(" TEST: Freeze calendar"),
|
||||
Path = <<"/v1/admin/calendar/", Id/binary>>,
|
||||
Body = #{<<"action">> => <<"freeze">>, <<"reason">> => <<"Test">>},
|
||||
Resp = api_test_runner:admin_put(Path, Token, Body),
|
||||
?assertEqual(<<"frozen">>, maps:get(<<"status">>, Resp)),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_unfreeze_calendar(Token, Id) ->
|
||||
ct:pal(" TEST: Unfreeze calendar"),
|
||||
Path = <<"/v1/admin/calendar/", Id/binary>>,
|
||||
Body = #{<<"action">> => <<"unfreeze">>, <<"reason">> => <<"Test">>},
|
||||
Resp = api_test_runner:admin_put(Path, Token, Body),
|
||||
?assertEqual(<<"active">>, maps:get(<<"status">>, Resp)),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_freeze_event(Token, Id) ->
|
||||
ct:pal(" TEST: Freeze event"),
|
||||
Path = <<"/v1/admin/event/", Id/binary>>,
|
||||
Body = #{<<"action">> => <<"freeze">>, <<"reason">> => <<"Test">>},
|
||||
Resp = api_test_runner:admin_put(Path, Token, Body),
|
||||
?assertEqual(<<"frozen">>, maps:get(<<"status">>, Resp)),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_unfreeze_event(Token, Id) ->
|
||||
ct:pal(" TEST: Unfreeze event"),
|
||||
Path = <<"/v1/admin/event/", Id/binary>>,
|
||||
Body = #{<<"action">> => <<"unfreeze">>, <<"reason">> => <<"Test">>},
|
||||
Resp = api_test_runner:admin_put(Path, Token, Body),
|
||||
?assertEqual(<<"active">>, maps:get(<<"status">>, Resp)),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_hide_review(Token, ReviewId) ->
|
||||
ct:pal(" TEST: Hide review"),
|
||||
Path = <<"/v1/admin/review/", ReviewId/binary>>,
|
||||
Body = #{<<"action">> => <<"hide">>, <<"reason">> => <<"Test">>},
|
||||
Resp = api_test_runner:admin_put(Path, Token, Body),
|
||||
?assertEqual(<<"hidden">>, maps:get(<<"status">>, Resp)),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_unhide_review(Token, ReviewId) ->
|
||||
ct:pal(" TEST: Unhide review"),
|
||||
Path = <<"/v1/admin/review/", ReviewId/binary>>,
|
||||
Body = #{<<"action">> => <<"unhide">>, <<"reason">> => <<"Test">>},
|
||||
Resp = api_test_runner:admin_put(Path, Token, Body),
|
||||
?assertEqual(<<"visible">>, maps:get(<<"status">>, Resp)),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_block_user(Token, UserId) ->
|
||||
ct:pal(" TEST: Block user"),
|
||||
Path = <<"/v1/admin/user/", UserId/binary>>,
|
||||
Body = #{<<"action">> => <<"block">>, <<"reason">> => <<"Test">>},
|
||||
Resp = api_test_runner:admin_put(Path, Token, Body),
|
||||
?assertEqual(<<"blocked">>, maps:get(<<"status">>, Resp)),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_unblock_user(Token, UserId) ->
|
||||
ct:pal(" TEST: Unblock user"),
|
||||
Path = <<"/v1/admin/user/", UserId/binary>>,
|
||||
Body = #{<<"action">> => <<"unblock">>, <<"reason">> => <<"Test">>},
|
||||
Resp = api_test_runner:admin_put(Path, Token, Body),
|
||||
?assertEqual(<<"active">>, maps:get(<<"status">>, Resp)),
|
||||
ct:pal(" OK").
|
||||
134
test/api/admins/admin_reports_tests.erl
Normal file
134
test/api/admins/admin_reports_tests.erl
Normal file
@@ -0,0 +1,134 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для управления жалобами.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/reports
|
||||
%%% GET /v1/admin/reports/:id
|
||||
%%% PUT /v1/admin/reports/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение списка жалоб
|
||||
%%% - получение жалобы по ID
|
||||
%%% - обновление статуса жалобы (review, dismiss)
|
||||
%%% - фильтрацию по статусу и типу цели
|
||||
%%% - пагинацию
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_reports_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Admin Reports Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
% Создаём тестовые данные: календарь, событие
|
||||
CalId = api_test_runner:create_calendar(UserToken, #{title => <<"ReportsTestCal">>}),
|
||||
EventId = api_test_runner:create_event(UserToken, CalId, #{
|
||||
title => <<"Event for reporting">>,
|
||||
start_time => api_test_runner:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
|
||||
% Создаём две жалобы от имени пользователя
|
||||
#{<<"id">> := Report1Id} = api_test_runner:client_post(<<"/v1/reports">>, UserToken, #{
|
||||
<<"target_type">> => <<"event">>,
|
||||
<<"target_id">> => EventId,
|
||||
<<"reason">> => <<"Inappropriate content">>
|
||||
}),
|
||||
#{<<"id">> := Report2Id} = api_test_runner:client_post(<<"/v1/reports">>, UserToken, #{
|
||||
<<"target_type">> => <<"event">>,
|
||||
<<"target_id">> => EventId,
|
||||
<<"reason">> => <<"Spam">>
|
||||
}),
|
||||
|
||||
test_list_reports(Token, Report1Id),
|
||||
test_get_report(Token, Report1Id),
|
||||
test_review_report(Token, Report1Id),
|
||||
test_dismiss_report(Token, Report2Id),
|
||||
test_filter_reports(Token),
|
||||
test_report_pagination(Token),
|
||||
|
||||
ct:pal("=== All admin reports tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/admin/reports – проверяет получение списка жалоб.
|
||||
-spec test_list_reports(binary(), binary()) -> ok.
|
||||
test_list_reports(Token, ReportId) ->
|
||||
ct:pal(" TEST: List all reports"),
|
||||
Reports = api_test_runner:admin_get(<<"/v1/admin/reports">>, Token),
|
||||
?assert(is_list(Reports)),
|
||||
?assert(length(Reports) >= 2),
|
||||
?assert(lists:any(fun(R) -> maps:get(<<"id">>, R) =:= ReportId end, Reports)),
|
||||
ct:pal(" OK: ~p reports", [length(Reports)]).
|
||||
|
||||
%% @doc GET /v1/admin/reports/:id – проверяет получение жалобы по ID.
|
||||
-spec test_get_report(binary(), binary()) -> ok.
|
||||
test_get_report(Token, ReportId) ->
|
||||
ct:pal(" TEST: Get report by ID"),
|
||||
Path = <<"/v1/admin/reports/", ReportId/binary>>,
|
||||
Report = api_test_runner:admin_get(Path, Token),
|
||||
?assertEqual(ReportId, maps:get(<<"id">>, Report)),
|
||||
?assertEqual(<<"pending">>, maps:get(<<"status">>, Report)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc PUT /v1/admin/reports/:id – проверяет изменение статуса на reviewed.
|
||||
-spec test_review_report(binary(), binary()) -> ok.
|
||||
test_review_report(Token, ReportId) ->
|
||||
ct:pal(" TEST: Review report (set status to reviewed)"),
|
||||
Path = <<"/v1/admin/reports/", ReportId/binary>>,
|
||||
Updated = api_test_runner:admin_put(Path, Token, #{<<"status">> => <<"reviewed">>}),
|
||||
?assertEqual(<<"reviewed">>, maps:get(<<"status">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc PUT /v1/admin/reports/:id – проверяет изменение статуса на dismissed.
|
||||
-spec test_dismiss_report(binary(), binary()) -> ok.
|
||||
test_dismiss_report(Token, ReportId) ->
|
||||
ct:pal(" TEST: Dismiss report (set status to dismissed)"),
|
||||
Path = <<"/v1/admin/reports/", ReportId/binary>>,
|
||||
Updated = api_test_runner:admin_put(Path, Token, #{<<"status">> => <<"dismissed">>}),
|
||||
?assertEqual(<<"dismissed">>, maps:get(<<"status">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc GET /v1/admin/reports?status=...&target_type=... – проверяет фильтрацию.
|
||||
-spec test_filter_reports(binary()) -> ok.
|
||||
test_filter_reports(Token) ->
|
||||
ct:pal(" TEST: Filter reports by status=reviewed"),
|
||||
Reports = api_test_runner:admin_get(<<"/v1/admin/reports?status=reviewed">>, Token),
|
||||
?assert(is_list(Reports)),
|
||||
[?assertEqual(<<"reviewed">>, maps:get(<<"status">>, R)) || R <- Reports],
|
||||
ct:pal(" OK: ~p reviewed reports", [length(Reports)]),
|
||||
|
||||
ct:pal(" TEST: Filter reports by target_type=event"),
|
||||
Reports2 = api_test_runner:admin_get(<<"/v1/admin/reports?target_type=event">>, Token),
|
||||
?assert(is_list(Reports2)),
|
||||
[?assertEqual(<<"event">>, maps:get(<<"target_type">>, R)) || R <- Reports2],
|
||||
ct:pal(" OK: ~p reports for events", [length(Reports2)]).
|
||||
|
||||
%% @doc GET /v1/admin/reports?limit=...&offset=... – проверяет пагинацию.
|
||||
-spec test_report_pagination(binary()) -> ok.
|
||||
test_report_pagination(Token) ->
|
||||
ct:pal(" TEST: Report pagination"),
|
||||
Page1 = api_test_runner:admin_get(<<"/v1/admin/reports?limit=1&offset=0">>, Token),
|
||||
?assert(length(Page1) >= 1),
|
||||
Page2 = api_test_runner:admin_get(<<"/v1/admin/reports?limit=1&offset=1">>, Token),
|
||||
?assert(length(Page2) >= 0),
|
||||
case {Page1, Page2} of
|
||||
{[First|_], [Second|_]} ->
|
||||
Id1 = maps:get(<<"id">>, First),
|
||||
Id2 = maps:get(<<"id">>, Second),
|
||||
?assertNotEqual(Id1, Id2);
|
||||
_ -> ok
|
||||
end,
|
||||
ct:pal(" OK").
|
||||
136
test/api/admins/admin_reviews_tests.erl
Normal file
136
test/api/admins/admin_reviews_tests.erl
Normal file
@@ -0,0 +1,136 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для управления отзывами.
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/reviews
|
||||
%%% PATCH /v1/admin/reviews
|
||||
%%% GET /v1/admin/reviews/:id
|
||||
%%% PUT /v1/admin/reviews/:id
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_reviews_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% EUnit test generator
|
||||
%%%===================================================================
|
||||
|
||||
test() ->
|
||||
ct:pal("=== Admin Reviews Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
%% Создаём тестовые данные: календарь, событие
|
||||
CalId = api_test_runner:create_calendar(UserToken, #{title => <<"ReviewsTestCal">>}),
|
||||
EventId = api_test_runner:create_event(UserToken, CalId, #{
|
||||
title => <<"Event for review testing">>,
|
||||
start_time => api_test_runner:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
|
||||
%% Создаём двух пользователей для отзывов
|
||||
User1Email = api_test_runner:unique_email(<<"rev1">>),
|
||||
User1Token = api_test_runner:register_and_login(User1Email, <<"pass1">>),
|
||||
User2Email = api_test_runner:unique_email(<<"rev2">>),
|
||||
User2Token = api_test_runner:register_and_login(User2Email, <<"pass2">>),
|
||||
|
||||
%% Записываем пользователей на событие и подтверждаем бронирования
|
||||
#{<<"id">> := Booking1Id} = api_test_runner:client_post(
|
||||
<<"/v1/events/", EventId/binary, "/bookings">>, User1Token, #{}),
|
||||
#{<<"id">> := Booking2Id} = api_test_runner:client_post(
|
||||
<<"/v1/events/", EventId/binary, "/bookings">>, User2Token, #{}),
|
||||
|
||||
ct:pal(" Confirming bookings as calendar owner..."),
|
||||
api_test_runner:client_put(<<"/v1/bookings/", Booking1Id/binary>>, UserToken,
|
||||
#{action => <<"confirm">>}),
|
||||
api_test_runner:client_put(<<"/v1/bookings/", Booking2Id/binary>>, UserToken,
|
||||
#{action => <<"confirm">>}),
|
||||
|
||||
%% Оставляем отзывы от имени участников
|
||||
#{<<"id">> := Review1Id} = api_test_runner:client_post(<<"/v1/reviews">>, User1Token, #{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 5,
|
||||
comment => <<"Great!">>
|
||||
}),
|
||||
#{<<"id">> := Review2Id} = api_test_runner:client_post(<<"/v1/reviews">>, User2Token, #{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 4,
|
||||
comment => <<"Good">>
|
||||
}),
|
||||
|
||||
%% Запускаем тесты
|
||||
test_list_reviews(Token),
|
||||
test_filter_reviews(Token, EventId),
|
||||
test_bulk_update_status(Token, Review1Id, Review2Id),
|
||||
test_get_review(Token, Review1Id),
|
||||
test_update_review(Token, Review1Id),
|
||||
test_review_pagination(Token),
|
||||
|
||||
ct:pal("=== All admin reviews tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/admin/reviews – список отзывов.
|
||||
test_list_reviews(Token) ->
|
||||
ct:pal(" TEST: List all reviews"),
|
||||
Reviews = api_test_runner:admin_get(<<"/v1/admin/reviews">>, Token),
|
||||
?assert(is_list(Reviews)),
|
||||
?assert(length(Reviews) >= 2),
|
||||
ct:pal(" OK: ~p reviews", [length(Reviews)]).
|
||||
|
||||
%% @doc GET /v1/admin/reviews?target_type=event&target_id=... – фильтрация по цели.
|
||||
test_filter_reviews(Token, EventId) ->
|
||||
ct:pal(" TEST: Filter reviews by target"),
|
||||
Path = <<"/v1/admin/reviews?target_type=event&target_id=", EventId/binary>>,
|
||||
Reviews = api_test_runner:admin_get(Path, Token),
|
||||
?assert(is_list(Reviews)),
|
||||
?assert(length(Reviews) >= 2),
|
||||
[?assertEqual(EventId, maps:get(<<"target_id">>, R)) || R <- Reviews],
|
||||
ct:pal(" OK: ~p reviews for event", [length(Reviews)]).
|
||||
|
||||
%% @doc PATCH /v1/admin/reviews – массовое обновление статусов.
|
||||
test_bulk_update_status(Token, Review1Id, Review2Id) ->
|
||||
ct:pal(" TEST: Bulk update review statuses"),
|
||||
Body = [
|
||||
#{<<"id">> => Review1Id, <<"status">> => <<"visible">>},
|
||||
#{<<"id">> => Review2Id, <<"status">> => <<"hidden">>}
|
||||
],
|
||||
#{<<"updated_count">> := Count} = api_test_runner:admin_patch(<<"/v1/admin/reviews">>, Token, Body),
|
||||
?assertEqual(2, Count),
|
||||
ct:pal(" OK: updated ~p reviews", [Count]).
|
||||
|
||||
%% @doc GET /v1/admin/reviews/:id – получение отзыва по ID.
|
||||
test_get_review(Token, ReviewId) ->
|
||||
ct:pal(" TEST: Get review by ID"),
|
||||
Path = <<"/v1/admin/reviews/", ReviewId/binary>>,
|
||||
Review = api_test_runner:admin_get(Path, Token),
|
||||
?assertEqual(ReviewId, maps:get(<<"id">>, Review)),
|
||||
?assertEqual(<<"visible">>, maps:get(<<"status">>, Review)),
|
||||
ct:pal(" OK: ~s", [maps:get(<<"comment">>, Review)]).
|
||||
|
||||
%% @doc PUT /v1/admin/reviews/:id – обновление отзыва.
|
||||
test_update_review(Token, ReviewId) ->
|
||||
ct:pal(" TEST: Update review"),
|
||||
Path = <<"/v1/admin/reviews/", ReviewId/binary>>,
|
||||
Updates = #{<<"comment">> => <<"Updated by admin">>},
|
||||
Updated = api_test_runner:admin_put(Path, Token, Updates),
|
||||
?assertEqual(<<"Updated by admin">>, maps:get(<<"comment">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc GET /v1/admin/reviews?limit=...&offset=... – пагинация.
|
||||
test_review_pagination(Token) ->
|
||||
ct:pal(" TEST: Review pagination"),
|
||||
Page1 = api_test_runner:admin_get(<<"/v1/admin/reviews?limit=1&offset=0">>, Token),
|
||||
?assertEqual(1, length(Page1)),
|
||||
Page2 = api_test_runner:admin_get(<<"/v1/admin/reviews?limit=1&offset=1">>, Token),
|
||||
?assert(length(Page2) >= 1),
|
||||
Id1 = maps:get(<<"id">>, hd(Page1)),
|
||||
Id2 = maps:get(<<"id">>, hd(Page2)),
|
||||
?assertNotEqual(Id1, Id2),
|
||||
ct:pal(" OK").
|
||||
77
test/api/admins/admin_stats_tests.erl
Normal file
77
test/api/admins/admin_stats_tests.erl
Normal file
@@ -0,0 +1,77 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для получения статистики.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/stats
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение статистики для всех четырёх ролей администраторов
|
||||
%%% - для superadmin и admin – наличие ключевых метрик
|
||||
%%% - для moderator и support – ответ непустой
|
||||
%%% - работу с фильтром по датам (from, to)
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_stats_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Admin Stats Tests ==="),
|
||||
|
||||
SuperToken = api_test_runner:get_superadmin_token(),
|
||||
AdminToken = api_test_runner:get_admin_token(),
|
||||
ModerToken = api_test_runner:get_moderator_token(),
|
||||
SupportToken = api_test_runner:get_support_token(),
|
||||
|
||||
test_stats_for_role("Superadmin", SuperToken, strict),
|
||||
test_stats_for_role("Admin", AdminToken, strict),
|
||||
test_stats_for_role("Moderator", ModerToken, loose),
|
||||
test_stats_for_role("Support", SupportToken, loose),
|
||||
|
||||
test_stats_with_dates(SuperToken),
|
||||
|
||||
ct:pal("=== All admin stats tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Проверяет получение статистики для конкретной роли.
|
||||
%% strict – ожидаем ключи users_total/users и events_total/events
|
||||
%% loose – просто убеждаемся, что ответ непустой
|
||||
-spec test_stats_for_role(string(), binary(), strict | loose) -> ok.
|
||||
test_stats_for_role(RoleName, Token, Strictness) ->
|
||||
ct:pal(" TEST: Get stats for role ~s", [RoleName]),
|
||||
Stats = api_test_runner:admin_get(<<"/v1/admin/stats">>, Token),
|
||||
?assert(is_map(Stats)),
|
||||
case Strictness of
|
||||
strict ->
|
||||
HasUsers = maps:is_key(<<"users_total">>, Stats) orelse
|
||||
maps:is_key(<<"users">>, Stats),
|
||||
HasEvents = maps:is_key(<<"events_total">>, Stats) orelse
|
||||
maps:is_key(<<"events">>, Stats),
|
||||
?assert(HasUsers orelse HasEvents);
|
||||
loose ->
|
||||
?assert(map_size(Stats) > 0)
|
||||
end,
|
||||
ct:pal(" OK: ~p keys", [length(maps:keys(Stats))]).
|
||||
|
||||
%% @doc GET /v1/admin/stats?from=...&to=... – проверяет фильтрацию по датам.
|
||||
-spec test_stats_with_dates(binary()) -> ok.
|
||||
test_stats_with_dates(Token) ->
|
||||
ct:pal(" TEST: Get stats with date range"),
|
||||
From = <<"2026-01-01T00:00:00Z">>,
|
||||
To = <<"2026-12-31T23:59:59Z">>,
|
||||
Path = <<"/v1/admin/stats?from=", From/binary, "&to=", To/binary>>,
|
||||
Stats = api_test_runner:admin_get(Path, Token),
|
||||
?assert(is_map(Stats)),
|
||||
?assert(maps:is_key(<<"users_total">>, Stats) orelse
|
||||
maps:is_key(<<"users">>, Stats)),
|
||||
ct:pal(" OK").
|
||||
131
test/api/admins/admin_subscriptions_tests.erl
Normal file
131
test/api/admins/admin_subscriptions_tests.erl
Normal file
@@ -0,0 +1,131 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для управления подписками.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/subscriptions
|
||||
%%% GET /v1/admin/subscriptions/:id
|
||||
%%% PUT /v1/admin/subscriptions/:id
|
||||
%%% DELETE /v1/admin/subscriptions/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение списка подписок
|
||||
%%% - получение подписки по ID
|
||||
%%% - обновление подписки (изменение плана, статуса)
|
||||
%%% - удаление подписки
|
||||
%%% - фильтрацию по статусу и плану
|
||||
%%% - пагинацию
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_subscriptions_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Admin Subscriptions Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
% Создаём две подписки для разных проверок
|
||||
#{<<"id">> := Sub1Id} = api_test_runner:client_post(<<"/v1/subscription">>, UserToken,
|
||||
#{<<"action">> => <<"start_trial">>}),
|
||||
|
||||
% Для второй подписки создаём нового пользователя
|
||||
User2Email = api_test_runner:unique_email(<<"sub2">>),
|
||||
User2Token = api_test_runner:register_and_login(User2Email, <<"pass2">>),
|
||||
#{<<"id">> := Sub2Id} = api_test_runner:client_post(<<"/v1/subscription">>, User2Token,
|
||||
#{<<"action">> => <<"start_trial">>}),
|
||||
|
||||
% Теперь две подписки в системе
|
||||
test_list_subscriptions(Token, Sub1Id),
|
||||
test_get_subscription(Token, Sub1Id),
|
||||
test_update_subscription(Token, Sub1Id),
|
||||
% После обновления Sub1Id имеет план biannual и статус active
|
||||
test_filter_subscriptions(Token),
|
||||
test_subscription_pagination(Token),
|
||||
% Удаляем вторую подписку, чтобы проверить delete
|
||||
test_delete_subscription(Token, Sub2Id),
|
||||
|
||||
ct:pal("=== All admin subscriptions tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/admin/subscriptions – список подписок.
|
||||
-spec test_list_subscriptions(binary(), binary()) -> ok.
|
||||
test_list_subscriptions(Token, SubId) ->
|
||||
ct:pal(" TEST: List all subscriptions"),
|
||||
Subs = api_test_runner:admin_get(<<"/v1/admin/subscriptions">>, Token),
|
||||
?assert(is_list(Subs)),
|
||||
?assert(length(Subs) >= 2),
|
||||
?assert(lists:any(fun(S) -> maps:get(<<"id">>, S) =:= SubId end, Subs)),
|
||||
ct:pal(" OK: ~p subscriptions", [length(Subs)]).
|
||||
|
||||
%% @doc GET /v1/admin/subscriptions/:id – получение подписки по ID.
|
||||
-spec test_get_subscription(binary(), binary()) -> ok.
|
||||
test_get_subscription(Token, SubId) ->
|
||||
ct:pal(" TEST: Get subscription by ID"),
|
||||
Path = <<"/v1/admin/subscriptions/", SubId/binary>>,
|
||||
Sub = api_test_runner:admin_get(Path, Token),
|
||||
?assertEqual(SubId, maps:get(<<"id">>, Sub)),
|
||||
?assert(maps:is_key(<<"plan">>, Sub)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc PUT /v1/admin/subscriptions/:id – обновление подписки (план + статус).
|
||||
-spec test_update_subscription(binary(), binary()) -> ok.
|
||||
test_update_subscription(Token, SubId) ->
|
||||
ct:pal(" TEST: Update subscription (change plan)"),
|
||||
Path = <<"/v1/admin/subscriptions/", SubId/binary>>,
|
||||
Updated = api_test_runner:admin_put(Path, Token, #{
|
||||
<<"plan">> => <<"biannual">>,
|
||||
<<"status">> => <<"active">>
|
||||
}),
|
||||
?assertEqual(<<"biannual">>, maps:get(<<"plan">>, Updated)),
|
||||
?assertEqual(<<"active">>, maps:get(<<"status">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc DELETE /v1/admin/subscriptions/:id – удаление подписки.
|
||||
-spec test_delete_subscription(binary(), binary()) -> ok.
|
||||
test_delete_subscription(Token, SubId) ->
|
||||
ct:pal(" TEST: Delete subscription"),
|
||||
Path = <<"/v1/admin/subscriptions/", SubId/binary>>,
|
||||
Deleted = api_test_runner:admin_delete(Path, Token),
|
||||
?assertEqual(<<"deleted">>, maps:get(<<"status">>, Deleted)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc GET /v1/admin/subscriptions?status=...&plan=... – фильтрация.
|
||||
-spec test_filter_subscriptions(binary()) -> ok.
|
||||
test_filter_subscriptions(Token) ->
|
||||
ct:pal(" TEST: Filter subscriptions by status=active"),
|
||||
Subs = api_test_runner:admin_get(<<"/v1/admin/subscriptions?status=active">>, Token),
|
||||
?assert(is_list(Subs)),
|
||||
?assert(length(Subs) >= 1),
|
||||
[?assertEqual(<<"active">>, maps:get(<<"status">>, S)) || S <- Subs],
|
||||
ct:pal(" OK: ~p active subscriptions", [length(Subs)]),
|
||||
|
||||
ct:pal(" TEST: Filter subscriptions by plan=biannual"),
|
||||
Subs2 = api_test_runner:admin_get(<<"/v1/admin/subscriptions?plan=biannual">>, Token),
|
||||
?assert(is_list(Subs2)),
|
||||
?assert(length(Subs2) >= 1),
|
||||
[?assertEqual(<<"biannual">>, maps:get(<<"plan">>, S)) || S <- Subs2],
|
||||
ct:pal(" OK: ~p biannual subscriptions", [length(Subs2)]).
|
||||
|
||||
%% @doc GET /v1/admin/subscriptions?limit=...&offset=... – пагинация.
|
||||
-spec test_subscription_pagination(binary()) -> ok.
|
||||
test_subscription_pagination(Token) ->
|
||||
ct:pal(" TEST: Subscription pagination"),
|
||||
Page1 = api_test_runner:admin_get(<<"/v1/admin/subscriptions?limit=1&offset=0">>, Token),
|
||||
?assert(length(Page1) >= 1),
|
||||
Page2 = api_test_runner:admin_get(<<"/v1/admin/subscriptions?limit=1&offset=1">>, Token),
|
||||
?assert(length(Page2) >= 1),
|
||||
Id1 = maps:get(<<"id">>, hd(Page1)),
|
||||
Id2 = maps:get(<<"id">>, hd(Page2)),
|
||||
?assertNotEqual(Id1, Id2),
|
||||
ct:pal(" OK").
|
||||
153
test/api/admins/admin_tickets_tests.erl
Normal file
153
test/api/admins/admin_tickets_tests.erl
Normal file
@@ -0,0 +1,153 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для управления тикетами.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/admin/tickets
|
||||
%%% GET /v1/admin/tickets/:id
|
||||
%%% PUT /v1/admin/tickets/:id
|
||||
%%% DELETE /v1/admin/tickets/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение списка тикетов
|
||||
%%% - получение тикета по ID
|
||||
%%% - разрешение (resolve) и закрытие (close) тикета
|
||||
%%% - назначение исполнителя
|
||||
%%% - удаление тикета
|
||||
%%% - фильтрацию по статусу
|
||||
%%% - пагинацию
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_tickets_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Admin Tickets Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
% Создаём два тикета для разных проверок
|
||||
Ticket1 = api_test_runner:client_post(<<"/v1/tickets">>, UserToken,
|
||||
#{<<"error_message">> => <<"Test bug">>, <<"stacktrace">> => <<"trace">>}),
|
||||
#{<<"id">> := Ticket1Id} = Ticket1,
|
||||
|
||||
Ticket2 = api_test_runner:client_post(<<"/v1/tickets">>, UserToken,
|
||||
#{<<"error_message">> => <<"Another bug">>, <<"stacktrace">> => <<"trace2">>}),
|
||||
#{<<"id">> := Ticket2Id} = Ticket2,
|
||||
|
||||
test_list_tickets(Token, Ticket1Id),
|
||||
test_get_ticket(Token, Ticket1Id),
|
||||
test_resolve_ticket(Token, Ticket1Id),
|
||||
test_close_ticket(Token, Ticket1Id),
|
||||
test_assign_ticket(Token, Ticket1Id),
|
||||
test_filter_tickets(Token),
|
||||
test_ticket_pagination(Token, Ticket2Id),
|
||||
test_delete_ticket(Token, Ticket1Id),
|
||||
test_delete_ticket(Token, Ticket2Id),
|
||||
|
||||
ct:pal("=== All admin tickets tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/admin/tickets – проверяет получение списка тикетов.
|
||||
%% Убеждается, что список не пуст и содержит созданный тикет.
|
||||
-spec test_list_tickets(binary(), binary()) -> ok.
|
||||
test_list_tickets(Token, TicketId) ->
|
||||
ct:pal(" TEST: List all tickets"),
|
||||
Tickets = api_test_runner:admin_get(<<"/v1/admin/tickets">>, Token),
|
||||
?assert(is_list(Tickets)),
|
||||
?assert(length(Tickets) >= 1),
|
||||
?assert(lists:any(fun(T) -> maps:get(<<"id">>, T) =:= TicketId end, Tickets)),
|
||||
ct:pal(" OK: ~p tickets", [length(Tickets)]).
|
||||
|
||||
%% @doc GET /v1/admin/tickets/:id – проверяет получение тикета по ID.
|
||||
%% Убеждается, что статус нового тикета – open.
|
||||
-spec test_get_ticket(binary(), binary()) -> ok.
|
||||
test_get_ticket(Token, TicketId) ->
|
||||
ct:pal(" TEST: Get ticket by ID"),
|
||||
Path = <<"/v1/admin/tickets/", TicketId/binary>>,
|
||||
Ticket = api_test_runner:admin_get(Path, Token),
|
||||
?assertEqual(TicketId, maps:get(<<"id">>, Ticket)),
|
||||
?assertEqual(<<"open">>, maps:get(<<"status">>, Ticket)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc PUT /v1/admin/tickets/:id – разрешение тикета (resolve).
|
||||
%% Ожидается статус resolved после успешного выполнения.
|
||||
-spec test_resolve_ticket(binary(), binary()) -> ok.
|
||||
test_resolve_ticket(Token, TicketId) ->
|
||||
ct:pal(" TEST: Resolve ticket"),
|
||||
Path = <<"/v1/admin/tickets/", TicketId/binary>>,
|
||||
Updated = api_test_runner:admin_put(Path, Token, #{
|
||||
<<"status">> => <<"resolved">>,
|
||||
<<"resolution_note">> => <<"Fixed">>
|
||||
}),
|
||||
?assertEqual(<<"resolved">>, maps:get(<<"status">>, Updated)),
|
||||
?assertEqual(<<"Fixed">>, maps:get(<<"resolution_note">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc PUT /v1/admin/tickets/:id – закрытие тикета (close).
|
||||
-spec test_close_ticket(binary(), binary()) -> ok.
|
||||
test_close_ticket(Token, TicketId) ->
|
||||
ct:pal(" TEST: Close ticket"),
|
||||
Path = <<"/v1/admin/tickets/", TicketId/binary>>,
|
||||
Updated = api_test_runner:admin_put(Path, Token, #{<<"status">> => <<"closed">>}),
|
||||
?assertEqual(<<"closed">>, maps:get(<<"status">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc PUT /v1/admin/tickets/:id – назначение исполнителя.
|
||||
-spec test_assign_ticket(binary(), binary()) -> ok.
|
||||
test_assign_ticket(Token, TicketId) ->
|
||||
ct:pal(" TEST: Assign ticket"),
|
||||
Me = api_test_runner:admin_get(<<"/v1/admin/me">>, Token),
|
||||
AdminId = maps:get(<<"id">>, Me),
|
||||
Path = <<"/v1/admin/tickets/", TicketId/binary>>,
|
||||
Updated = api_test_runner:admin_put(Path, Token, #{<<"assigned_to">> => AdminId}),
|
||||
?assertEqual(AdminId, maps:get(<<"assigned_to">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc DELETE /v1/admin/tickets/:id – удаление тикета.
|
||||
-spec test_delete_ticket(binary(), binary()) -> ok.
|
||||
test_delete_ticket(Token, TicketId) ->
|
||||
ct:pal(" TEST: Delete ticket"),
|
||||
Path = <<"/v1/admin/tickets/", TicketId/binary>>,
|
||||
Deleted = api_test_runner:admin_delete(Path, Token),
|
||||
?assertEqual(<<"deleted">>, maps:get(<<"status">>, Deleted)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc GET /v1/admin/tickets?status=... – проверяет фильтрацию по статусу open.
|
||||
%% Использует второй тикет, который всё ещё open.
|
||||
-spec test_filter_tickets(binary()) -> ok.
|
||||
test_filter_tickets(Token) ->
|
||||
ct:pal(" TEST: Filter tickets by status=open"),
|
||||
Tickets = api_test_runner:admin_get(<<"/v1/admin/tickets?status=open">>, Token),
|
||||
?assert(is_list(Tickets)),
|
||||
?assert(length(Tickets) >= 1),
|
||||
[?assertEqual(<<"open">>, maps:get(<<"status">>, T)) || T <- Tickets],
|
||||
ct:pal(" OK: ~p open tickets", [length(Tickets)]).
|
||||
|
||||
%% @doc GET /v1/admin/tickets?limit=...&offset=... – проверяет пагинацию.
|
||||
%% Использует второй тикет как гарантированно существующий.
|
||||
-spec test_ticket_pagination(binary(), binary()) -> ok.
|
||||
test_ticket_pagination(Token, _TicketId) ->
|
||||
ct:pal(" TEST: Ticket pagination"),
|
||||
Page1 = api_test_runner:admin_get(<<"/v1/admin/tickets?limit=1&offset=0">>, Token),
|
||||
?assert(length(Page1) >= 1),
|
||||
Page2 = api_test_runner:admin_get(<<"/v1/admin/tickets?limit=1&offset=1">>, Token),
|
||||
?assert(length(Page2) >= 0),
|
||||
case {Page1, Page2} of
|
||||
{[First|_], [Second|_]} ->
|
||||
Id1 = maps:get(<<"id">>, First),
|
||||
Id2 = maps:get(<<"id">>, Second),
|
||||
?assertNotEqual(Id1, Id2);
|
||||
_ -> ok
|
||||
end,
|
||||
ct:pal(" OK").
|
||||
67
test/api/admins/admin_users_tests.erl
Normal file
67
test/api/admins/admin_users_tests.erl
Normal file
@@ -0,0 +1,67 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты административного API для управления пользователями.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_users_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
test() ->
|
||||
ct:pal("=== Admin Users Tests ==="),
|
||||
Token = api_test_runner:get_admin_token(),
|
||||
|
||||
%% Создаём тестового пользователя
|
||||
Email = api_test_runner:unique_email(<<"usertest">>),
|
||||
UserToken = api_test_runner:register_and_login(Email, <<"testpass">>),
|
||||
Me = api_test_runner:client_get(<<"/v1/user/me">>, UserToken),
|
||||
UserId = maps:get(<<"id">>, Me),
|
||||
|
||||
test_list_users(Token),
|
||||
test_get_user(Token, UserId),
|
||||
test_update_user(Token, UserId),
|
||||
test_filter_users(Token),
|
||||
test_delete_user(Token, UserId),
|
||||
|
||||
ct:pal("=== All admin users tests passed ==="),
|
||||
ok.
|
||||
|
||||
test_list_users(Token) ->
|
||||
ct:pal(" TEST: List all users"),
|
||||
Users = api_test_runner:admin_get(<<"/v1/admin/users">>, Token),
|
||||
?assert(is_list(Users)),
|
||||
?assert(length(Users) >= 1),
|
||||
ct:pal(" OK: ~p users", [length(Users)]).
|
||||
|
||||
test_get_user(Token, UserId) ->
|
||||
ct:pal(" TEST: Get user by ID"),
|
||||
Path = <<"/v1/admin/users/", UserId/binary>>,
|
||||
User = api_test_runner:admin_get(Path, Token),
|
||||
?assertEqual(UserId, maps:get(<<"id">>, User)),
|
||||
ct:pal(" OK: ~s", [maps:get(<<"email">>, User)]).
|
||||
|
||||
test_update_user(Token, UserId) ->
|
||||
ct:pal(" TEST: Update user (change role and status)"),
|
||||
Path = <<"/v1/admin/users/", UserId/binary>>,
|
||||
Updated = api_test_runner:admin_put(Path, Token, #{
|
||||
<<"role">> => <<"bot">>,
|
||||
<<"status">> => <<"frozen">>,
|
||||
<<"reason">> => <<"Test freeze">>
|
||||
}),
|
||||
?assertEqual(<<"bot">>, maps:get(<<"role">>, Updated)),
|
||||
?assertEqual(<<"frozen">>, maps:get(<<"status">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_filter_users(Token) ->
|
||||
ct:pal(" TEST: Filter users by status=frozen"),
|
||||
Users = api_test_runner:admin_get(<<"/v1/admin/users?status=frozen">>, Token),
|
||||
?assert(is_list(Users)),
|
||||
[?assertEqual(<<"frozen">>, maps:get(<<"status">>, U)) || U <- Users],
|
||||
ct:pal(" OK: ~p frozen users", [length(Users)]).
|
||||
|
||||
test_delete_user(Token, UserId) ->
|
||||
ct:pal(" TEST: Delete (soft-delete) user"),
|
||||
Path = <<"/v1/admin/users/", UserId/binary>>,
|
||||
Deleted = api_test_runner:admin_delete(Path, Token),
|
||||
?assertEqual(<<"deleted">>, maps:get(<<"status">>, Deleted)),
|
||||
ct:pal(" OK").
|
||||
258
test/api/admins/admin_websocket_tests.erl
Normal file
258
test/api/admins/admin_websocket_tests.erl
Normal file
@@ -0,0 +1,258 @@
|
||||
-module(admin_websocket_tests).
|
||||
-export([test/0]).
|
||||
|
||||
test() ->
|
||||
ct:pal("Testing WebSocket API..."),
|
||||
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)]),
|
||||
|
||||
% Создаём календарь и событие через новый api_test_runner
|
||||
#{<<"id">> := CalId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars">>, UserToken,
|
||||
#{title => <<"WS Test Calendar">>, type => <<"commercial">>}),
|
||||
ct:pal(" CalId: ~s", [CalId]),
|
||||
|
||||
#{<<"id">> := EventId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, UserToken,
|
||||
#{title => <<"WS Test Event">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60}),
|
||||
ct:pal(" EventId: ~s", [EventId]),
|
||||
|
||||
WsUrl = api_test_runner:get_base_ws_url() ++ "/ws",
|
||||
AdminWsUrl = api_test_runner:get_admin_ws_url() ++ "/admin/ws",
|
||||
|
||||
%% TEST 1: Connect to WebSocket with valid token
|
||||
ct:pal(" TEST 1: Connect WebSocket with valid token..."),
|
||||
ct:pal(" URL: ~s", [WsUrl]),
|
||||
ct:pal(" Token: ~s...", [binary_part(UserToken, 0, 30)]),
|
||||
case test_ws_connect_debug(WsUrl, 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(AdminWsUrl, 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..."),
|
||||
api_test_runner:client_post(<<"/v1/reports">>, UserToken,
|
||||
#{target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
reason => <<"Test report">>}),
|
||||
{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(AdminWsUrl, 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(AdminWsUrl, 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,
|
||||
{ok, Port} = extract_port(Url),
|
||||
{ok, Host} = extract_host(Url),
|
||||
Opts = case Port of
|
||||
443 -> #{protocols => [http],
|
||||
transport => tls,
|
||||
tls_opts => [{verify, verify_none}]};
|
||||
_ -> #{protocols => [http]}
|
||||
end,
|
||||
ct:pal(" Host: ~s", [Host]),
|
||||
ct:pal(" Port: ~p", [Port]),
|
||||
ct:pal(" Path: ~s", [Path]),
|
||||
{ok, ConnPid} = gun:open(Host, Port, Opts),
|
||||
{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).
|
||||
|
||||
%% ========== URL parsing helpers ==========
|
||||
extract_port(Url) ->
|
||||
case string:split(Url, "://", trailing) of
|
||||
[_, Rest] ->
|
||||
HostPort = case string:split(Rest, "/", leading) of
|
||||
[H, _] -> H;
|
||||
[H] -> H
|
||||
end,
|
||||
case string:split(HostPort, ":", trailing) of
|
||||
[_, PortStr] -> {ok, list_to_integer(PortStr)};
|
||||
_ -> case string:split(Rest, "://", trailing) of
|
||||
[_, R] -> extract_port("https://" ++ R);
|
||||
_ -> {ok, 80}
|
||||
end
|
||||
end;
|
||||
_ -> {ok, 80}
|
||||
end.
|
||||
|
||||
extract_host(Url) ->
|
||||
case string:split(Url, "://", trailing) of
|
||||
[_, Rest] ->
|
||||
HostPort = case string:split(Rest, "/", leading) of
|
||||
[H, _] -> H;
|
||||
[H] -> H
|
||||
end,
|
||||
case string:split(HostPort, ":", trailing) of
|
||||
[Host, _] -> {ok, Host};
|
||||
[Host] -> {ok, Host}
|
||||
end;
|
||||
_ -> {ok, "localhost"}
|
||||
end.
|
||||
@@ -1,510 +0,0 @@
|
||||
-module(api_admin_tests).
|
||||
-export([test/0]).
|
||||
|
||||
%% Учётные данные по умолчанию
|
||||
-define(FALLBACK_ADMIN_SUPER_EMAIL, <<"superadmin@eventhub.local">>).
|
||||
-define(FALLBACK_ADMIN_SUPER_PASSWORD, <<"123456">>).
|
||||
-define(FALLBACK_ADMIN_MODER_EMAIL, <<"moderator@eventhub.local">>).
|
||||
-define(FALLBACK_ADMIN_MODER_PASSWORD, <<"123456">>).
|
||||
-define(FALLBACK_ADMIN_SUPPORT_EMAIL, <<"support@eventhub.local">>).
|
||||
-define(FALLBACK_ADMIN_SUPPORT_PASSWORD,<<"123456">>).
|
||||
|
||||
test() ->
|
||||
ct:pal("Testing admin panel API...~n"),
|
||||
AdminURL = api_test_runner:get_admin_url(),
|
||||
UserURL = api_test_runner:get_base_url(),
|
||||
|
||||
% Получаем токен суперадмина
|
||||
AdminToken = api_test_runner:get_admin_token(),
|
||||
|
||||
%% TEST 1: Admin healthcheck (public)
|
||||
ct:pal(" TEST 1: Admin healthcheck... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/health", []}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 2: Admin login (дополнительная проверка)
|
||||
ct:pal(" TEST 2: Admin login (attempt)... "),
|
||||
LoginBody = jsx:encode(#{
|
||||
<<"email">> => ?FALLBACK_ADMIN_SUPER_EMAIL,
|
||||
<<"password">> => ?FALLBACK_ADMIN_SUPER_PASSWORD
|
||||
}),
|
||||
case httpc:request(post, {AdminURL ++ "/v1/admin/login", [], "application/json", LoginBody}, [], []) of
|
||||
{ok, {{_, 200, _}, _, _}} -> ct:pal("OK (logged in)~n");
|
||||
_ -> ct:pal("SKIPPED (credentials not found, using runner token)~n")
|
||||
end,
|
||||
|
||||
%% TEST 3: Admin stats (superadmin)
|
||||
ct:pal(" TEST 3: Admin stats for role... "),
|
||||
SuperadminToken = api_test_runner:login_custom_admin(?FALLBACK_ADMIN_SUPER_EMAIL, ?FALLBACK_ADMIN_SUPER_PASSWORD),
|
||||
ModeratorToken = api_test_runner:login_custom_admin(?FALLBACK_ADMIN_MODER_EMAIL, ?FALLBACK_ADMIN_MODER_PASSWORD),
|
||||
SupportToken = api_test_runner:login_custom_admin(?FALLBACK_ADMIN_SUPPORT_EMAIL, ?FALLBACK_ADMIN_SUPPORT_PASSWORD),
|
||||
|
||||
ct:pal(" Admin stats (superadmin)... "),
|
||||
{ok, {{_, 200, _}, _, StatsResp1}} = httpc:request(get, {AdminURL ++ "/v1/admin/stats", [{"Authorization", "Bearer " ++ binary_to_list(SuperadminToken)}]}, [], []),
|
||||
Stats1 = jsx:decode(list_to_binary(StatsResp1), [return_maps]),
|
||||
ct:pal(" OK (Stats 1: ~p)~n", [Stats1]),
|
||||
true = map_size(Stats1) > 0,
|
||||
|
||||
ct:pal(" Admin stats (admin)... "),
|
||||
{ok, {{_, 200, _}, _, StatsResp2}} = httpc:request(get, {AdminURL ++ "/v1/admin/stats", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
Stats2 = jsx:decode(list_to_binary(StatsResp2), [return_maps]),
|
||||
ct:pal(" OK (Stats 1: ~p)~n", [Stats2]),
|
||||
true = map_size(Stats2) > 0,
|
||||
|
||||
ct:pal(" Admin stats (moderator)... "),
|
||||
{ok, {{_, 200, _}, _, StatsResp3}} = httpc:request(get, {AdminURL ++ "/v1/admin/stats", [{"Authorization", "Bearer " ++ binary_to_list(ModeratorToken)}]}, [], []),
|
||||
Stats3 = jsx:decode(list_to_binary(StatsResp3), [return_maps]),
|
||||
ct:pal(" OK (Stats 1: ~p)~n", [Stats3]),
|
||||
true = map_size(Stats3) > 0,
|
||||
|
||||
ct:pal(" Admin stats (support)... "),
|
||||
{ok, {{_, 200, _}, _, StatsResp4}} = httpc:request(get, {AdminURL ++ "/v1/admin/stats", [{"Authorization", "Bearer " ++ binary_to_list(SupportToken)}]}, [], []),
|
||||
Stats4 = jsx:decode(list_to_binary(StatsResp4), [return_maps]),
|
||||
ct:pal(" OK (Stats 1: ~p)~n", [Stats4]),
|
||||
true = map_size(Stats4) > 0,
|
||||
|
||||
%% TEST 4: List users
|
||||
ct:pal(" TEST 4: List users... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/users", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 5: Get user by ID
|
||||
ct:pal(" TEST 5: Get user by ID... "),
|
||||
UserId = api_test_runner:get_user_id(),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/users/" ++ binary_to_list(UserId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 6: List reports
|
||||
ct:pal(" TEST 6: List reports... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/reports", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% ── TEST 7: Full moderation flow (create event, report, resolve) ──
|
||||
ct:pal(" TEST 7: Moderation flow... "),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
CalId = api_test_runner:create_calendar(UserToken, #{title => <<"ModerationTest">>}),
|
||||
EventId = api_test_runner:create_event(UserToken, CalId, #{
|
||||
title => <<"Event to report">>,
|
||||
start_time => api_SUITE:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
% Подаём жалобу на это событие
|
||||
CreateBody = jsx:encode(#{
|
||||
<<"target_type">> => <<"event">>,
|
||||
<<"target_id">> => EventId,
|
||||
<<"reason">> => <<"Inappropriate content">>
|
||||
}),
|
||||
{ok, {{_, 201, _}, _, CreateResp}} = httpc:request(post, {UserURL ++ "/v1/reports", [{"Authorization", "Bearer " ++ binary_to_list(UserToken)}], "application/json", CreateBody}, [], []),
|
||||
#{<<"id">> := ReportId} = jsx:decode(list_to_binary(CreateResp), [return_maps]),
|
||||
% Администратор изменяет статус жалобы
|
||||
EditBody = jsx:encode(#{
|
||||
<<"status">> => <<"reviewed">>,
|
||||
<<"reason">> => <<"Issue resolved">>
|
||||
}),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(put, {AdminURL ++ "/v1/admin/reports/" ++ binary_to_list(ReportId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", EditBody}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 8: List banned words
|
||||
ct:pal(" TEST 8: List banned words... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/banned-words", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 9: Add banned word
|
||||
ct:pal(" TEST 9: Add banned word... "),
|
||||
BannedWordBody = jsx:encode(#{<<"word">> => <<"test_banned_word">>}),
|
||||
{ok, {{_, 201, _}, _, _}} = httpc:request(post, {AdminURL ++ "/v1/admin/banned-words", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", BannedWordBody}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 10: Delete banned word
|
||||
ct:pal(" TEST 10: Delete banned word... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(delete, {AdminURL ++ "/v1/admin/banned-words/test_banned_word", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 11: List tickets
|
||||
ct:pal(" TEST 11: List tickets... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/tickets", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 12: Create ticket
|
||||
ct:pal(" TEST 12: Create ticket... "),
|
||||
TicketBody = jsx:encode(#{
|
||||
<<"error_message">> => <<"Test error">>,
|
||||
<<"stacktrace">> => <<"trace">>
|
||||
}),
|
||||
{ok, {{_, 201, _}, _, TicketResp}} = httpc:request(post, {UserURL ++ "/v1/tickets", [{"Authorization", "Bearer " ++ binary_to_list(UserToken)}], "application/json", TicketBody}, [], []),
|
||||
#{<<"id">> := TicketId} = jsx:decode(list_to_binary(TicketResp), [return_maps]),
|
||||
ct:pal(" OK (TicketId: ~p)~n", [TicketId]),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 13: Get ticket by ID
|
||||
ct:pal(" TEST 13: Get ticket by ID... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/tickets/" ++ binary_to_list(TicketId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 14: Update ticket
|
||||
ct:pal(" TEST 14: Update ticket... "),
|
||||
UpdateTicketBody = jsx:encode(#{<<"status">> => <<"closed">>}),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(put, {AdminURL ++ "/v1/admin/tickets/" ++ binary_to_list(TicketId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", UpdateTicketBody}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 15: Delete ticket
|
||||
ct:pal(" TEST 15: Delete ticket... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(delete, {AdminURL ++ "/v1/admin/tickets/" ++ binary_to_list(TicketId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 16: Ticket stats
|
||||
ct:pal(" TEST 16: Ticket stats... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/tickets/stats", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 17: List subscriptions
|
||||
ct:pal(" TEST 17: List subscriptions... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/subscriptions", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 18: Create subscription
|
||||
ct:pal(" TEST 18: Create subscription... "),
|
||||
SubBody = jsx:encode(#{action => <<"activate">>, plan => <<"monthly">>, payment_info => #{card => <<"4242">>}}),
|
||||
{ok, {{_, 201, _}, _, SubResp}} = httpc:request(post, {UserURL ++ "/v1/subscription", [{"Authorization", "Bearer " ++ binary_to_list(UserToken)}], "application/json", SubBody}, [], []),
|
||||
#{<<"id">> := SubId} = jsx:decode(list_to_binary(SubResp), [return_maps]),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 19: Get subscription by ID
|
||||
ct:pal(" TEST 19: Get subscription by ID... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get, {AdminURL ++ "/v1/admin/subscriptions/" ++ binary_to_list(SubId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 20: Update subscription
|
||||
ct:pal(" TEST 20: Update subscription... "),
|
||||
UpdateSubBody = jsx:encode(#{<<"status">> => <<"cancelled">>}),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(put, {AdminURL ++ "/v1/admin/subscriptions/" ++ binary_to_list(SubId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", UpdateSubBody}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 21: Delete subscription
|
||||
ct:pal(" TEST 21: Delete subscription... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(delete, {AdminURL ++ "/v1/admin/subscriptions/" ++ binary_to_list(SubId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 22: Moderation - block user
|
||||
ct:pal(" TEST 22: Moderation - block user... "),
|
||||
ModBody = jsx:encode(#{
|
||||
<<"action">> => <<"block">>,
|
||||
<<"reason">> => <<"test">>
|
||||
}),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(put, {AdminURL ++ "/v1/admin/user/" ++ binary_to_list(UserId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", ModBody}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% TEST 23: Moderation - unblock user
|
||||
ct:pal(" TEST 23: Moderation - unblock user... "),
|
||||
UnblockBody = jsx:encode(#{
|
||||
<<"action">> => <<"unblock">>,
|
||||
<<"reason">> => <<"restore">>
|
||||
}),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(put, {AdminURL ++ "/v1/admin/user/" ++ binary_to_list(UserId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", UnblockBody}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% ========================================================
|
||||
%% Admin Reviews list tests
|
||||
%% ========================================================
|
||||
|
||||
%% Подготовка тестовых данных для отзывов
|
||||
ct:pal(" Preparing test data for reviews... "),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
% Создаем календарь и событие (отдельные переменные, чтобы не перекрыть TEST 7)
|
||||
RevCalId = api_test_runner:create_calendar(UserToken, #{title => <<"ReviewsTest">>}),
|
||||
RevEventId = api_test_runner:create_event(UserToken, RevCalId, #{
|
||||
title => <<"Event for review testing">>,
|
||||
start_time => api_SUITE:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
ct:pal("OK (calendar: ~s, event: ~s)~n", [RevCalId, RevEventId]),
|
||||
|
||||
ParticipantEmail = api_test_runner:unique_email(<<"rev_1">>),
|
||||
ParticipantEmail2 = api_test_runner:unique_email(<<"rev_2">>),
|
||||
ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"part123">>),
|
||||
ParticipantToken2 = api_test_runner:register_and_login(ParticipantEmail2, <<"part123">>),
|
||||
|
||||
% Создаём и подтверждаем бронирование
|
||||
BookingId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/events/" ++ binary_to_list(RevEventId) ++ "/bookings", #{}, ParticipantToken), <<"id">>),
|
||||
api_test_runner:http_put("/v1/bookings/" ++ binary_to_list(BookingId), #{action => <<"confirm">>}, UserToken),
|
||||
|
||||
Booking2Id = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/events/" ++ binary_to_list(RevEventId) ++ "/bookings", #{}, ParticipantToken2), <<"id">>),
|
||||
api_test_runner:http_put("/v1/bookings/" ++ binary_to_list(Booking2Id), #{action => <<"confirm">>}, UserToken),
|
||||
|
||||
ReviewId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/reviews",
|
||||
#{target_type => <<"event">>, target_id => RevEventId, rating => 5, comment => <<"Great!">>},
|
||||
ParticipantToken), <<"id">>),
|
||||
ct:pal(" Review2Id: ~p~n", [ReviewId]),
|
||||
Review2Id = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/reviews",
|
||||
#{target_type => <<"event">>, target_id => RevEventId, rating => 5, comment => <<"Great!">>},
|
||||
ParticipantToken2), <<"id">>),
|
||||
ct:pal(" Review2Id: ~p~n", [Review2Id]),
|
||||
|
||||
%% TEST 24: List all reviews (GET /v1/admin/reviews)
|
||||
ct:pal(" TEST 24: List all reviews... "),
|
||||
{ok, {{_, 200, _}, _, ListReviewsResp}} = httpc:request(get, {AdminURL ++ "/v1/admin/reviews", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
ReviewsList = jsx:decode(list_to_binary(ListReviewsResp), [return_maps]),
|
||||
true = is_list(ReviewsList),
|
||||
ct:pal(" OK (count: ~p)~n", [length(ReviewsList)]),
|
||||
|
||||
%% TEST 25: List reviews with filters (GET /v1/admin/reviews?target_type=event&target_id=...)
|
||||
ct:pal(" TEST 25: List reviews with filters... "),
|
||||
FilterURL = AdminURL ++ "/v1/admin/reviews?target_type=event&target_id=" ++ binary_to_list(RevEventId),
|
||||
{ok, {{_, 200, _}, _, FilteredResp}} = httpc:request(get, {FilterURL, [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
FilteredList = jsx:decode(list_to_binary(FilteredResp), [return_maps]),
|
||||
ct:pal(" OK (filtered count: ~p)~n", [length(FilteredList)]),
|
||||
|
||||
%% TEST 26: Bulk update review statuses (PATCH /v1/admin/reviews)
|
||||
ct:pal(" TEST 26: Bulk update review statuses... "),
|
||||
case ReviewsList of
|
||||
[FirstReview, SecondReview | _] ->
|
||||
FirstId = maps:get(<<"id">>, FirstReview),
|
||||
SecondId = maps:get(<<"id">>, SecondReview),
|
||||
PatchBody = jsx:encode([
|
||||
#{<<"id">> => FirstId, <<"status">> => <<"visible">>},
|
||||
#{<<"id">> => SecondId, <<"status">> => <<"hidden">>}
|
||||
]),
|
||||
ct:pal(" OK (PatchBody: ~p)~n", [PatchBody]),
|
||||
{ok, {{_, 200, _}, _, PatchResp}} = httpc:request(patch, {AdminURL ++ "/v1/admin/reviews", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", PatchBody}, [], []),
|
||||
#{<<"updated_count">> := UpdatedCount} = jsx:decode(list_to_binary(PatchResp), [return_maps]),
|
||||
true = (UpdatedCount =:= 2),
|
||||
ct:pal(" OK (updated: ~p)~n", [UpdatedCount]);
|
||||
_ ->
|
||||
ct:pal("SKIPPED (not enough reviews for bulk update)~n")
|
||||
end,
|
||||
|
||||
%% TEST 27: Method not allowed (POST /v1/admin/reviews → 405)
|
||||
ct:pal(" TEST 27: POST method not allowed... "),
|
||||
{ok, {{_, 405, _}, _, _}} = httpc:request(post, {AdminURL ++ "/v1/admin/reviews", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", <<"{}">>}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% ========================================================
|
||||
%% Admin Events tests
|
||||
%% ========================================================
|
||||
|
||||
FutureDate = api_SUITE:future_date(),
|
||||
FutureDateStr = binary_to_list(FutureDate),
|
||||
|
||||
%% TEST 28: List all events (GET /v1/admin/events)
|
||||
ct:pal(" TEST 28: List all events... "),
|
||||
{ok, {{_, 200, _}, _, EventsListResp}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
EventsList = jsx:decode(list_to_binary(EventsListResp), [return_maps]),
|
||||
true = is_list(EventsList),
|
||||
ct:pal(" OK (count: ~p)~n", [length(EventsList)]),
|
||||
|
||||
%% TEST 29: List events with date filters
|
||||
ct:pal(" TEST 29: List events with date filters... "),
|
||||
FilterEventsURL = AdminURL ++ "/v1/admin/events?from=" ++ FutureDateStr ++
|
||||
"&to=" ++ FutureDateStr,
|
||||
{ok, {{_, 200, _}, _, FilteredEventsResp}} =
|
||||
httpc:request(get, {FilterEventsURL,
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
FilteredEventsList = jsx:decode(list_to_binary(FilteredEventsResp), [return_maps]),
|
||||
true = is_list(FilteredEventsList),
|
||||
ct:pal(" OK (filtered count: ~p)~n", [length(FilteredEventsList)]),
|
||||
|
||||
%% TEST 30: Get event by ID (GET /v1/admin/events/:id)
|
||||
ct:pal(" TEST 30: Get event by ID... "),
|
||||
{ok, {{_, 200, _}, _, EventByIdResp}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events/" ++ binary_to_list(EventId),
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
#{<<"id">> := EventId} = jsx:decode(list_to_binary(EventByIdResp), [return_maps]),
|
||||
ct:pal(" OK (id: ~s)~n", [binary_to_list(EventId)]),
|
||||
|
||||
%% TEST 31: Update event by ID (PUT /v1/admin/events/:id)
|
||||
ct:pal(" TEST 31: Update event by ID... "),
|
||||
UpdateEventBody = jsx:encode(#{
|
||||
<<"title">> => <<"Updated by admin">>,
|
||||
<<"description">> => <<"Admin test update">>
|
||||
}),
|
||||
{ok, {{_, 200, _}, _, UpdateEventResp}} =
|
||||
httpc:request(put, {AdminURL ++ "/v1/admin/events/" ++ binary_to_list(EventId),
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}],
|
||||
"application/json", UpdateEventBody},
|
||||
[], []),
|
||||
#{<<"title">> := <<"Updated by admin">>} =
|
||||
jsx:decode(list_to_binary(UpdateEventResp), [return_maps]),
|
||||
ct:pal(" OK~n"),
|
||||
|
||||
%% TEST 32: Delete event by ID (DELETE /v1/admin/events/:id)
|
||||
ct:pal(" TEST 32: Delete event by ID... "),
|
||||
{ok, {{_, 200, _}, _, DeleteResp}} =
|
||||
httpc:request(delete, {AdminURL ++ "/v1/admin/events/" ++ binary_to_list(EventId),
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(list_to_binary(DeleteResp), [return_maps]),
|
||||
ct:pal(" OK (status deleted)~n"),
|
||||
|
||||
%% TEST 33: Method not allowed (POST /v1/admin/events → 405)
|
||||
ct:pal(" TEST 33: POST method not allowed... "),
|
||||
{ok, {{_, 405, _}, _, _}} =
|
||||
httpc:request(post, {AdminURL ++ "/v1/admin/events",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}],
|
||||
"application/json", <<"{}">>},
|
||||
[], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% ========================================================
|
||||
%% Extended Admin Events Search & Filter Tests
|
||||
%% ========================================================
|
||||
|
||||
%% ── Подготовка изолированных данных ──
|
||||
ct:pal(" Preparing isolated search test data... "),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
SearchCalId = api_test_runner:create_calendar(UserToken, #{title => <<"SearchTestCal">>}),
|
||||
SearchCalIdStr = binary_to_list(SearchCalId),
|
||||
AlphaId = api_test_runner:create_event(UserToken, SearchCalId, #{
|
||||
title => <<"Test Event Alpha">>,
|
||||
start_time => api_SUITE:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
BetaId = api_test_runner:create_event(UserToken, SearchCalId, #{
|
||||
title => <<"Beta Event">>,
|
||||
start_time => api_SUITE:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
_AlphaConfId = api_test_runner:create_event(UserToken, SearchCalId, #{
|
||||
title => <<"Alpha Conference">>,
|
||||
start_time => api_SUITE:future_date(),
|
||||
duration => 60
|
||||
}),
|
||||
% Отменяем BetaId через административный эндпоинт (PUT /v1/admin/events/:id)
|
||||
ct:pal(" Cancelling Beta Event (admin)... "),
|
||||
{ok, {{_, 200, _}, _, _}} =
|
||||
httpc:request(put, {AdminURL ++ "/v1/admin/events/" ++ binary_to_list(BetaId),
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}],
|
||||
"application/json", jsx:encode(#{<<"status">> => <<"cancelled">>})}, [], []),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% ── TEST 34: Filter by status=active ──
|
||||
ct:pal(" TEST 34: Filter events by status=active... "),
|
||||
{ok, {{_, 200, _}, _, Body34}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr ++ "&status=active",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events34 = jsx:decode(list_to_binary(Body34), [return_maps]),
|
||||
ct:pal("DEBUG: events34 count = ~p", [length(Events34)]),
|
||||
ct:pal("DEBUG: events34 = ~p", [Events34]),
|
||||
true = (length(Events34) >= 2),
|
||||
Ids34 = [maps:get(<<"id">>, E) || E <- Events34],
|
||||
ct:pal("OK (count: ~p, ids: ~p)~n", [length(Events34), Ids34]),
|
||||
|
||||
%% ── TEST 35: Filter by status=cancelled ──
|
||||
ct:pal(" TEST 35: Filter events by status=cancelled... "),
|
||||
{ok, {{_, 200, _}, _, Body35}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr ++ "&status=cancelled",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events35 = jsx:decode(list_to_binary(Body35), [return_maps]),
|
||||
ct:pal("DEBUG: Events35 count = ~p", [length(Events35)]),
|
||||
ct:pal("DEBUG: Events35 = ~p", [Events35]),
|
||||
true = (length(Events35) >= 1),
|
||||
ct:pal("OK (count: ~p)~n", [length(Events35)]),
|
||||
|
||||
%% ── TEST 36: Filter by status=all ──
|
||||
ct:pal(" TEST 36: Filter events by status=all... "),
|
||||
{ok, {{_, 200, _}, _, Body36}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr ++ "&status=all",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events36 = jsx:decode(list_to_binary(Body36), [return_maps]),
|
||||
ct:pal("DEBUG: Events36 count = ~p", [length(Events36)]),
|
||||
ct:pal("DEBUG: Events36 = ~p", [Events36]),
|
||||
true = (length(Events36) >= 3),
|
||||
ct:pal("OK (count: ~p)~n", [length(Events36)]),
|
||||
|
||||
%% ── TEST 37: Filter by calendar_id ──
|
||||
ct:pal(" TEST 37: Filter by calendar_id... "),
|
||||
{ok, {{_, 200, _}, _, Body37}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr,
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events37 = jsx:decode(list_to_binary(Body37), [return_maps]),
|
||||
ct:pal("DEBUG: Events37 count = ~p", [length(Events37)]),
|
||||
ct:pal("DEBUG: Events37 = ~p", [Events37]),
|
||||
true = (length(Events37) >= 3),
|
||||
ct:pal("OK (count: ~p)~n", [length(Events37)]),
|
||||
|
||||
%% ── TEST 38: Exact title match ──
|
||||
ct:pal(" TEST 38: Exact title match... "),
|
||||
{ok, {{_, 200, _}, _, Body38}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr ++ "&title=Test%20Event%20Alpha",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events38 = jsx:decode(list_to_binary(Body38), [return_maps]),
|
||||
ct:pal("DEBUG: Events38 count = ~p", [length(Events38)]),
|
||||
ct:pal("DEBUG: Events38 = ~p", [Events38]),
|
||||
1 = length(Events38),
|
||||
#{<<"id">> := AlphaId} = hd(Events38),
|
||||
ct:pal("OK~n"),
|
||||
|
||||
%% ── TEST 39: Substring search (q) ──
|
||||
ct:pal(" TEST 39: Substring search (q=Alpha)... "),
|
||||
{ok, {{_, 200, _}, _, Body39}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr ++ "&q=Alpha",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events39 = jsx:decode(list_to_binary(Body39), [return_maps]),
|
||||
ct:pal("DEBUG: Events39 count = ~p", [length(Events39)]),
|
||||
ct:pal("DEBUG: Events39 = ~p", [Events39]),
|
||||
true = (length(Events39) >= 2),
|
||||
Titles39 = [maps:get(<<"title">>, E) || E <- Events39],
|
||||
ct:pal("OK (count: ~p, titles: ~p)~n", [length(Events39), Titles39]),
|
||||
|
||||
%% ── TEST 40: Combined filters (calendar_id + status) ──
|
||||
ct:pal(" TEST 40: Combined filters (calendar+status)... "),
|
||||
{ok, {{_, 200, _}, _, Body40}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr ++ "&status=active",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events40 = jsx:decode(list_to_binary(Body40), [return_maps]),
|
||||
ct:pal("DEBUG: Events40 count = ~p", [length(Events40)]),
|
||||
ct:pal("DEBUG: Events40 = ~p", [Events40]),
|
||||
true = (length(Events40) >= 2),
|
||||
ct:pal("OK (count: ~p)~n", [length(Events40)]),
|
||||
|
||||
%% ── TEST 41: Pagination (limit & offset) ──
|
||||
ct:pal(" TEST 41: Pagination... "),
|
||||
{ok, {{_, 200, _}, Headers41a, Body41a}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr ++ "&status=all&limit=2&offset=0&sort=title&order=asc",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events41a = jsx:decode(list_to_binary(Body41a), [return_maps]),
|
||||
ct:pal("DEBUG: Events41a count = ~p", [length(Events41a)]),
|
||||
ct:pal("DEBUG: Events41a = ~p", [Events41a]),
|
||||
2 = length(Events41a),
|
||||
{"content-range", ContentRange41a} = lists:keyfind("content-range", 1, Headers41a),
|
||||
ct:pal("page1: ~s; ", [ContentRange41a]),
|
||||
{ok, {{_, 200, _}, Headers41b, Body41b}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr ++ "&status=all&limit=2&offset=2&sort=title&order=asc",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events41b = jsx:decode(list_to_binary(Body41b), [return_maps]),
|
||||
ct:pal("DEBUG: Events41b count = ~p", [length(Events41b)]),
|
||||
ct:pal("DEBUG: Events41b = ~p", [Events41b]),
|
||||
1 = length(Events41b),
|
||||
{"content-range", ContentRange41b} = lists:keyfind("content-range", 1, Headers41b),
|
||||
ct:pal("page2: ~s~n", [ContentRange41b]),
|
||||
|
||||
%% ── TEST 42: Sorting (order=asc) ──
|
||||
ct:pal(" TEST 42: Sorting by title ascending... "),
|
||||
{ok, {{_, 200, _}, _, Body42}} =
|
||||
httpc:request(get, {AdminURL ++ "/v1/admin/events?calendar_id=" ++ SearchCalIdStr ++ "&status=all&sort=title&order=asc",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||
[], []),
|
||||
Events42 = jsx:decode(list_to_binary(Body42), [return_maps]),
|
||||
SortedTitles = [maps:get(<<"title">>, E) || E <- Events42],
|
||||
SortedTitles = lists:sort(SortedTitles),
|
||||
ct:pal("OK (titles: ~p)~n", [SortedTitles]),
|
||||
|
||||
ct:pal("~n✅ Admin API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
@@ -1,64 +0,0 @@
|
||||
-module(api_auth_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
|
||||
test() ->
|
||||
io:format("Testing authentication API...~n"),
|
||||
|
||||
Email = api_test_runner:unique_email(<<"auth_test">>),
|
||||
Password = <<"test123">>,
|
||||
|
||||
% TEST 1: Register
|
||||
io:format(" TEST 1: Register... "),
|
||||
RegBody = #{email => Email, password => Password},
|
||||
Token = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/register", RegBody), <<"token">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 2: Register with existing email
|
||||
io:format(" TEST 2: Register duplicate... "),
|
||||
{ok, {{_, 409, _}, _, _}} = api_test_runner:http_post("/v1/register", RegBody),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 3: Login with correct credentials
|
||||
io:format(" TEST 3: Login... "),
|
||||
LoginBody = #{email => Email, password => Password},
|
||||
RefreshToken = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/login", LoginBody), <<"refresh_token">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 4: Login with wrong password
|
||||
io:format(" TEST 4: Login wrong password... "),
|
||||
{ok, {{_, 401, _}, _, _}} = api_test_runner:http_post("/v1/login", #{email => Email, password => <<"wrong">>}),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 5: Get profile with valid token
|
||||
io:format(" TEST 5: Get profile... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/user/me", Token),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 6: Get profile with invalid token
|
||||
io:format(" TEST 6: Get profile invalid token... "),
|
||||
{ok, {{_, 401, _}, _, _}} = api_test_runner:http_get("/v1/user/me", <<"invalid">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 7: Refresh token
|
||||
io:format(" TEST 7: Refresh token... "),
|
||||
RefreshBody = #{refresh_token => RefreshToken},
|
||||
NewToken = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/refresh", RefreshBody), <<"token">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 8: Refresh with used token (should fail)
|
||||
io:format(" TEST 8: Refresh with used token... "),
|
||||
{ok, {{_, 401, _}, _, _}} = api_test_runner:http_post("/v1/refresh", RefreshBody),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 9: Use new token
|
||||
io:format(" TEST 9: Use new token... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/user/me", NewToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
io:format("~n✅ Authentication API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
@@ -1,83 +0,0 @@
|
||||
-module(api_booking_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
|
||||
test() ->
|
||||
io:format("Testing booking API...~n"),
|
||||
|
||||
OwnerEmail = api_test_runner:unique_email(<<"book_owner">>),
|
||||
ParticipantEmail = api_test_runner:unique_email(<<"book_part">>),
|
||||
|
||||
OwnerToken = api_test_runner:register_and_login(OwnerEmail, <<"owner123">>),
|
||||
ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"part123">>),
|
||||
|
||||
% Используем COMMERCIAL календари
|
||||
AutoCalId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars",
|
||||
#{title => <<"Auto">>, type => <<"commercial">>, confirmation => <<"auto">>}, OwnerToken), <<"id">>),
|
||||
ManualCalId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars",
|
||||
#{title => <<"Manual">>, type => <<"commercial">>, confirmation => <<"manual">>}, OwnerToken), <<"id">>),
|
||||
|
||||
% Создаём события
|
||||
AutoEventId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(AutoCalId) ++ "/events",
|
||||
#{title => <<"Auto Event">>, start_time => <<"2026-06-01T10:00:00Z">>, duration => 60}, OwnerToken), <<"id">>),
|
||||
ManualEventId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(ManualCalId) ++ "/events",
|
||||
#{title => <<"Manual Event">>, start_time => <<"2026-06-01T10:00:00Z">>, duration => 60}, OwnerToken), <<"id">>),
|
||||
|
||||
% TEST 1: Auto booking
|
||||
io:format(" TEST 1: Auto booking... "),
|
||||
AutoBookingId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/events/" ++ binary_to_list(AutoEventId) ++ "/bookings", #{}, ParticipantToken), <<"id">>),
|
||||
timer:sleep(200),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/bookings/" ++ binary_to_list(AutoBookingId), ParticipantToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 2: Manual booking
|
||||
io:format(" TEST 2: Manual booking... "),
|
||||
ManualBookingId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/events/" ++ binary_to_list(ManualEventId) ++ "/bookings", #{}, ParticipantToken), <<"id">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 3: Duplicate booking
|
||||
io:format(" TEST 3: Duplicate booking... "),
|
||||
{ok, {{_, 409, _}, _, _}} = api_test_runner:http_post("/v1/events/" ++ binary_to_list(AutoEventId) ++ "/bookings", #{}, ParticipantToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 4: Owner confirms booking
|
||||
io:format(" TEST 4: Owner confirms booking... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_put("/v1/bookings/" ++ binary_to_list(ManualBookingId),
|
||||
#{action => <<"confirm">>}, OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 5: List event bookings
|
||||
io:format(" TEST 5: List event bookings... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/events/" ++ binary_to_list(ManualEventId) ++ "/bookings", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 6: List user bookings
|
||||
io:format(" TEST 6: List user bookings... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/user/bookings", ParticipantToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 7: Cancel booking
|
||||
io:format(" TEST 7: Cancel booking... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_delete("/v1/bookings/" ++ binary_to_list(AutoBookingId), ParticipantToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 8: Owner declines booking (новое событие)
|
||||
io:format(" TEST 8: Owner declines booking... "),
|
||||
NewEventId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(ManualCalId) ++ "/events",
|
||||
#{title => <<"Decline Event">>, start_time => <<"2026-06-02T10:00:00Z">>, duration => 60}, OwnerToken), <<"id">>),
|
||||
NewBookingId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/events/" ++ binary_to_list(NewEventId) ++ "/bookings", #{}, ParticipantToken), <<"id">>),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_put("/v1/bookings/" ++ binary_to_list(NewBookingId),
|
||||
#{action => <<"decline">>}, OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
io:format("~n✅ Booking API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
@@ -1,61 +0,0 @@
|
||||
-module(api_calendar_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
|
||||
test() ->
|
||||
io:format("Testing calendar API...~n"),
|
||||
|
||||
OwnerEmail = api_test_runner:unique_email(<<"cal_owner">>),
|
||||
OtherEmail = api_test_runner:unique_email(<<"cal_other">>),
|
||||
|
||||
OwnerToken = api_test_runner:register_and_login(OwnerEmail, <<"owner123">>),
|
||||
OtherToken = api_test_runner:register_and_login(OtherEmail, <<"other123">>),
|
||||
|
||||
% TEST 1: Create personal calendar
|
||||
io:format(" TEST 1: Create personal calendar... "),
|
||||
CalId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars",
|
||||
#{title => <<"Personal">>, type => <<"personal">>}, OwnerToken), <<"id">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 2: Create commercial calendar
|
||||
io:format(" TEST 2: Create commercial calendar... "),
|
||||
CommId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars",
|
||||
#{title => <<"Commercial">>, type => <<"commercial">>}, OwnerToken), <<"id">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 3: List calendars
|
||||
io:format(" TEST 3: List calendars... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/calendars", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 4: Get personal calendar (owner)
|
||||
io:format(" TEST 4: Get personal calendar... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/calendars/" ++ binary_to_list(CalId), OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 5: Get personal calendar (other - denied)
|
||||
io:format(" TEST 5: Get personal calendar (other)... "),
|
||||
{ok, {{_, 403, _}, _, _}} = api_test_runner:http_get("/v1/calendars/" ++ binary_to_list(CalId), OtherToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 6: Get commercial calendar (other - allowed)
|
||||
io:format(" TEST 6: Get commercial calendar (other)... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/calendars/" ++ binary_to_list(CommId), OtherToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 7: Update calendar
|
||||
io:format(" TEST 7: Update calendar... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_put("/v1/calendars/" ++ binary_to_list(CalId),
|
||||
#{title => <<"Updated">>}, OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 8: Delete calendar
|
||||
io:format(" TEST 8: Delete calendar... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_delete("/v1/calendars/" ++ binary_to_list(CalId), OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
io:format("~n✅ Calendar API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
@@ -1,70 +0,0 @@
|
||||
-module(api_event_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
|
||||
test() ->
|
||||
io:format("Testing event API...~n"),
|
||||
|
||||
OwnerEmail = api_test_runner:unique_email(<<"ev_owner">>),
|
||||
OwnerToken = api_test_runner:register_and_login(OwnerEmail, <<"owner123">>),
|
||||
|
||||
CalId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars", #{title => <<"Test">>}, OwnerToken), <<"id">>),
|
||||
|
||||
% TEST 1: Create single event
|
||||
io:format(" TEST 1: Create single event... "),
|
||||
EventId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(CalId) ++ "/events",
|
||||
#{title => <<"Test Event">>, start_time => <<"2026-06-01T10:00:00Z">>, duration => 60}, OwnerToken), <<"id">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 2: Create event in past (should fail)
|
||||
io:format(" TEST 2: Create past event... "),
|
||||
{ok, {{_, 400, _}, _, _}} = api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(CalId) ++ "/events",
|
||||
#{title => <<"Past Event">>, start_time => <<"2020-01-01T10:00:00Z">>, duration => 60}, OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 3: Create recurring event
|
||||
io:format(" TEST 3: Create recurring event... "),
|
||||
RecurringId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(CalId) ++ "/events",
|
||||
#{title => <<"Weekly Meeting">>, start_time => <<"2026-06-01T10:00:00Z">>, duration => 60,
|
||||
recurrence => #{freq => <<"WEEKLY">>, interval => 1}}, OwnerToken), <<"id">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 4: List events
|
||||
io:format(" TEST 4: List events... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/calendars/" ++ binary_to_list(CalId) ++ "/events", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 5: Get event
|
||||
io:format(" TEST 5: Get event... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/events/" ++ binary_to_list(EventId), OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 6: Update event
|
||||
io:format(" TEST 6: Update event... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_put("/v1/events/" ++ binary_to_list(EventId),
|
||||
#{title => <<"Updated Event">>}, OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 7: Get occurrences
|
||||
io:format(" TEST 7: Get occurrences... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get(
|
||||
"/v1/events/" ++ binary_to_list(RecurringId) ++ "/occurrences?from=2026-06-01T00:00:00Z&to=2026-06-30T00:00:00Z", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 8: Cancel occurrence
|
||||
io:format(" TEST 8: Cancel occurrence... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_delete(
|
||||
"/v1/events/" ++ binary_to_list(RecurringId) ++ "/occurrences/2026-06-08T10:00:00Z", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 9: Delete event
|
||||
io:format(" TEST 9: Delete event... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_delete("/v1/events/" ++ binary_to_list(EventId), OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
io:format("~n✅ Event API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
@@ -1,72 +0,0 @@
|
||||
-module(api_moderation_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
-define(ADMIN_BASE_URL, api_test_runner:get_admin_url()).
|
||||
|
||||
test() ->
|
||||
io:format("Testing moderation API...~n"),
|
||||
AdminToken = api_test_runner:get_admin_token(),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
%% Создаём календарь и событие
|
||||
CalId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars", #{title => <<"Mod Cal">>}, UserToken),
|
||||
<<"id">>),
|
||||
EventId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(CalId) ++ "/events",
|
||||
#{title => <<"Mod Event">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60},
|
||||
UserToken),
|
||||
<<"id">>),
|
||||
|
||||
%% TEST 1: Create report
|
||||
io:format(" TEST 1: Create report... "),
|
||||
ReportId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/reports",
|
||||
#{target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
reason => <<"Inappropriate">>},
|
||||
UserToken),
|
||||
<<"id">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 2: Admin views reports
|
||||
io:format(" TEST 2: Admin views reports... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/reports", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 3: Admin resolves report с reason
|
||||
io:format(" TEST 3: Admin resolves report... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(put,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/reports/" ++ binary_to_list(ReportId),
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}],
|
||||
"application/json",
|
||||
jsx:encode(#{status => <<"reviewed">>, reason => <<"Resolved by moderator">>})}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 4: Add banned word
|
||||
io:format(" TEST 4: Add banned word... "),
|
||||
{ok, {{_, 201, _}, _, _}} = httpc:request(post,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/banned-words",
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}],
|
||||
"application/json",
|
||||
jsx:encode(#{<<"word">> => <<"badword">>})}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 5: List banned words
|
||||
io:format(" TEST 5: List banned words... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/banned-words", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 6: Remove banned word
|
||||
io:format(" TEST 6: Remove banned word... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(delete,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/banned-words/badword", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
io:format("~n✅ Moderation API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
@@ -1,58 +0,0 @@
|
||||
-module(api_reviews_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
|
||||
test() ->
|
||||
io:format("Testing reviews API...~n"),
|
||||
|
||||
OwnerEmail = api_test_runner:unique_email(<<"rev_owner">>),
|
||||
ParticipantEmail = api_test_runner:unique_email(<<"rev_part">>),
|
||||
|
||||
OwnerToken = api_test_runner:register_and_login(OwnerEmail, <<"owner123">>),
|
||||
ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"part123">>),
|
||||
|
||||
CalId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars", #{title => <<"Review Cal">>}, OwnerToken), <<"id">>),
|
||||
|
||||
EventId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(CalId) ++ "/events",
|
||||
#{title => <<"Review Event">>, start_time => <<"2026-06-01T10:00:00Z">>, duration => 60}, OwnerToken), <<"id">>),
|
||||
|
||||
% Создаём и подтверждаем бронирование
|
||||
BookingId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/events/" ++ binary_to_list(EventId) ++ "/bookings", #{}, ParticipantToken), <<"id">>),
|
||||
api_test_runner:http_put("/v1/bookings/" ++ binary_to_list(BookingId), #{action => <<"confirm">>}, OwnerToken),
|
||||
|
||||
% TEST 1: Create review
|
||||
io:format(" TEST 1: Create review... "),
|
||||
ReviewId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/reviews",
|
||||
#{target_type => <<"event">>, target_id => EventId, rating => 5, comment => <<"Great!">>},
|
||||
ParticipantToken), <<"id">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 2: Duplicate review
|
||||
io:format(" TEST 2: Duplicate review... "),
|
||||
{ok, {{_, 409, _}, _, _}} = api_test_runner:http_post("/v1/reviews",
|
||||
#{target_type => <<"event">>, target_id => EventId, rating => 4, comment => <<"Again">>}, ParticipantToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 3: Get event reviews
|
||||
io:format(" TEST 3: Get event reviews... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/reviews?target_type=event&target_id=" ++ binary_to_list(EventId), ParticipantToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 4: Update review
|
||||
io:format(" TEST 4: Update review... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_put("/v1/reviews/" ++ binary_to_list(ReviewId),
|
||||
#{rating => 4}, ParticipantToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 5: Delete review
|
||||
io:format(" TEST 5: Delete review... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_delete("/v1/reviews/" ++ binary_to_list(ReviewId), ParticipantToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
io:format("~n✅ Reviews API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
@@ -1,54 +0,0 @@
|
||||
-module(api_search_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
|
||||
test() ->
|
||||
io:format("Testing search API...~n"),
|
||||
|
||||
OwnerEmail = api_test_runner:unique_email(<<"search_owner">>),
|
||||
OwnerToken = api_test_runner:register_and_login(OwnerEmail, <<"owner123">>),
|
||||
|
||||
CalId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars", #{title => <<"Search Cal">>}, OwnerToken), <<"id">>),
|
||||
|
||||
% Создаём события с тегами
|
||||
api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(CalId) ++ "/events",
|
||||
#{title => <<"Python Workshop">>, start_time => <<"2026-06-01T10:00:00Z">>, duration => 60,
|
||||
tags => [<<"python">>, <<"workshop">>]}, OwnerToken), <<"id">>),
|
||||
|
||||
api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars/" ++ binary_to_list(CalId) ++ "/events",
|
||||
#{title => <<"JavaScript">>, start_time => <<"2026-06-15T10:00:00Z">>, duration => 60,
|
||||
tags => [<<"javascript">>]}, OwnerToken), <<"id">>),
|
||||
|
||||
timer:sleep(500),
|
||||
|
||||
% TEST 1: Text search
|
||||
io:format(" TEST 1: Text search... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/search?type=event&q=Python", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 2: Tag search
|
||||
io:format(" TEST 2: Tag search... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/search?type=event&tags=workshop", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 3: Combined search
|
||||
io:format(" TEST 3: Combined search... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/search?type=event&q=Python&tags=workshop", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 4: Pagination
|
||||
io:format(" TEST 4: Pagination... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/search?type=event&limit=2", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 5: Search calendars
|
||||
io:format(" TEST 5: Search calendars... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/search?type=calendar", OwnerToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
io:format("~n✅ Search API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
@@ -1,36 +0,0 @@
|
||||
-module(api_subscription_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
|
||||
test() ->
|
||||
io:format("Testing subscription API...~n"),
|
||||
|
||||
UserEmail = api_test_runner:unique_email(<<"sub_user">>),
|
||||
UserToken = api_test_runner:register_and_login(UserEmail, <<"user123">>),
|
||||
|
||||
% TEST 1: Get subscription (free)
|
||||
io:format(" TEST 1: Get subscription (free)... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/subscription", UserToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 2: Create commercial calendar (auto-activates trial)
|
||||
io:format(" TEST 2: Create commercial calendar... "),
|
||||
api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/calendars",
|
||||
#{title => <<"Commercial">>, type => <<"commercial">>}, UserToken), <<"id">>),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 3: Get subscription (trial)
|
||||
io:format(" TEST 3: Get subscription (trial)... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/subscription", UserToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
% TEST 4: Activate paid subscription
|
||||
io:format(" TEST 4: Activate paid subscription... "),
|
||||
{ok, {{_, 201, _}, _, _}} = api_test_runner:http_post("/v1/subscription",
|
||||
#{action => <<"activate">>, plan => <<"monthly">>, payment_info => #{card => <<"4242">>}}, UserToken),
|
||||
io:format("OK~n"),
|
||||
|
||||
io:format("~n✅ Subscription API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
@@ -1,277 +1,302 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Централизованный модуль для запуска API-тестов.
|
||||
%%% Предоставляет функции для выполнения HTTP-запросов
|
||||
%%% к административному и клиентскому API с автоматическим
|
||||
%%% логированием, проверкой статусов и конфигурацией
|
||||
%%% через стандартные переменные окружения.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(api_test_runner).
|
||||
-export([run_all/0, run/1]).
|
||||
-export([http_post/2, http_post/3, http_get/1, http_get/2, http_put/3, http_delete/2]).
|
||||
-export([extract_json/2, extract_json/3, assert_status/2]).
|
||||
-export([unique_email/1, register_and_login/2, create_calendar/2, create_event/3]).
|
||||
-export([get_admin_token/0, get_admin_id/0, get_user_token/0, get_user_id/0, get_admin_url/0, get_base_url/0, get_admin_ws_url/0, get_base_ws_url/0, login_admin/2, login_custom_admin/2]).
|
||||
-export([wait_for_server/0]).
|
||||
-export([format_datetime/1]).
|
||||
|
||||
-define(BASE_URL, base_url()).
|
||||
-define(ADMIN_URL, admin_base_url()).
|
||||
-export([
|
||||
get_admin_url/0,
|
||||
get_base_url/0,
|
||||
get_base_ws_url/0,
|
||||
get_admin_ws_url/0,
|
||||
get_admin_token/0,
|
||||
get_superadmin_token/0,
|
||||
get_moderator_token/0,
|
||||
get_support_token/0,
|
||||
get_user_token/0,
|
||||
unique_email/1,
|
||||
future_date/0,
|
||||
register_and_login/2,
|
||||
create_calendar/2,
|
||||
create_event/3
|
||||
]).
|
||||
-export([
|
||||
admin_request/3,
|
||||
admin_request/4,
|
||||
client_request/3,
|
||||
client_request/4
|
||||
]).
|
||||
-export([
|
||||
admin_get/2,
|
||||
admin_post/3,
|
||||
admin_put/3,
|
||||
admin_delete/2,
|
||||
client_get/2,
|
||||
client_post/3,
|
||||
client_put/3,
|
||||
client_delete/2,
|
||||
admin_patch/3]).
|
||||
|
||||
%% Учётные данные по умолчанию (используются в локальном режиме, если словарь пуст)
|
||||
-define(FALLBACK_ADMIN_EMAIL, <<"admin@eventhub.local">>).
|
||||
-define(FALLBACK_ADMIN_PASSWORD, <<"123456">>).
|
||||
-define(USER_EMAIL, <<"global_user@test.com">>).
|
||||
-define(USER_PASSWORD, <<"user123">>).
|
||||
%%%===================================================================
|
||||
%%% Конфигурация окружения (CT_MODE, ...)
|
||||
%%%===================================================================
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Выбор базовых URL в зависимости от режима запуска
|
||||
%% ------------------------------------------------------------------
|
||||
base_url() ->
|
||||
case os:getenv("CT_MODE", "local") of
|
||||
-spec ct_mode() -> string().
|
||||
ct_mode() ->
|
||||
os:getenv("CT_MODE", "local").
|
||||
|
||||
-spec get_base_url() -> string().
|
||||
get_base_url() ->
|
||||
case ct_mode() of
|
||||
"remote" -> os:getenv("API_HOST", "http://localhost:8080");
|
||||
_ -> "http://localhost:8080"
|
||||
end.
|
||||
|
||||
base_ws_url() ->
|
||||
case os:getenv("CT_MODE", "local") of
|
||||
"remote" -> os:getenv("WS_HOST", "ws://localhost:8081");
|
||||
_ -> "ws://localhost:8081"
|
||||
end.
|
||||
|
||||
admin_base_url() ->
|
||||
case os:getenv("CT_MODE", "local") of
|
||||
-spec get_admin_url() -> string().
|
||||
get_admin_url() ->
|
||||
case ct_mode() of
|
||||
"remote" -> os:getenv("ADMIN_API_HOST", "http://localhost:8445");
|
||||
_ -> "http://localhost:8445"
|
||||
end.
|
||||
|
||||
admin_ws_url() ->
|
||||
case os:getenv("CT_MODE", "local") of
|
||||
-spec get_base_ws_url() -> string().
|
||||
get_base_ws_url() ->
|
||||
case ct_mode() of
|
||||
"remote" -> os:getenv("WS_HOST", "ws://localhost:8081");
|
||||
_ -> "ws://localhost:8081"
|
||||
end.
|
||||
|
||||
-spec get_admin_ws_url() -> string().
|
||||
get_admin_ws_url() ->
|
||||
case ct_mode() of
|
||||
"remote" -> os:getenv("ADMIN_WS_HOST", "ws://localhost:8446");
|
||||
_ -> "ws://localhost:8446"
|
||||
end.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Инициализация глобальных тестовых пользователей
|
||||
%% ------------------------------------------------------------------
|
||||
init_global_urls() ->
|
||||
put(admin_url, admin_base_url()),
|
||||
put(admin_ws_url, admin_ws_url()),
|
||||
put(base_url, base_url()),
|
||||
put(base_ws_url, base_ws_url()).
|
||||
%%%===================================================================
|
||||
%%% Учётные данные администраторов (из переменных окружения)
|
||||
%%%===================================================================
|
||||
|
||||
init_global_users() ->
|
||||
case get(admin_token) of
|
||||
undefined ->
|
||||
ct:pal("~n=== Initializing global test users ===~n"),
|
||||
-spec admin_super_email() -> binary().
|
||||
admin_super_email() ->
|
||||
list_to_binary(os:getenv("ADMIN_SUPER_EMAIL", "superadmin@eventhub.local")).
|
||||
|
||||
%% 1. Администратор
|
||||
AdminEmail = get(admin_super_email),
|
||||
AdminPassword = get(admin_super_password),
|
||||
AdminToken =
|
||||
if
|
||||
AdminEmail =/= undefined, AdminPassword =/= undefined ->
|
||||
%% Учётные данные переданы из api_SUITE (remote‑режим) – просто логинимся
|
||||
login_admin(AdminEmail, AdminPassword);
|
||||
true ->
|
||||
%% Локальный режим: админы уже есть, логинимся под суперадмином
|
||||
login_admin(?FALLBACK_ADMIN_EMAIL, ?FALLBACK_ADMIN_PASSWORD)
|
||||
end,
|
||||
-spec admin_super_password() -> binary().
|
||||
admin_super_password() ->
|
||||
list_to_binary(os:getenv("ADMIN_SUPER_PASSWORD", "123456")).
|
||||
|
||||
%% Получаем ID администратора через /v1/admin/me
|
||||
MeUrl = ?ADMIN_URL ++ "/v1/admin/me",
|
||||
{ok, {{_, 200, _}, _, MeBody}} = httpc:request(get,
|
||||
{MeUrl, [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, ssl_opts(), []),
|
||||
#{<<"id">> := AdminId} = jsx:decode(list_to_binary(MeBody), [return_maps]),
|
||||
-spec admin_email() -> binary().
|
||||
admin_email() ->
|
||||
list_to_binary(os:getenv("ADMIN_EMAIL", "admin@eventhub.local")).
|
||||
|
||||
put(admin_token, AdminToken),
|
||||
put(admin_id, AdminId),
|
||||
-spec admin_password() -> binary().
|
||||
admin_password() ->
|
||||
list_to_binary(os:getenv("ADMIN_PASSWORD", "123456")).
|
||||
|
||||
%% 2. Обычный пользователь
|
||||
UserToken = register_and_login(?USER_EMAIL, ?USER_PASSWORD),
|
||||
{ok, {{_, 200, _}, _, UserMeBody}} = http_get("/v1/user/me", UserToken),
|
||||
#{<<"id">> := UserId} = jsx:decode(list_to_binary(UserMeBody), [return_maps]),
|
||||
-spec admin_moder_email() -> binary().
|
||||
admin_moder_email() ->
|
||||
list_to_binary(os:getenv("ADMIN_MODER_EMAIL", "moderator@eventhub.local")).
|
||||
|
||||
put(user_token, UserToken),
|
||||
put(user_id, UserId),
|
||||
-spec admin_moder_password() -> binary().
|
||||
admin_moder_password() ->
|
||||
list_to_binary(os:getenv("ADMIN_MODER_PASSWORD", "123456")).
|
||||
|
||||
ct:pal("Admin ID: ~s, User ID: ~s~n", [AdminId, UserId]),
|
||||
ct:pal("=== Global users initialized ===~n~n"),
|
||||
ok;
|
||||
-spec admin_support_email() -> binary().
|
||||
admin_support_email() ->
|
||||
list_to_binary(os:getenv("ADMIN_SUPPORT_EMAIL", "support@eventhub.local")).
|
||||
|
||||
-spec admin_support_password() -> binary().
|
||||
admin_support_password() ->
|
||||
list_to_binary(os:getenv("ADMIN_SUPPORT_PASSWORD", "123456")).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Получение токенов (с кешированием в persistent_term)
|
||||
%%%===================================================================
|
||||
|
||||
-spec get_admin_token() -> binary().
|
||||
get_admin_token() ->
|
||||
get_or_login(admin, admin_email(), admin_password()).
|
||||
|
||||
-spec get_superadmin_token() -> binary().
|
||||
get_superadmin_token() ->
|
||||
get_or_login(superadmin, admin_super_email(), admin_super_password()).
|
||||
|
||||
-spec get_moderator_token() -> binary().
|
||||
get_moderator_token() ->
|
||||
get_or_login(moderator, admin_moder_email(), admin_moder_password()).
|
||||
|
||||
-spec get_support_token() -> binary().
|
||||
get_support_token() ->
|
||||
get_or_login(support, admin_support_email(), admin_support_password()).
|
||||
|
||||
-spec get_or_login(atom(), binary(), binary()) -> binary().
|
||||
get_or_login(Role, Email, Password) ->
|
||||
Key = {?MODULE, admin_token, Role},
|
||||
case persistent_term:get(Key, undefined) of
|
||||
Token when is_binary(Token) -> Token;
|
||||
_ ->
|
||||
ct:pal("Global users already initialized.~n"),
|
||||
ok
|
||||
Token = login_admin(Email, Password),
|
||||
persistent_term:put(Key, Token),
|
||||
timer:apply_after(5 * 60 * 1000, fun() -> persistent_term:erase(Key) end),
|
||||
Token
|
||||
end.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Вход администратора (используется, когда учётки уже известны)
|
||||
%% ------------------------------------------------------------------
|
||||
login_admin(Email, Password) ->
|
||||
ct:pal("Admin url: ~s~n", [?ADMIN_URL]),
|
||||
ct:pal("Admin: ~s, password: ~s~n", [Email, Password]),
|
||||
LoginBody = jsx:encode(#{<<"email">> => Email, <<"password">> => Password}),
|
||||
ct:pal("url: ~s, body: ~s~n", [?ADMIN_URL ++ "/v1/admin/login", LoginBody]),
|
||||
{ok, {{_, _, _}, _, LoginResp}} = httpc:request(post,
|
||||
{?ADMIN_URL ++ "/v1/admin/login", [], "application/json", LoginBody}, ssl_opts(), []),
|
||||
ct:pal("LoginResp: ~s~n", [LoginResp]),
|
||||
#{<<"token">> := Token} = jsx:decode(list_to_binary(LoginResp), [return_maps]),
|
||||
%% @doc Возвращает JWT-токен обычного пользователя.
|
||||
%% При каждом вызове создаёт нового уникального пользователя,
|
||||
%% чтобы избежать конфликтов состояния в тестах.
|
||||
-spec get_user_token() -> binary().
|
||||
get_user_token() ->
|
||||
Email = unique_email(<<"testuser">>),
|
||||
register_and_login(Email, <<"testpass">>).
|
||||
|
||||
%%%===================================================================
|
||||
%%% HTTP-клиент (логирование, заголовки)
|
||||
%%%===================================================================
|
||||
|
||||
-spec admin_request(atom(), binary(), binary()) -> {ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
admin_request(Method, Path, Token) ->
|
||||
admin_request(Method, Path, Token, <<>>).
|
||||
|
||||
-spec admin_request(atom(), binary(), binary(), binary()) -> {ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
admin_request(Method, Path, Token, Body) ->
|
||||
request(get_admin_url(), Method, Path, Token, Body, "ADMIN").
|
||||
|
||||
-spec client_request(atom(), binary(), binary()) -> {ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
client_request(Method, Path, Token) ->
|
||||
client_request(Method, Path, Token, <<>>).
|
||||
|
||||
-spec client_request(atom(), binary(), binary(), binary()) -> {ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
client_request(Method, Path, Token, Body) ->
|
||||
request(get_base_url(), Method, Path, Token, Body, "CLIENT").
|
||||
|
||||
%%%===================================================================
|
||||
%%% Внутренняя реализация HTTP-запроса
|
||||
%%%===================================================================
|
||||
|
||||
-spec request(string(), atom(), binary(), binary(), binary(), string()) -> {ok, integer(), proplists:proplist(), binary()} | {error, term()}.
|
||||
request(BaseUrl, Method, Path, Token, Body, Prefix) ->
|
||||
URL = BaseUrl ++ binary_to_list(Path),
|
||||
Headers0 = [],
|
||||
Headers = case Token of
|
||||
<<>> -> Headers0; % пустой токен – не добавляем Authorization
|
||||
_ -> [{"Authorization", "Bearer " ++ binary_to_list(Token)}]
|
||||
end,
|
||||
ct:pal("~s REQUEST: ~s ~s", [Prefix, Method, URL]),
|
||||
RequestArg = case Method of
|
||||
get -> {URL, Headers};
|
||||
delete -> {URL, Headers};
|
||||
_ -> {URL, Headers, "application/json", Body}
|
||||
end,
|
||||
Response = httpc:request(Method, RequestArg, [], []),
|
||||
case Response of
|
||||
{ok, {{_, Status, _}, RespHeaders, RespBody}} ->
|
||||
ct:pal("~s RESPONSE: ~p ~s", [Prefix, Status, RespBody]),
|
||||
{ok, Status, RespHeaders, RespBody};
|
||||
_ ->
|
||||
ct:pal("~s REQUEST ERROR: ~p", [Prefix, Response]),
|
||||
{error, Response}
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Высокоуровневые обёртки (GET/POST/PUT/DELETE)
|
||||
%%%===================================================================
|
||||
|
||||
-spec admin_get(binary(), binary()) -> jsx:json_term().
|
||||
admin_get(Path, Token) ->
|
||||
{ok, 200, _, Body} = admin_request(get, Path, Token),
|
||||
jsx:decode(list_to_binary(Body), [return_maps]).
|
||||
|
||||
-spec admin_post(binary(), binary(), map()) -> jsx:json_term().
|
||||
admin_post(Path, Token, BodyMap) ->
|
||||
Body = jsx:encode(BodyMap),
|
||||
{ok, 201, _, RespBody} = admin_request(post, Path, Token, Body),
|
||||
jsx:decode(list_to_binary(RespBody), [return_maps]).
|
||||
|
||||
-spec admin_put(binary(), binary(), map()) -> jsx:json_term().
|
||||
admin_put(Path, Token, BodyMap) ->
|
||||
Body = jsx:encode(BodyMap),
|
||||
{ok, 200, _, RespBody} = admin_request(put, Path, Token, Body),
|
||||
jsx:decode(list_to_binary(RespBody), [return_maps]).
|
||||
|
||||
%% В api_test_runner.erl добавить в блок высокоуровневых обёрток:
|
||||
-spec admin_patch(binary(), binary(), [map()]) -> jsx:json_term().
|
||||
admin_patch(Path, Token, BodyList) ->
|
||||
Body = jsx:encode(BodyList),
|
||||
{ok, 200, _, RespBody} = admin_request(patch, Path, Token, Body),
|
||||
jsx:decode(list_to_binary(RespBody), [return_maps]).
|
||||
|
||||
-spec admin_delete(binary(), binary()) -> jsx:json_term().
|
||||
admin_delete(Path, Token) ->
|
||||
{ok, 200, _, Body} = admin_request(delete, Path, Token),
|
||||
jsx:decode(list_to_binary(Body), [return_maps]).
|
||||
|
||||
-spec client_get(binary(), binary()) -> jsx:json_term().
|
||||
client_get(Path, Token) ->
|
||||
{ok, 200, _, Body} = client_request(get, Path, Token),
|
||||
jsx:decode(list_to_binary(Body), [return_maps]).
|
||||
|
||||
-spec client_post(binary(), binary(), map()) -> jsx:json_term().
|
||||
client_post(Path, Token, BodyMap) ->
|
||||
Body = jsx:encode(BodyMap),
|
||||
{ok, 201, _, RespBody} = client_request(post, Path, Token, Body),
|
||||
jsx:decode(list_to_binary(RespBody), [return_maps]).
|
||||
|
||||
-spec client_put(binary(), binary(), map()) -> jsx:json_term().
|
||||
client_put(Path, Token, BodyMap) ->
|
||||
Body = jsx:encode(BodyMap),
|
||||
{ok, 200, _, RespBody} = client_request(put, Path, Token, Body),
|
||||
jsx:decode(list_to_binary(RespBody), [return_maps]).
|
||||
|
||||
-spec client_delete(binary(), binary()) -> jsx:json_term().
|
||||
client_delete(Path, Token) ->
|
||||
{ok, 200, _, Body} = client_request(delete, Path, Token),
|
||||
jsx:decode(list_to_binary(Body), [return_maps]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Фикстуры (создание тестовых данных)
|
||||
%%%===================================================================
|
||||
|
||||
-spec unique_email(binary()) -> binary().
|
||||
unique_email(Prefix) ->
|
||||
Unique = integer_to_binary(erlang:system_time()),
|
||||
<<Prefix/binary, "_", Unique/binary, "@test.local">>.
|
||||
|
||||
-spec future_date() -> calendar:datetime().
|
||||
future_date() ->
|
||||
Seconds = calendar:datetime_to_gregorian_seconds(calendar:universal_time()) + 86400,
|
||||
calendar:gregorian_seconds_to_datetime(Seconds).
|
||||
|
||||
-spec register_and_login(binary(), binary()) -> binary().
|
||||
register_and_login(Email, Password) ->
|
||||
Resp = client_request(post, <<"/v1/register">>, <<>>,
|
||||
jsx:encode(#{email => Email, password => Password})),
|
||||
{ok, 201, _, Body} = Resp,
|
||||
#{<<"token">> := Token} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
Token.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Остальные функции (без изменений, только используют ?BASE_URL / ?ADMIN_URL)
|
||||
%% ------------------------------------------------------------------
|
||||
get_admin_url() ->
|
||||
init_global_urls(),
|
||||
get(admin_url).
|
||||
|
||||
get_admin_ws_url() ->
|
||||
init_global_urls(),
|
||||
get(admin_ws_url).
|
||||
|
||||
get_base_url() ->
|
||||
init_global_urls(),
|
||||
get(base_url).
|
||||
|
||||
get_base_ws_url() ->
|
||||
init_global_urls(),
|
||||
get(base_ws_url).
|
||||
|
||||
get_admin_token() ->
|
||||
init_global_users(),
|
||||
get(admin_token).
|
||||
|
||||
get_admin_id() ->
|
||||
init_global_users(),
|
||||
get(admin_id).
|
||||
|
||||
get_user_token() ->
|
||||
init_global_users(),
|
||||
get(user_token).
|
||||
|
||||
get_user_id() ->
|
||||
init_global_users(),
|
||||
get(user_id).
|
||||
|
||||
run_all() ->
|
||||
inets:start(),
|
||||
ssl:start(),
|
||||
|
||||
case wait_for_server() of
|
||||
ok -> ok;
|
||||
{error, _} -> ct:pal("❌ Server is not running!~n"), exit(server_not_running)
|
||||
end,
|
||||
|
||||
init_global_users(),
|
||||
|
||||
ct:pal("Starting API tests...~n"),
|
||||
Modules = [
|
||||
api_auth_tests,
|
||||
api_calendar_tests,
|
||||
api_event_tests,
|
||||
api_booking_tests,
|
||||
api_search_tests,
|
||||
api_reviews_tests,
|
||||
api_moderation_tests,
|
||||
api_tickets_tests,
|
||||
api_subscription_tests,
|
||||
api_admin_tests
|
||||
],
|
||||
lists:foreach(fun(M) -> M:test() end, Modules).
|
||||
|
||||
run(Module) ->
|
||||
inets:start(),
|
||||
ssl:start(),
|
||||
init_global_users(),
|
||||
Module:test().
|
||||
|
||||
%% ── HTTP‑запросы ─────────────────────────────────────────
|
||||
ssl_opts() ->
|
||||
[{ssl, [{verify, verify_none}]}].
|
||||
|
||||
http_post(Url, Body) -> http_post(Url, Body, undefined).
|
||||
http_post(Url, Body, Token) ->
|
||||
Headers = case Token of
|
||||
undefined -> [{"Content-Type", "application/json"}];
|
||||
_ -> [{"Content-Type", "application/json"}, {"Authorization", "Bearer " ++ binary_to_list(Token)}]
|
||||
end,
|
||||
httpc:request(post, {?BASE_URL ++ Url, Headers, "application/json", jsx:encode(Body)}, ssl_opts(), []).
|
||||
|
||||
http_get(Url) -> http_get(Url, undefined).
|
||||
http_get(Url, Token) ->
|
||||
Headers = case Token of
|
||||
undefined -> [];
|
||||
_ -> [{"Authorization", "Bearer " ++ binary_to_list(Token)}]
|
||||
end,
|
||||
httpc:request(get, {?BASE_URL ++ Url, Headers}, ssl_opts(), []).
|
||||
|
||||
http_put(Url, Body, Token) ->
|
||||
Headers = [{"Content-Type", "application/json"}, {"Authorization", "Bearer " ++ binary_to_list(Token)}],
|
||||
httpc:request(put, {?BASE_URL ++ Url, Headers, "application/json", jsx:encode(Body)}, ssl_opts(), []).
|
||||
|
||||
http_delete(Url, Token) ->
|
||||
Headers = [{"Authorization", "Bearer " ++ binary_to_list(Token)}],
|
||||
httpc:request(delete, {?BASE_URL ++ Url, Headers}, ssl_opts(), []).
|
||||
|
||||
%% ── Вспомогательные функции ──────────────────────────────
|
||||
extract_json({ok, {{_, 200, _}, _, Body}}, Field) ->
|
||||
Map = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
maps:get(Field, Map);
|
||||
extract_json({ok, {{_, 201, _}, _, Body}}, Field) ->
|
||||
Map = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
maps:get(Field, Map);
|
||||
extract_json(Response, _Field) ->
|
||||
error({unexpected_response, Response}).
|
||||
|
||||
extract_json(Response, Field, ExpectedStatus) ->
|
||||
case Response of
|
||||
{ok, {{_, ExpectedStatus, _}, _, Body}} ->
|
||||
Map = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
maps:get(Field, Map);
|
||||
_ ->
|
||||
error({unexpected_response, Response})
|
||||
end.
|
||||
|
||||
assert_status(Status, {ok, {{_, Status, _}, _, _}}) -> ok;
|
||||
assert_status(Expected, {ok, {{_, Got, _}, _, _}}) ->
|
||||
error({expected_status, Expected, got, Got}).
|
||||
|
||||
unique_email(Prefix) ->
|
||||
list_to_binary([Prefix, "_", integer_to_binary(os:system_time(millisecond)), "@test.com"]).
|
||||
|
||||
register_and_login(Email, Password) ->
|
||||
RegBody = #{email => Email, password => Password},
|
||||
case http_post("/v1/register", RegBody) of
|
||||
{ok, {{_, 201, _}, _, RegResp}} ->
|
||||
Map = jsx:decode(list_to_binary(RegResp), [return_maps]),
|
||||
maps:get(<<"token">>, Map);
|
||||
{ok, {{_, 409, _}, _, _}} ->
|
||||
LoginBody = #{email => Email, password => Password},
|
||||
{ok, {{_, 200, _}, _, LoginResp}} = http_post("/v1/login", LoginBody),
|
||||
Map = jsx:decode(list_to_binary(LoginResp), [return_maps]),
|
||||
maps:get(<<"token">>, Map)
|
||||
end.
|
||||
|
||||
login_custom_admin(Email, Password) ->
|
||||
%% LoginBody = #{email => Email, password => Password},
|
||||
LoginBody = jsx:encode(#{<<"email">> => Email, <<"password">> => Password}),
|
||||
{ok, {{_, _, _}, _, LoginResp}} = httpc:request(post,
|
||||
{?ADMIN_URL ++ "/v1/admin/login", [], "application/json", LoginBody}, ssl_opts(), []),
|
||||
Map = jsx:decode(list_to_binary(LoginResp), [return_maps]),
|
||||
maps:get(<<"token">>, Map).
|
||||
|
||||
-spec create_calendar(binary(), map()) -> binary().
|
||||
create_calendar(Token, Params) ->
|
||||
Response = http_post("/v1/calendars", Params, Token),
|
||||
ct:pal(" create_calendar Response: ~p~n", [Response]),
|
||||
Id = extract_json(Response, <<"id">>),
|
||||
Id.
|
||||
#{<<"id">> := CalId} = client_post(<<"/v1/calendars">>, Token, Params),
|
||||
CalId.
|
||||
|
||||
-spec create_event(binary(), binary(), map()) -> binary().
|
||||
create_event(Token, CalId, Params) ->
|
||||
Url = "/v1/calendars/" ++ binary_to_list(CalId) ++ "/events",
|
||||
Id = extract_json(http_post(Url, Params, Token), <<"id">>),
|
||||
Id.
|
||||
Path = <<"/v1/calendars/", CalId/binary, "/events">>,
|
||||
#{<<"id">> := EventId} = client_post(Path, Token, Params),
|
||||
EventId.
|
||||
|
||||
wait_for_server() -> wait_for_server(30).
|
||||
wait_for_server(0) -> {error, timeout};
|
||||
wait_for_server(Attempts) ->
|
||||
case httpc:request(get, {?BASE_URL ++ "/health", []}, ssl_opts(), [{timeout, 1000}]) of
|
||||
{ok, {{_, 200, _}, _, _}} -> ok;
|
||||
_ -> timer:sleep(1000), wait_for_server(Attempts - 1)
|
||||
end.
|
||||
%%%===================================================================
|
||||
%%% Внутренние функции
|
||||
%%%===================================================================
|
||||
|
||||
format_datetime({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
||||
iolist_to_binary(
|
||||
io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
|
||||
[Year, Month, Day, Hour, Minute, Second])
|
||||
).
|
||||
-spec login_admin(binary(), binary()) -> binary().
|
||||
login_admin(Email, Password) ->
|
||||
BodyMap = #{<<"email">> => Email, <<"password">> => Password},
|
||||
Body = jsx:encode(BodyMap),
|
||||
{ok, 200, _, RespBody} = admin_request(post, <<"/v1/admin/login">>, <<>>, Body),
|
||||
#{<<"token">> := Token} = jsx:decode(list_to_binary(RespBody), [return_maps]),
|
||||
Token.
|
||||
@@ -1,72 +0,0 @@
|
||||
-module(api_tickets_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(ADMIN_BASE_URL, api_test_runner:get_admin_url()).
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
|
||||
test() ->
|
||||
io:format("Testing tickets API...~n"),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
AdminToken = api_test_runner:get_admin_token(),
|
||||
|
||||
%% TEST 1: Create ticket (user)
|
||||
io:format(" TEST 1: Create ticket... "),
|
||||
TicketId = api_test_runner:extract_json(
|
||||
api_test_runner:http_post("/v1/tickets",
|
||||
#{error_message => <<"Bug">>,
|
||||
stacktrace => <<"Something broke">>},
|
||||
Token),
|
||||
<<"id">>),
|
||||
ct:pal(" OK (TicketId: ~p)~n", [TicketId]),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 2: Get my tickets (user)
|
||||
io:format(" TEST 2: Get my tickets... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get("/v1/tickets", Token),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 3: Get single ticket (user)
|
||||
io:format(" TEST 3: Get single ticket... "),
|
||||
{ok, {{_, 200, _}, _, _}} = api_test_runner:http_get(
|
||||
"/v1/tickets/" ++ binary_to_list(TicketId),
|
||||
Token),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 4: Admin lists all tickets
|
||||
io:format(" TEST 4: Admin lists all tickets... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/tickets", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 5: Admin updates ticket status
|
||||
io:format(" TEST 5: Admin updates ticket status... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(put,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/tickets/" ++ binary_to_list(TicketId),
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}],
|
||||
"application/json",
|
||||
jsx:encode(#{status => <<"in_progress">>})}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 6: Admin assigns ticket
|
||||
io:format(" TEST 6: Admin assigns ticket... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(put,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/tickets/" ++ binary_to_list(TicketId),
|
||||
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}],
|
||||
"application/json",
|
||||
jsx:encode(#{assigned_to => AdminToken})}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 7: Admin views ticket stats
|
||||
io:format(" TEST 7: Admin views ticket stats... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(get,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/tickets/stats", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
%% TEST 8: Admin deletes ticket
|
||||
io:format(" TEST 8: Admin deletes ticket... "),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(delete,
|
||||
{?ADMIN_BASE_URL ++ "/v1/admin/tickets/" ++ binary_to_list(TicketId), [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]}, [], []),
|
||||
io:format("OK~n"),
|
||||
|
||||
io:format("~n✅ Tickets API tests passed!~n"),
|
||||
{?MODULE, ok}.
|
||||
115
test/api/users/user_bookings_tests.erl
Normal file
115
test/api/users/user_bookings_tests.erl
Normal file
@@ -0,0 +1,115 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для бронирований.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% POST /v1/events/:id/bookings
|
||||
%%% PUT /v1/bookings/:id
|
||||
%%% DELETE /v1/bookings/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - создание бронирования участником
|
||||
%%% - подтверждение бронирования владельцем календаря
|
||||
%%% - отмену бронирования участником
|
||||
%%% - ошибку при повторном бронировании
|
||||
%%% - ошибку 401 без токена
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_bookings_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Bookings Tests ==="),
|
||||
OwnerToken = api_test_runner:get_user_token(),
|
||||
ParticipantEmail = api_test_runner:unique_email(<<"participant">>),
|
||||
ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"pass">>),
|
||||
|
||||
% Создаём календарь и событие
|
||||
CalId = api_test_runner:create_calendar(OwnerToken, #{title => <<"BookingTest">>}),
|
||||
#{<<"id">> := EventId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken,
|
||||
#{title => <<"Event to book">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60}),
|
||||
|
||||
test_create_booking(ParticipantToken, EventId),
|
||||
test_confirm_booking(OwnerToken, EventId),
|
||||
test_cancel_booking(ParticipantToken, EventId),
|
||||
test_duplicate_booking(ParticipantToken, EventId),
|
||||
test_booking_unauthorized(EventId),
|
||||
|
||||
ct:pal("=== All user bookings tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешное создание бронирования: 201 Created.
|
||||
-spec test_create_booking(binary(), binary()) -> ok.
|
||||
test_create_booking(Token, EventId) ->
|
||||
ct:pal(" TEST: Create booking"),
|
||||
Path = <<"/v1/events/", EventId/binary, "/bookings">>,
|
||||
Resp = api_test_runner:client_request(post, Path, Token, <<"{}">>),
|
||||
{ok, 201, _, Body} = Resp,
|
||||
#{<<"id">> := BookingId, <<"status">> := Status} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assert(is_binary(BookingId)),
|
||||
?assertEqual(<<"pending">>, Status),
|
||||
ct:pal(" OK: booking ~s created", [BookingId]).
|
||||
|
||||
%% @doc Подтверждение бронирования владельцем: 200 OK.
|
||||
-spec test_confirm_booking(binary(), binary()) -> ok.
|
||||
test_confirm_booking(OwnerToken, EventId) ->
|
||||
ct:pal(" TEST: Confirm booking as owner"),
|
||||
% Создаём новое бронирование, которое ещё не подтверждено
|
||||
Participant2 = api_test_runner:register_and_login(
|
||||
api_test_runner:unique_email(<<"part2">>), <<"pass">>),
|
||||
Path = <<"/v1/events/", EventId/binary, "/bookings">>,
|
||||
#{<<"id">> := BookingId} = api_test_runner:client_post(Path, Participant2, #{}),
|
||||
% Подтверждаем
|
||||
ConfirmPath = <<"/v1/bookings/", BookingId/binary>>,
|
||||
Updated = api_test_runner:client_put(ConfirmPath, OwnerToken,
|
||||
#{action => <<"confirm">>}),
|
||||
?assertEqual(<<"confirmed">>, maps:get(<<"status">>, Updated)),
|
||||
ct:pal(" OK: booking ~s confirmed", [BookingId]).
|
||||
|
||||
%% @doc Отмена бронирования участником: 200 OK.
|
||||
-spec test_cancel_booking(binary(), binary()) -> ok.
|
||||
test_cancel_booking(OwnerToken, EventId) ->
|
||||
ct:pal(" TEST: Cancel booking as participant"),
|
||||
% Создаём нового участника, у которого нет бронирований
|
||||
CancelUserEmail = api_test_runner:unique_email(<<"canceluser">>),
|
||||
CancelUserToken = api_test_runner:register_and_login(CancelUserEmail, <<"pass">>),
|
||||
Path = <<"/v1/events/", EventId/binary, "/bookings">>,
|
||||
#{<<"id">> := BookingId} = api_test_runner:client_post(Path, CancelUserToken, #{}),
|
||||
CancelPath = <<"/v1/bookings/", BookingId/binary>>,
|
||||
Resp = api_test_runner:client_request(delete, CancelPath, CancelUserToken),
|
||||
?assertMatch({ok, 200, _, _}, Resp),
|
||||
ct:pal(" OK: booking ~s cancelled", [BookingId]).
|
||||
|
||||
%% @doc Повторное бронирование того же события: 409 Conflict.
|
||||
-spec test_duplicate_booking(binary(), binary()) -> ok.
|
||||
test_duplicate_booking(ParticipantToken, EventId) ->
|
||||
ct:pal(" TEST: Duplicate booking"),
|
||||
Path = <<"/v1/events/", EventId/binary, "/bookings">>,
|
||||
% Первый раз должно быть 201 (или 200, если уже есть pending)
|
||||
_ = api_test_runner:client_request(post, Path, ParticipantToken, <<"{}">>),
|
||||
% Второй раз – уже booked
|
||||
Resp2 = api_test_runner:client_request(post, Path, ParticipantToken, <<"{}">>),
|
||||
{ok, 409, _, _} = Resp2,
|
||||
ct:pal(" OK: got 409 conflict").
|
||||
|
||||
%% @doc Запрос без токена: 401 Unauthorized.
|
||||
-spec test_booking_unauthorized(binary()) -> ok.
|
||||
test_booking_unauthorized(EventId) ->
|
||||
ct:pal(" TEST: Booking without token"),
|
||||
Path = <<"/v1/events/", EventId/binary, "/bookings">>,
|
||||
Resp = api_test_runner:client_request(post, Path, <<>>, <<"{}">>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
87
test/api/users/user_calendar_by_id_tests.erl
Normal file
87
test/api/users/user_calendar_by_id_tests.erl
Normal file
@@ -0,0 +1,87 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для работы с конкретным календарём.
|
||||
%%% Покрывает GET, PUT, DELETE /v1/calendars/:id.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_calendar_by_id_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Calendar By ID Tests ==="),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
OtherToken = api_test_runner:register_and_login(
|
||||
api_test_runner:unique_email(<<"other">>), <<"pass">>),
|
||||
|
||||
% Создаём календарь для тестов
|
||||
#{<<"id">> := CalId} = api_test_runner:client_post(<<"/v1/calendars">>, Token,
|
||||
#{title => <<"TestCal">>, type => <<"personal">>}),
|
||||
|
||||
test_get_calendar(Token, CalId),
|
||||
test_get_calendar_unauthorized(CalId),
|
||||
test_get_calendar_not_found(Token),
|
||||
test_update_calendar(Token, CalId),
|
||||
test_update_calendar_forbidden(OtherToken, CalId),
|
||||
test_delete_calendar(Token, CalId),
|
||||
test_delete_calendar_forbidden(OtherToken, CalId),
|
||||
|
||||
ct:pal("=== All user calendar by id tests passed ==="),
|
||||
ok.
|
||||
|
||||
test_get_calendar(Token, CalId) ->
|
||||
ct:pal(" TEST: Get calendar by ID"),
|
||||
Path = <<"/v1/calendars/", CalId/binary>>,
|
||||
Cal = api_test_runner:client_get(Path, Token),
|
||||
?assertEqual(CalId, maps:get(<<"id">>, Cal)),
|
||||
?assert(maps:is_key(<<"title">>, Cal)),
|
||||
ct:pal(" OK: ~s", [maps:get(<<"title">>, Cal)]).
|
||||
|
||||
test_get_calendar_unauthorized(CalId) ->
|
||||
ct:pal(" TEST: Get calendar without token (401)"),
|
||||
Path = <<"/v1/calendars/", CalId/binary>>,
|
||||
Resp = api_test_runner:client_request(get, Path, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
|
||||
test_get_calendar_not_found(Token) ->
|
||||
ct:pal(" TEST: Get non-existent calendar (404)"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/calendars/fakeid">>, Token),
|
||||
?assertMatch({ok, 404, _, _}, Resp),
|
||||
ct:pal(" OK: got 404").
|
||||
|
||||
test_update_calendar(Token, CalId) ->
|
||||
ct:pal(" TEST: Update calendar"),
|
||||
Path = <<"/v1/calendars/", CalId/binary>>,
|
||||
Updated = api_test_runner:client_put(Path, Token,
|
||||
#{title => <<"Updated">>, description => <<"New desc">>}),
|
||||
?assertEqual(<<"Updated">>, maps:get(<<"title">>, Updated)),
|
||||
?assertEqual(<<"New desc">>, maps:get(<<"description">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_update_calendar_forbidden(OtherToken, CalId) ->
|
||||
ct:pal(" TEST: Update calendar by non-owner (403)"),
|
||||
Path = <<"/v1/calendars/", CalId/binary>>,
|
||||
Resp = api_test_runner:client_request(put, Path, OtherToken,
|
||||
jsx:encode(#{title => <<"fail">>})),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
test_delete_calendar(Token, CalId) ->
|
||||
ct:pal(" TEST: Delete calendar (soft-delete)"),
|
||||
Path = <<"/v1/calendars/", CalId/binary>>,
|
||||
Resp = api_test_runner:client_request(delete, Path, Token),
|
||||
?assertMatch({ok, 200, _, _}, Resp),
|
||||
ct:pal(" OK: deleted").
|
||||
|
||||
test_delete_calendar_forbidden(OtherToken, CalId) ->
|
||||
% Первый раз мы уже удалили, но проверим на другом календаре
|
||||
ct:pal(" TEST: Delete calendar by non-owner (403)"),
|
||||
% Создадим новый календарь владельцем Token, попробуем удалить OtherToken
|
||||
#{<<"id">> := NewCalId} = api_test_runner:client_post(<<"/v1/calendars">>, api_test_runner:get_user_token(),
|
||||
#{title => <<"ForbiddenDel">>, type => <<"personal">>}),
|
||||
Path = <<"/v1/calendars/", NewCalId/binary>>,
|
||||
Resp = api_test_runner:client_request(delete, Path, OtherToken),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
61
test/api/users/user_calendar_view_tests.erl
Normal file
61
test/api/users/user_calendar_view_tests.erl
Normal file
@@ -0,0 +1,61 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для HTML-представления календаря.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/calendars/:calendar_id/view
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - успешное получение HTML-страницы (200, text/html)
|
||||
%%% - ошибку 401 без токена
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_calendar_view_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Calendar View Tests ==="),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
|
||||
% Создаём календарь
|
||||
CalId = api_test_runner:create_calendar(Token, #{title => <<"ViewCal">>}),
|
||||
|
||||
test_get_calendar_view(Token, CalId),
|
||||
test_get_calendar_view_unauthorized(CalId),
|
||||
|
||||
ct:pal("=== All user calendar view tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешный запрос HTML-представления: 200 OK, тип text/html.
|
||||
-spec test_get_calendar_view(binary(), binary()) -> ok.
|
||||
test_get_calendar_view(Token, CalId) ->
|
||||
ct:pal(" TEST: Get calendar HTML view"),
|
||||
Path = <<"/v1/calendars/", CalId/binary, "/view?month=2026-06">>,
|
||||
Resp = api_test_runner:client_request(get, Path, Token),
|
||||
{ok, 200, Headers, Body} = Resp,
|
||||
?assert(lists:keymember("content-type", 1, Headers)),
|
||||
{"content-type", CT} = lists:keyfind("content-type", 1, Headers),
|
||||
?assert(string:str(CT, "text/html") > 0),
|
||||
% Body может быть строкой или binary, приводим к binary и проверяем непустоту
|
||||
BodyBin = iolist_to_binary(Body),
|
||||
?assert(byte_size(BodyBin) > 0),
|
||||
ct:pal(" OK: got HTML of ~p bytes", [byte_size(BodyBin)]).
|
||||
|
||||
%% @doc Запрос без токена: 401 Unauthorized.
|
||||
-spec test_get_calendar_view_unauthorized(binary()) -> ok.
|
||||
test_get_calendar_view_unauthorized(CalId) ->
|
||||
ct:pal(" TEST: Get calendar view without token"),
|
||||
Path = <<"/v1/calendars/", CalId/binary, "/view?month=2026-06">>,
|
||||
Resp = api_test_runner:client_request(get, Path, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
51
test/api/users/user_calendars_tests.erl
Normal file
51
test/api/users/user_calendars_tests.erl
Normal file
@@ -0,0 +1,51 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для управления календарями.
|
||||
%%% Покрывает POST /v1/calendars (создание) и GET /v1/calendars (список).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_calendars_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Calendars Tests ==="),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
|
||||
% Создаём один календарь для тестов
|
||||
#{<<"id">> := CalId} = api_test_runner:client_post(<<"/v1/calendars">>, Token,
|
||||
#{title => <<"TestCal">>, type => <<"personal">>}),
|
||||
|
||||
test_create_calendar(Token),
|
||||
test_list_calendars(Token),
|
||||
test_list_calendars_unauthorized(),
|
||||
|
||||
ct:pal("=== All user calendars tests passed ==="),
|
||||
ok.
|
||||
|
||||
test_create_calendar(Token) ->
|
||||
ct:pal(" TEST: Create a new calendar"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/calendars">>, Token,
|
||||
jsx:encode(#{title => <<"NewCal">>, type => <<"personal">>})),
|
||||
{ok, 201, _, Body} = Resp,
|
||||
#{<<"id">> := Id, <<"title">> := Title} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assert(is_binary(Id)),
|
||||
?assertEqual(<<"NewCal">>, Title),
|
||||
ct:pal(" OK: created calendar ~s", [Id]).
|
||||
|
||||
test_list_calendars(Token) ->
|
||||
ct:pal(" TEST: List user calendars"),
|
||||
Calendars = api_test_runner:client_get(<<"/v1/calendars">>, Token),
|
||||
?assert(is_list(Calendars)),
|
||||
?assert(length(Calendars) >= 1),
|
||||
First = hd(Calendars),
|
||||
?assert(maps:is_key(<<"id">>, First)),
|
||||
?assert(maps:is_key(<<"title">>, First)),
|
||||
ct:pal(" OK: ~p calendars found", [length(Calendars)]).
|
||||
|
||||
test_list_calendars_unauthorized() ->
|
||||
ct:pal(" TEST: List calendars without token (401)"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/calendars">>, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
114
test/api/users/user_event_by_id_tests.erl
Normal file
114
test/api/users/user_event_by_id_tests.erl
Normal file
@@ -0,0 +1,114 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для работы с конкретным событием.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/events/:id
|
||||
%%% PUT /v1/events/:id
|
||||
%%% DELETE /v1/events/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение события по ID
|
||||
%%% - обновление события (владельцем)
|
||||
%%% - мягкое удаление события (статус становится deleted)
|
||||
%%% - ошибку 403 при попытке изменения чужого события
|
||||
%%% - ошибку 404 для несуществующего события
|
||||
%%% - ошибку 401 без токена
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_event_by_id_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Event By ID Tests ==="),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
OtherToken = api_test_runner:register_and_login(
|
||||
api_test_runner:unique_email(<<"other">>), <<"pass">>),
|
||||
|
||||
% Создаём календарь и событие
|
||||
CalId = api_test_runner:create_calendar(Token, #{title => <<"EvtById">>}),
|
||||
#{<<"id">> := EventId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, Token,
|
||||
#{title => <<"Test Event">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60}),
|
||||
|
||||
test_get_event(Token, EventId),
|
||||
test_update_event(Token, EventId),
|
||||
test_update_event_forbidden(OtherToken, EventId),
|
||||
test_delete_event(Token, EventId),
|
||||
test_get_event_not_found(Token),
|
||||
test_get_event_unauthorized(EventId),
|
||||
|
||||
ct:pal("=== All user event by id tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/events/:id – получение события.
|
||||
-spec test_get_event(binary(), binary()) -> ok.
|
||||
test_get_event(Token, EventId) ->
|
||||
ct:pal(" TEST: Get event by ID"),
|
||||
Path = <<"/v1/events/", EventId/binary>>,
|
||||
Event = api_test_runner:client_get(Path, Token),
|
||||
?assertEqual(EventId, maps:get(<<"id">>, Event)),
|
||||
?assertEqual(<<"Test Event">>, maps:get(<<"title">>, Event)),
|
||||
ct:pal(" OK: got event ~s", [EventId]).
|
||||
|
||||
%% @doc PUT /v1/events/:id – обновление события.
|
||||
-spec test_update_event(binary(), binary()) -> ok.
|
||||
test_update_event(Token, EventId) ->
|
||||
ct:pal(" TEST: Update event"),
|
||||
Path = <<"/v1/events/", EventId/binary>>,
|
||||
Updated = api_test_runner:client_put(Path, Token,
|
||||
#{title => <<"Updated Event">>, description => <<"New desc">>}),
|
||||
?assertEqual(<<"Updated Event">>, maps:get(<<"title">>, Updated)),
|
||||
?assertEqual(<<"New desc">>, maps:get(<<"description">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc PUT /v1/events/:id – попытка обновления чужим пользователем (403).
|
||||
-spec test_update_event_forbidden(binary(), binary()) -> ok.
|
||||
test_update_event_forbidden(OtherToken, EventId) ->
|
||||
ct:pal(" TEST: Update event by non-owner"),
|
||||
Path = <<"/v1/events/", EventId/binary>>,
|
||||
Resp = api_test_runner:client_request(put, Path, OtherToken,
|
||||
jsx:encode(#{title => <<"fail">>})),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
%% @doc DELETE /v1/events/:id – мягкое удаление события.
|
||||
-spec test_delete_event(binary(), binary()) -> ok.
|
||||
test_delete_event(Token, EventId) ->
|
||||
ct:pal(" TEST: Soft-delete event"),
|
||||
Path = <<"/v1/events/", EventId/binary>>,
|
||||
% Удаляем событие
|
||||
{ok, 200, _, _} = api_test_runner:client_request(delete, Path, Token),
|
||||
% Проверяем, что событие доступно, но его статус = deleted
|
||||
Event = api_test_runner:client_get(Path, Token),
|
||||
?assertEqual(<<"deleted">>, maps:get(<<"status">>, Event)),
|
||||
ct:pal(" OK: event soft-deleted").
|
||||
|
||||
%% @doc GET /v1/events/:id – несуществующее событие (404).
|
||||
-spec test_get_event_not_found(binary()) -> ok.
|
||||
test_get_event_not_found(Token) ->
|
||||
ct:pal(" TEST: Get non-existent event"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/events/fakeid">>, Token),
|
||||
?assertMatch({ok, 404, _, _}, Resp),
|
||||
ct:pal(" OK: got 404").
|
||||
|
||||
%% @doc GET /v1/events/:id – без токена (401).
|
||||
-spec test_get_event_unauthorized(binary()) -> ok.
|
||||
test_get_event_unauthorized(EventId) ->
|
||||
ct:pal(" TEST: Get event without token"),
|
||||
Path = <<"/v1/events/", EventId/binary>>,
|
||||
Resp = api_test_runner:client_request(get, Path, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
80
test/api/users/user_events_tests.erl
Normal file
80
test/api/users/user_events_tests.erl
Normal file
@@ -0,0 +1,80 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для событий.
|
||||
%%% Покрывает POST /v1/calendars/:id/events и GET /v1/events/:id/occurrences
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_events_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Events Tests ==="),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
|
||||
% Создаём календарь
|
||||
CalId = api_test_runner:create_calendar(Token, #{title => <<"EventsTest">>}),
|
||||
|
||||
% Тесты
|
||||
test_create_single_event(Token, CalId),
|
||||
test_list_events_with_dates(Token, CalId),
|
||||
test_create_recurring_event(Token, CalId),
|
||||
|
||||
ct:pal("=== All user events tests passed ==="),
|
||||
ok.
|
||||
|
||||
%% @doc POST /v1/calendars/:calendar_id/events – одиночное событие.
|
||||
test_create_single_event(Token, CalId) ->
|
||||
ct:pal(" TEST: Create single event"),
|
||||
Path = <<"/v1/calendars/", CalId/binary, "/events">>,
|
||||
Body = jsx:encode(#{
|
||||
title => <<"Single Event">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60
|
||||
}),
|
||||
Resp = api_test_runner:client_request(post, Path, Token, Body),
|
||||
{ok, 201, _, RespBody} = Resp,
|
||||
#{<<"id">> := EventId, <<"title">> := Title} = jsx:decode(list_to_binary(RespBody), [return_maps]),
|
||||
?assert(is_binary(EventId)),
|
||||
?assertEqual(<<"Single Event">>, Title),
|
||||
ct:pal(" OK: created event ~s", [EventId]).
|
||||
|
||||
%% @doc GET /v1/calendars/:calendar_id/events?from=...&to=... – список с фильтром.
|
||||
test_list_events_with_dates(Token, CalId) ->
|
||||
ct:pal(" TEST: List events with date range"),
|
||||
Path = <<"/v1/calendars/", CalId/binary, "/events?from=2026-05-01T00:00:00Z&to=2026-07-01T00:00:00Z">>,
|
||||
Events = api_test_runner:client_get(Path, Token),
|
||||
?assert(is_list(Events)),
|
||||
?assert(length(Events) >= 1),
|
||||
First = hd(Events),
|
||||
?assert(maps:is_key(<<"id">>, First)),
|
||||
?assert(maps:is_key(<<"title">>, First)),
|
||||
ct:pal(" OK: ~p events found", [length(Events)]).
|
||||
|
||||
%% @doc POST /v1/calendars/:calendar_id/events – повторяющееся событие и проверка вхождений.
|
||||
test_create_recurring_event(Token, CalId) ->
|
||||
ct:pal(" TEST: Create recurring event and check occurrences"),
|
||||
Path = <<"/v1/calendars/", CalId/binary, "/events">>,
|
||||
Body = jsx:encode(#{
|
||||
title => <<"Weekly Meeting">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60,
|
||||
recurrence => #{
|
||||
freq => <<"WEEKLY">>,
|
||||
interval => 1
|
||||
}
|
||||
}),
|
||||
Resp = api_test_runner:client_request(post, Path, Token, Body),
|
||||
{ok, 201, _, RespBody} = Resp,
|
||||
#{<<"id">> := RecurringId} = jsx:decode(list_to_binary(RespBody), [return_maps]),
|
||||
ct:pal(" Created recurring event ~s", [RecurringId]),
|
||||
|
||||
% Запрашиваем вхождения на месяц
|
||||
OccPath = <<"/v1/events/", RecurringId/binary, "/occurrences?from=2026-06-01T00:00:00Z&to=2026-06-30T00:00:00Z">>,
|
||||
Occs = api_test_runner:client_get(OccPath, Token),
|
||||
?assert(is_list(Occs)),
|
||||
?assert(length(Occs) >= 4), % минимум 4 недели в июне
|
||||
FirstOcc = hd(Occs),
|
||||
?assert(maps:is_key(<<"start_time">>, FirstOcc)),
|
||||
ct:pal(" OK: ~p occurrences found", [length(Occs)]).
|
||||
90
test/api/users/user_login_tests.erl
Normal file
90
test/api/users/user_login_tests.erl
Normal file
@@ -0,0 +1,90 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для входа пользователей.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% POST /v1/login
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - успешный вход с правильными email и паролем
|
||||
%%% - ошибку при неверном пароле
|
||||
%%% - ошибку при несуществующем email
|
||||
%%% - ошибку при отсутствии обязательных полей
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_login_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Client Login Tests ==="),
|
||||
Email = api_test_runner:unique_email(<<"login">>),
|
||||
Password = <<"StrongPass1!">>,
|
||||
|
||||
% Создаём пользователя для тестов входа
|
||||
api_test_runner:register_and_login(Email, Password),
|
||||
|
||||
test_successful_login(Email, Password),
|
||||
test_wrong_password(Email),
|
||||
test_nonexistent_email(),
|
||||
test_missing_fields(),
|
||||
|
||||
ct:pal("=== All client login tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешный вход: 200 OK, возвращает токен и данные пользователя.
|
||||
-spec test_successful_login(binary(), binary()) -> ok.
|
||||
test_successful_login(Email, Password) ->
|
||||
ct:pal(" TEST: Successful login"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
|
||||
jsx:encode(#{email => Email, password => Password})),
|
||||
{ok, 200, _, Body} = Resp,
|
||||
#{<<"token">> := Token, <<"user">> := User} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assert(is_binary(Token)),
|
||||
?assertEqual(Email, maps:get(<<"email">>, User)),
|
||||
ct:pal(" OK: user ~s logged in", [maps:get(<<"id">>, User)]).
|
||||
|
||||
%% @doc Неверный пароль: 401 Unauthorized.
|
||||
-spec test_wrong_password(binary()) -> ok.
|
||||
test_wrong_password(Email) ->
|
||||
ct:pal(" TEST: Wrong password"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
|
||||
jsx:encode(#{email => Email, password => <<"WrongPass1">>})),
|
||||
{ok, 401, _, Body} = Resp,
|
||||
#{<<"error">> := Msg} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assertEqual(<<"Invalid credentials">>, Msg),
|
||||
ct:pal(" OK: got 401 unauthorized").
|
||||
|
||||
%% @doc Несуществующий email: 401 Unauthorized.
|
||||
-spec test_nonexistent_email() -> ok.
|
||||
test_nonexistent_email() ->
|
||||
ct:pal(" TEST: Nonexistent email"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
|
||||
jsx:encode(#{email => <<"no@such.user">>, password => <<"Anything1">>})),
|
||||
{ok, 401, _, Body} = Resp,
|
||||
#{<<"error">> := Msg} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assertEqual(<<"Invalid credentials">>, Msg),
|
||||
ct:pal(" OK: got 401 unauthorized").
|
||||
|
||||
%% @doc Отсутствие обязательных полей: 400 Bad Request.
|
||||
-spec test_missing_fields() -> ok.
|
||||
test_missing_fields() ->
|
||||
ct:pal(" TEST: Missing required fields"),
|
||||
Resp1 = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
|
||||
jsx:encode(#{email => <<"a@b.com">>})),
|
||||
?assertMatch({ok, 400, _, _}, Resp1),
|
||||
|
||||
Resp2 = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
|
||||
jsx:encode(#{password => <<"NoEmail1">>})),
|
||||
?assertMatch({ok, 400, _, _}, Resp2),
|
||||
|
||||
ct:pal(" OK: 400 on missing fields").
|
||||
55
test/api/users/user_me_tests.erl
Normal file
55
test/api/users/user_me_tests.erl
Normal file
@@ -0,0 +1,55 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для получения профиля текущего пользователя.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/user/me
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - успешное получение профиля с валидным токеном
|
||||
%%% - ошибку 401 при отсутствии токена
|
||||
%%% - наличие ключевых полей в ответе
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_me_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Profile (me) Tests ==="),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
|
||||
test_get_me_success(Token),
|
||||
test_get_me_unauthorized(),
|
||||
|
||||
ct:pal("=== All user me tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешное получение профиля: 200 OK, возвращает данные пользователя.
|
||||
-spec test_get_me_success(binary()) -> ok.
|
||||
test_get_me_success(Token) ->
|
||||
ct:pal(" TEST: Get current user profile"),
|
||||
User = api_test_runner:client_get(<<"/v1/user/me">>, Token),
|
||||
?assert(is_map(User)),
|
||||
?assert(maps:is_key(<<"id">>, User)),
|
||||
?assert(maps:is_key(<<"email">>, User)),
|
||||
?assert(maps:is_key(<<"role">>, User)),
|
||||
?assert(maps:is_key(<<"status">>, User)),
|
||||
ct:pal(" OK: got profile for ~s", [maps:get(<<"email">>, User)]).
|
||||
|
||||
%% @doc Отсутствие токена: 401 Unauthorized.
|
||||
-spec test_get_me_unauthorized() -> ok.
|
||||
test_get_me_unauthorized() ->
|
||||
ct:pal(" TEST: Get profile without token"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/user/me">>, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401 unauthorized").
|
||||
69
test/api/users/user_my_bookings_tests.erl
Normal file
69
test/api/users/user_my_bookings_tests.erl
Normal file
@@ -0,0 +1,69 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для получения своих бронирований.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/user/bookings
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение списка бронирований текущего пользователя
|
||||
%%% - что бронирование, созданное пользователем, присутствует в ответе
|
||||
%%% - ошибку 401 при отсутствии токена
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_my_bookings_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User My Bookings Tests ==="),
|
||||
OwnerToken = api_test_runner:get_user_token(),
|
||||
ParticipantEmail = api_test_runner:unique_email(<<"mybooker">>),
|
||||
ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"pass">>),
|
||||
|
||||
% Создаём календарь и событие
|
||||
CalId = api_test_runner:create_calendar(OwnerToken, #{title => <<"MyBookTest">>}),
|
||||
#{<<"id">> := EventId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken,
|
||||
#{title => <<"Event for my booking">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60}),
|
||||
|
||||
% Бронируем событие от имени участника и подтверждаем
|
||||
#{<<"id">> := BookingId} = api_test_runner:client_post(
|
||||
<<"/v1/events/", EventId/binary, "/bookings">>, ParticipantToken, #{}),
|
||||
api_test_runner:client_put(<<"/v1/bookings/", BookingId/binary>>, OwnerToken,
|
||||
#{action => <<"confirm">>}),
|
||||
|
||||
test_get_my_bookings(ParticipantToken, BookingId),
|
||||
test_get_my_bookings_unauthorized(),
|
||||
|
||||
ct:pal("=== All user my bookings tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешное получение своих бронирований: 200 OK, содержит бронирование.
|
||||
-spec test_get_my_bookings(binary(), binary()) -> ok.
|
||||
test_get_my_bookings(Token, ExpectedBookingId) ->
|
||||
ct:pal(" TEST: Get my bookings"),
|
||||
Bookings = api_test_runner:client_get(<<"/v1/user/bookings">>, Token),
|
||||
?assert(is_list(Bookings)),
|
||||
?assert(length(Bookings) >= 1),
|
||||
?assert(lists:any(fun(B) -> maps:get(<<"id">>, B) =:= ExpectedBookingId end, Bookings)),
|
||||
ct:pal(" OK: booking ~s found in list", [ExpectedBookingId]).
|
||||
|
||||
%% @doc Запрос без токена: 401 Unauthorized.
|
||||
-spec test_get_my_bookings_unauthorized() -> ok.
|
||||
test_get_my_bookings_unauthorized() ->
|
||||
ct:pal(" TEST: Get my bookings without token"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/user/bookings">>, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
75
test/api/users/user_my_reviews_tests.erl
Normal file
75
test/api/users/user_my_reviews_tests.erl
Normal file
@@ -0,0 +1,75 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для получения своих отзывов.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/user/reviews
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение списка отзывов текущего пользователя
|
||||
%%% - наличие созданного отзыва в ответе
|
||||
%%% - ошибку 401 при отсутствии токена
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_my_reviews_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User My Reviews Tests ==="),
|
||||
OwnerToken = api_test_runner:get_user_token(),
|
||||
ParticipantEmail = api_test_runner:unique_email(<<"myreviewer">>),
|
||||
ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"pass">>),
|
||||
|
||||
% Создаём календарь и событие
|
||||
CalId = api_test_runner:create_calendar(OwnerToken, #{title => <<"MyRevTest">>}),
|
||||
#{<<"id">> := EventId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken,
|
||||
#{title => <<"Event for my review">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60}),
|
||||
|
||||
% Бронируем, подтверждаем, оставляем отзыв
|
||||
#{<<"id">> := BookingId} = api_test_runner:client_post(
|
||||
<<"/v1/events/", EventId/binary, "/bookings">>, ParticipantToken, #{}),
|
||||
api_test_runner:client_put(<<"/v1/bookings/", BookingId/binary>>, OwnerToken,
|
||||
#{action => <<"confirm">>}),
|
||||
#{<<"id">> := ReviewId} = api_test_runner:client_post(
|
||||
<<"/v1/reviews">>, ParticipantToken,
|
||||
#{target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 4,
|
||||
comment => <<"Nice event!">>}),
|
||||
|
||||
test_get_my_reviews(ParticipantToken, ReviewId),
|
||||
test_get_my_reviews_unauthorized(),
|
||||
|
||||
ct:pal("=== All user my reviews tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешное получение своих отзывов: 200 OK, содержит отзыв.
|
||||
-spec test_get_my_reviews(binary(), binary()) -> ok.
|
||||
test_get_my_reviews(Token, ExpectedReviewId) ->
|
||||
ct:pal(" TEST: Get my reviews"),
|
||||
Reviews = api_test_runner:client_get(<<"/v1/user/reviews">>, Token),
|
||||
?assert(is_list(Reviews)),
|
||||
?assert(length(Reviews) >= 1),
|
||||
?assert(lists:any(fun(R) -> maps:get(<<"id">>, R) =:= ExpectedReviewId end, Reviews)),
|
||||
ct:pal(" OK: review ~s found in list", [ExpectedReviewId]).
|
||||
|
||||
%% @doc Запрос без токена: 401 Unauthorized.
|
||||
-spec test_get_my_reviews_unauthorized() -> ok.
|
||||
test_get_my_reviews_unauthorized() ->
|
||||
ct:pal(" TEST: Get my reviews without token"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/user/reviews">>, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
99
test/api/users/user_occurrence_cancel_tests.erl
Normal file
99
test/api/users/user_occurrence_cancel_tests.erl
Normal file
@@ -0,0 +1,99 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для отмены вхождения повторяющегося события.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% DELETE /v1/events/:id/occurrences/:start_time
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - успешную отмену конкретного вхождения
|
||||
%%% - ошибку 400 для не-recurring события
|
||||
%%% - ошибку 403 при попытке отмены чужим пользователем
|
||||
%%% - ошибку 401 без токена
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_occurrence_cancel_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Occurrence Cancel Tests ==="),
|
||||
OwnerToken = api_test_runner:get_user_token(),
|
||||
OtherToken = api_test_runner:register_and_login(
|
||||
api_test_runner:unique_email(<<"other">>), <<"pass">>),
|
||||
|
||||
% Создаём календарь и повторяющееся событие
|
||||
CalId = api_test_runner:create_calendar(OwnerToken, #{title => <<"OccCancel">>}),
|
||||
#{<<"id">> := RecurringId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken,
|
||||
#{title => <<"Weekly Standup">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 30,
|
||||
recurrence => #{freq => <<"WEEKLY">>, interval => 1}}),
|
||||
|
||||
% Получаем вхождения (ответ – список карт)
|
||||
OccPath = <<"/v1/events/", RecurringId/binary, "/occurrences?from=2026-06-01T00:00:00Z&to=2026-06-30T00:00:00Z">>,
|
||||
Occurrences = api_test_runner:client_get(OccPath, OwnerToken),
|
||||
?assert(is_list(Occurrences)),
|
||||
?assert(length(Occurrences) >= 1),
|
||||
#{<<"start_time">> := FirstStart} = hd(Occurrences),
|
||||
|
||||
test_cancel_occurrence(OwnerToken, RecurringId, FirstStart),
|
||||
test_cancel_occurrence_on_single_event(OwnerToken, CalId),
|
||||
test_cancel_occurrence_forbidden(OtherToken, RecurringId, FirstStart),
|
||||
test_cancel_occurrence_unauthorized(RecurringId, FirstStart),
|
||||
|
||||
ct:pal("=== All user occurrence cancel tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешная отмена вхождения: 200 OK.
|
||||
-spec test_cancel_occurrence(binary(), binary(), binary()) -> ok.
|
||||
test_cancel_occurrence(Token, EventId, StartTime) ->
|
||||
ct:pal(" TEST: Cancel occurrence"),
|
||||
Path = <<"/v1/events/", EventId/binary, "/occurrences/", StartTime/binary>>,
|
||||
Resp = api_test_runner:client_request(delete, Path, Token),
|
||||
{ok, 200, _, Body} = Resp,
|
||||
#{<<"status">> := Status} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assertEqual(<<"cancelled">>, Status),
|
||||
ct:pal(" OK: occurrence cancelled").
|
||||
|
||||
%% @doc Попытка отменить вхождение для одиночного события: 400.
|
||||
-spec test_cancel_occurrence_on_single_event(binary(), binary()) -> ok.
|
||||
test_cancel_occurrence_on_single_event(Token, CalId) ->
|
||||
ct:pal(" TEST: Cancel occurrence on non-recurring event"),
|
||||
#{<<"id">> := SingleId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, Token,
|
||||
#{title => <<"Single">>,
|
||||
start_time => <<"2026-06-02T10:00:00Z">>,
|
||||
duration => 30}),
|
||||
Path = <<"/v1/events/", SingleId/binary, "/occurrences/2026-06-02T10:00:00Z">>,
|
||||
Resp = api_test_runner:client_request(delete, Path, Token),
|
||||
?assertMatch({ok, 400, _, _}, Resp),
|
||||
ct:pal(" OK: got 400").
|
||||
|
||||
%% @doc Попытка отмены чужим пользователем: 403.
|
||||
-spec test_cancel_occurrence_forbidden(binary(), binary(), binary()) -> ok.
|
||||
test_cancel_occurrence_forbidden(OtherToken, EventId, StartTime) ->
|
||||
ct:pal(" TEST: Cancel occurrence by non-owner"),
|
||||
Path = <<"/v1/events/", EventId/binary, "/occurrences/", StartTime/binary>>,
|
||||
Resp = api_test_runner:client_request(delete, Path, OtherToken),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
%% @doc Запрос без токена: 401.
|
||||
-spec test_cancel_occurrence_unauthorized(binary(), binary()) -> ok.
|
||||
test_cancel_occurrence_unauthorized(EventId, StartTime) ->
|
||||
ct:pal(" TEST: Cancel occurrence without token"),
|
||||
Path = <<"/v1/events/", EventId/binary, "/occurrences/", StartTime/binary>>,
|
||||
Resp = api_test_runner:client_request(delete, Path, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
88
test/api/users/user_refresh_tests.erl
Normal file
88
test/api/users/user_refresh_tests.erl
Normal file
@@ -0,0 +1,88 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для обновления токена.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% POST /v1/refresh
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - успешное обновление токена по валидному refresh_token
|
||||
%%% - ошибку 401 при невалидном refresh_token
|
||||
%%% - ошибку 400 при отсутствии refresh_token в теле запроса
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_refresh_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Refresh Tests ==="),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
|
||||
% Получаем refresh_token через логин (или регистрацию)
|
||||
Email = api_test_runner:unique_email(<<"refresh">>),
|
||||
Password = <<"StrongPass1!">>,
|
||||
#{<<"refresh_token">> := RefreshToken} = register_and_get_refresh(Email, Password),
|
||||
|
||||
test_successful_refresh(RefreshToken),
|
||||
test_invalid_refresh_token(),
|
||||
test_missing_refresh_token(),
|
||||
|
||||
ct:pal("=== All user refresh tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешное обновление: 200 OK, возвращает новую пару токенов.
|
||||
-spec test_successful_refresh(binary()) -> ok.
|
||||
test_successful_refresh(RefreshToken) ->
|
||||
ct:pal(" TEST: Successful token refresh"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/refresh">>, <<>>,
|
||||
jsx:encode(#{refresh_token => RefreshToken})),
|
||||
{ok, 200, _, Body} = Resp,
|
||||
#{<<"token">> := NewToken, <<"refresh_token">> := NewRefresh} =
|
||||
jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assert(is_binary(NewToken)),
|
||||
?assert(is_binary(NewRefresh)),
|
||||
?assertNotEqual(RefreshToken, NewRefresh),
|
||||
ct:pal(" OK: got new token pair").
|
||||
|
||||
%% @doc Невалидный refresh_token: 401 Unauthorized.
|
||||
-spec test_invalid_refresh_token() -> ok.
|
||||
test_invalid_refresh_token() ->
|
||||
ct:pal(" TEST: Invalid refresh token"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/refresh">>, <<>>,
|
||||
jsx:encode(#{refresh_token => <<"invalid_token_here">>})),
|
||||
{ok, 401, _, _} = Resp,
|
||||
ct:pal(" OK: got 401").
|
||||
|
||||
%% @doc Отсутствие refresh_token в теле: 400 Bad Request.
|
||||
-spec test_missing_refresh_token() -> ok.
|
||||
test_missing_refresh_token() ->
|
||||
ct:pal(" TEST: Missing refresh_token field"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/refresh">>, <<>>,
|
||||
jsx:encode(#{})),
|
||||
?assertMatch({ok, 400, _, _}, Resp),
|
||||
ct:pal(" OK: got 400").
|
||||
|
||||
%%%===================================================================
|
||||
%%% Вспомогательные функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Регистрирует пользователя, выполняет логин и возвращает refresh_token.
|
||||
-spec register_and_get_refresh(binary(), binary()) -> map().
|
||||
register_and_get_refresh(Email, Password) ->
|
||||
% Регистрируем
|
||||
_ = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
|
||||
jsx:encode(#{email => Email, password => Password})),
|
||||
% Логинимся, чтобы получить refresh_token
|
||||
{ok, 200, _, Body} = api_test_runner:client_request(post, <<"/v1/login">>, <<>>,
|
||||
jsx:encode(#{email => Email, password => Password})),
|
||||
jsx:decode(list_to_binary(Body), [return_maps]).
|
||||
76
test/api/users/user_register_tests.erl
Normal file
76
test/api/users/user_register_tests.erl
Normal file
@@ -0,0 +1,76 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для регистрации пользователей.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% POST /v1/register
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - успешную регистрацию нового пользователя
|
||||
%%% - возврат JWT токена и данных пользователя
|
||||
%%% - ошибку при повторной регистрации с тем же email
|
||||
%%% - ошибку при отсутствии обязательных полей
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_register_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== Client Register Tests ==="),
|
||||
Email = api_test_runner:unique_email(<<"register">>),
|
||||
Password = <<"StrongPass1!">>,
|
||||
|
||||
test_successful_register(Email, Password),
|
||||
test_duplicate_register(Email, Password),
|
||||
test_missing_fields(),
|
||||
|
||||
ct:pal("=== All client register tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешная регистрация: 201 Created, возвращает токен и пользователя.
|
||||
-spec test_successful_register(binary(), binary()) -> ok.
|
||||
test_successful_register(Email, Password) ->
|
||||
ct:pal(" TEST: Successful registration"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
|
||||
jsx:encode(#{email => Email, password => Password})),
|
||||
{ok, 201, _, Body} = Resp,
|
||||
#{<<"token">> := Token, <<"user">> := User} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assert(is_binary(Token)),
|
||||
?assert(maps:is_key(<<"id">>, User)),
|
||||
?assertEqual(Email, maps:get(<<"email">>, User)),
|
||||
ct:pal(" OK: user ~s created", [maps:get(<<"id">>, User)]).
|
||||
|
||||
%% @doc Повторная регистрация с тем же email: 409 Conflict.
|
||||
-spec test_duplicate_register(binary(), binary()) -> ok.
|
||||
test_duplicate_register(Email, Password) ->
|
||||
ct:pal(" TEST: Duplicate registration"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
|
||||
jsx:encode(#{email => Email, password => Password})),
|
||||
{ok, 409, _, Body} = Resp,
|
||||
#{<<"error">> := ErrorMsg} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assertEqual(<<"Email already exists">>, ErrorMsg),
|
||||
ct:pal(" OK: got 409 conflict").
|
||||
|
||||
%% @doc Отсутствие обязательных полей: 400 Bad Request.
|
||||
-spec test_missing_fields() -> ok.
|
||||
test_missing_fields() ->
|
||||
ct:pal(" TEST: Missing required fields"),
|
||||
Resp1 = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
|
||||
jsx:encode(#{email => <<"missing@test.local">>})),
|
||||
?assertMatch({ok, 400, _, _}, Resp1),
|
||||
|
||||
Resp2 = api_test_runner:client_request(post, <<"/v1/register">>, <<>>,
|
||||
jsx:encode(#{password => <<"NoEmail1">>})),
|
||||
?assertMatch({ok, 400, _, _}, Resp2),
|
||||
|
||||
ct:pal(" OK: 400 on missing fields").
|
||||
101
test/api/users/user_reports_tests.erl
Normal file
101
test/api/users/user_reports_tests.erl
Normal file
@@ -0,0 +1,101 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для работы с жалобами.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% POST /v1/reports
|
||||
%%% GET /v1/reports (требует прав администратора)
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - успешное создание жалобы (201 Created)
|
||||
%%% - ошибку 400 при отсутствии обязательных полей
|
||||
%%% - ошибку 401 при создании без токена
|
||||
%%% - ошибку 403 при попытке получения списка обычным пользователем
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_reports_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Reports Tests ==="),
|
||||
UserToken = api_test_runner:get_user_token(),
|
||||
|
||||
% Создаём событие, на которое можно пожаловаться
|
||||
CalId = api_test_runner:create_calendar(UserToken, #{title => <<"ReportTest">>}),
|
||||
#{<<"id">> := EventId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, UserToken,
|
||||
#{title => <<"Event to report">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60}),
|
||||
|
||||
test_create_report(UserToken, EventId),
|
||||
test_create_report_missing_fields(UserToken),
|
||||
test_create_report_unauthorized(EventId),
|
||||
test_list_reports_forbidden(UserToken),
|
||||
|
||||
ct:pal("=== All user reports tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешное создание жалобы: 201 Created.
|
||||
-spec test_create_report(binary(), binary()) -> ok.
|
||||
test_create_report(Token, EventId) ->
|
||||
ct:pal(" TEST: Create a report"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/reports">>, Token,
|
||||
jsx:encode(#{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
reason => <<"Inappropriate content">>
|
||||
})),
|
||||
{ok, 201, _, Body} = Resp,
|
||||
#{<<"id">> := ReportId, <<"status">> := Status} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assert(is_binary(ReportId)),
|
||||
?assertEqual(<<"pending">>, Status),
|
||||
ct:pal(" OK: report ~s created", [ReportId]).
|
||||
|
||||
%% @doc Отсутствие обязательных полей: 400 Bad Request.
|
||||
-spec test_create_report_missing_fields(binary()) -> ok.
|
||||
test_create_report_missing_fields(Token) ->
|
||||
ct:pal(" TEST: Create report with missing fields"),
|
||||
Resp1 = api_test_runner:client_request(post, <<"/v1/reports">>, Token,
|
||||
jsx:encode(#{target_id => <<"id">>, reason => <<"text">>})),
|
||||
?assertMatch({ok, 400, _, _}, Resp1),
|
||||
|
||||
Resp2 = api_test_runner:client_request(post, <<"/v1/reports">>, Token,
|
||||
jsx:encode(#{target_type => <<"event">>, reason => <<"text">>})),
|
||||
?assertMatch({ok, 400, _, _}, Resp2),
|
||||
|
||||
Resp3 = api_test_runner:client_request(post, <<"/v1/reports">>, Token,
|
||||
jsx:encode(#{target_type => <<"event">>, target_id => <<"id">>})),
|
||||
?assertMatch({ok, 400, _, _}, Resp3),
|
||||
ct:pal(" OK: got 400").
|
||||
|
||||
%% @doc Создание жалобы без токена: 401 Unauthorized.
|
||||
-spec test_create_report_unauthorized(binary()) -> ok.
|
||||
test_create_report_unauthorized(EventId) ->
|
||||
ct:pal(" TEST: Create report without token"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/reports">>, <<>>,
|
||||
jsx:encode(#{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
reason => <<"test">>
|
||||
})),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
|
||||
%% @doc GET /v1/reports для обычного пользователя должен вернуть 403.
|
||||
-spec test_list_reports_forbidden(binary()) -> ok.
|
||||
test_list_reports_forbidden(Token) ->
|
||||
ct:pal(" TEST: List reports as regular user"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/reports">>, Token),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
126
test/api/users/user_review_by_id_tests.erl
Normal file
126
test/api/users/user_review_by_id_tests.erl
Normal file
@@ -0,0 +1,126 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для работы с конкретным отзывом.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/reviews/:id
|
||||
%%% PUT /v1/reviews/:id
|
||||
%%% DELETE /v1/reviews/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение отзыва по ID
|
||||
%%% - обновление отзыва (автором)
|
||||
%%% - удаление отзыва (автором)
|
||||
%%% - ошибку 403 при попытке изменения чужим пользователем
|
||||
%%% - ошибку 404 для несуществующего отзыва
|
||||
%%% - ошибку 401 без токена
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_review_by_id_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Review By ID Tests ==="),
|
||||
OwnerToken = api_test_runner:get_user_token(),
|
||||
ParticipantEmail = api_test_runner:unique_email(<<"reviewer">>),
|
||||
ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"pass">>),
|
||||
StrangerEmail = api_test_runner:unique_email(<<"stranger">>),
|
||||
StrangerToken = api_test_runner:register_and_login(StrangerEmail, <<"pass">>),
|
||||
|
||||
% Создаём календарь, событие, бронирование и отзыв
|
||||
CalId = api_test_runner:create_calendar(OwnerToken, #{title => <<"RevById">>}),
|
||||
#{<<"id">> := EventId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken,
|
||||
#{title => <<"Event for review">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60}),
|
||||
#{<<"id">> := BookingId} = api_test_runner:client_post(
|
||||
<<"/v1/events/", EventId/binary, "/bookings">>, ParticipantToken, #{}),
|
||||
api_test_runner:client_put(<<"/v1/bookings/", BookingId/binary>>, OwnerToken,
|
||||
#{action => <<"confirm">>}),
|
||||
#{<<"id">> := ReviewId} = api_test_runner:client_post(
|
||||
<<"/v1/reviews">>, ParticipantToken,
|
||||
#{target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 5,
|
||||
comment => <<"Excellent!">>}),
|
||||
|
||||
test_get_review(ParticipantToken, ReviewId),
|
||||
test_update_review(ParticipantToken, ReviewId),
|
||||
test_update_review_forbidden(StrangerToken, ReviewId),
|
||||
test_delete_review(ParticipantToken, ReviewId),
|
||||
test_get_review_not_found(ParticipantToken),
|
||||
test_get_review_unauthorized(ReviewId),
|
||||
|
||||
ct:pal("=== All user review by id tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/reviews/:id – получение отзыва.
|
||||
-spec test_get_review(binary(), binary()) -> ok.
|
||||
test_get_review(Token, ReviewId) ->
|
||||
ct:pal(" TEST: Get review by ID"),
|
||||
Path = <<"/v1/reviews/", ReviewId/binary>>,
|
||||
Review = api_test_runner:client_get(Path, Token),
|
||||
?assertEqual(ReviewId, maps:get(<<"id">>, Review)),
|
||||
?assertEqual(<<"Excellent!">>, maps:get(<<"comment">>, Review)),
|
||||
ct:pal(" OK: got review").
|
||||
|
||||
%% @doc PUT /v1/reviews/:id – обновление отзыва автором.
|
||||
-spec test_update_review(binary(), binary()) -> ok.
|
||||
test_update_review(Token, ReviewId) ->
|
||||
ct:pal(" TEST: Update review"),
|
||||
Path = <<"/v1/reviews/", ReviewId/binary>>,
|
||||
Updated = api_test_runner:client_put(Path, Token,
|
||||
#{comment => <<"Updated comment">>, rating => 4}),
|
||||
?assertEqual(<<"Updated comment">>, maps:get(<<"comment">>, Updated)),
|
||||
?assertEqual(4, maps:get(<<"rating">>, Updated)),
|
||||
ct:pal(" OK").
|
||||
|
||||
%% @doc PUT /v1/reviews/:id – попытка обновления чужим пользователем (403).
|
||||
-spec test_update_review_forbidden(binary(), binary()) -> ok.
|
||||
test_update_review_forbidden(StrangerToken, ReviewId) ->
|
||||
ct:pal(" TEST: Update review by non-author"),
|
||||
Path = <<"/v1/reviews/", ReviewId/binary>>,
|
||||
Resp = api_test_runner:client_request(put, Path, StrangerToken,
|
||||
jsx:encode(#{comment => <<"fail">>})),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
%% @doc DELETE /v1/reviews/:id – удаление отзыва автором.
|
||||
-spec test_delete_review(binary(), binary()) -> ok.
|
||||
test_delete_review(Token, ReviewId) ->
|
||||
ct:pal(" TEST: Delete review"),
|
||||
Path = <<"/v1/reviews/", ReviewId/binary>>,
|
||||
% Удаляем
|
||||
{ok, 200, _, _} = api_test_runner:client_request(delete, Path, Token),
|
||||
% Проверяем, что отзыв больше недоступен
|
||||
GetResp = api_test_runner:client_request(get, Path, Token),
|
||||
?assertMatch({ok, 404, _, _}, GetResp),
|
||||
ct:pal(" OK: review deleted").
|
||||
|
||||
%% @doc GET /v1/reviews/:id – несуществующий отзыв (404).
|
||||
-spec test_get_review_not_found(binary()) -> ok.
|
||||
test_get_review_not_found(Token) ->
|
||||
ct:pal(" TEST: Get non-existent review"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/reviews/fakeid">>, Token),
|
||||
?assertMatch({ok, 404, _, _}, Resp),
|
||||
ct:pal(" OK: got 404").
|
||||
|
||||
%% @doc GET /v1/reviews/:id – без токена (401).
|
||||
-spec test_get_review_unauthorized(binary()) -> ok.
|
||||
test_get_review_unauthorized(ReviewId) ->
|
||||
ct:pal(" TEST: Get review without token"),
|
||||
Path = <<"/v1/reviews/", ReviewId/binary>>,
|
||||
Resp = api_test_runner:client_request(get, Path, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
114
test/api/users/user_reviews_tests.erl
Normal file
114
test/api/users/user_reviews_tests.erl
Normal file
@@ -0,0 +1,114 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для отзывов.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% POST /v1/reviews
|
||||
%%% GET /v1/reviews
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - создание отзыва участником подтверждённого бронирования
|
||||
%%% - ошибку при повторном отзыве на ту же цель
|
||||
%%% - ошибку при отзыве без участия (403)
|
||||
%%% - получение списка отзывов для цели
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_reviews_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Reviews Tests ==="),
|
||||
OwnerToken = api_test_runner:get_user_token(),
|
||||
ParticipantEmail = api_test_runner:unique_email(<<"reviewer">>),
|
||||
ParticipantToken = api_test_runner:register_and_login(ParticipantEmail, <<"pass">>),
|
||||
StrangerEmail = api_test_runner:unique_email(<<"stranger">>),
|
||||
StrangerToken = api_test_runner:register_and_login(StrangerEmail, <<"pass">>),
|
||||
|
||||
% Создаём календарь и событие
|
||||
CalId = api_test_runner:create_calendar(OwnerToken, #{title => <<"ReviewTest">>}),
|
||||
#{<<"id">> := EventId} = api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, OwnerToken,
|
||||
#{title => <<"Event for review">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60}),
|
||||
|
||||
% Бронируем и подтверждаем участие
|
||||
#{<<"id">> := BookingId} = api_test_runner:client_post(
|
||||
<<"/v1/events/", EventId/binary, "/bookings">>, ParticipantToken, #{}),
|
||||
api_test_runner:client_put(<<"/v1/bookings/", BookingId/binary>>, OwnerToken,
|
||||
#{action => <<"confirm">>}),
|
||||
|
||||
test_create_review(ParticipantToken, EventId),
|
||||
test_duplicate_review(ParticipantToken, EventId),
|
||||
test_review_without_booking(StrangerToken, EventId),
|
||||
test_list_reviews(OwnerToken, EventId),
|
||||
|
||||
ct:pal("=== All user reviews tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешное создание отзыва: 201 Created.
|
||||
-spec test_create_review(binary(), binary()) -> ok.
|
||||
test_create_review(Token, EventId) ->
|
||||
ct:pal(" TEST: Create a review"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/reviews">>, Token,
|
||||
jsx:encode(#{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 5,
|
||||
comment => <<"Excellent!">>
|
||||
})),
|
||||
{ok, 201, _, Body} = Resp,
|
||||
#{<<"id">> := ReviewId, <<"status">> := Status} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assert(is_binary(ReviewId)),
|
||||
?assertEqual(<<"visible">>, Status),
|
||||
ct:pal(" OK: review ~s created", [ReviewId]).
|
||||
|
||||
%% @doc Повторный отзыв на ту же цель: 409 Conflict.
|
||||
-spec test_duplicate_review(binary(), binary()) -> ok.
|
||||
test_duplicate_review(Token, EventId) ->
|
||||
ct:pal(" TEST: Duplicate review"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/reviews">>, Token,
|
||||
jsx:encode(#{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 3,
|
||||
comment => <<"Second try">>
|
||||
})),
|
||||
{ok, 409, _, _} = Resp,
|
||||
ct:pal(" OK: got 409 conflict").
|
||||
|
||||
%% @doc Попытка оставить отзыв без бронирования: 403 Forbidden.
|
||||
-spec test_review_without_booking(binary(), binary()) -> ok.
|
||||
test_review_without_booking(StrangerToken, EventId) ->
|
||||
ct:pal(" TEST: Review without booking"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/reviews">>, StrangerToken,
|
||||
jsx:encode(#{
|
||||
target_type => <<"event">>,
|
||||
target_id => EventId,
|
||||
rating => 1,
|
||||
comment => <<"Not allowed">>
|
||||
})),
|
||||
{ok, 403, _, _} = Resp,
|
||||
ct:pal(" OK: got 403 forbidden").
|
||||
|
||||
%% @doc GET /v1/reviews?target_type=event&target_id=... – список отзывов.
|
||||
-spec test_list_reviews(binary(), binary()) -> ok.
|
||||
test_list_reviews(_, EventId) ->
|
||||
ct:pal(" TEST: List reviews for event"),
|
||||
Path = <<"/v1/reviews?target_type=event&target_id=", EventId/binary>>,
|
||||
Reviews = api_test_runner:client_get(Path, api_test_runner:get_user_token()),
|
||||
?assert(is_list(Reviews)),
|
||||
?assert(length(Reviews) >= 1),
|
||||
First = hd(Reviews),
|
||||
?assertEqual(<<"Excellent!">>, maps:get(<<"comment">>, First)),
|
||||
ct:pal(" OK: ~p reviews found", [length(Reviews)]).
|
||||
126
test/api/users/user_search_tests.erl
Normal file
126
test/api/users/user_search_tests.erl
Normal file
@@ -0,0 +1,126 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для поиска.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/search
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - полнотекстовый поиск по названиям событий
|
||||
%%% - поиск с фильтрацией по типу (event/calendar)
|
||||
%%% - поиск с фильтрацией по тегам
|
||||
%%% - поиск с фильтрацией по датам (from/to)
|
||||
%%% - геопоиск (lat, lon, radius)
|
||||
%%% - пагинацию результатов
|
||||
%%% - ошибку 401 без токена
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_search_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Search Tests ==="),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
|
||||
% Создаём тестовые данные: календарь и несколько событий с тегами
|
||||
CalId = api_test_runner:create_calendar(Token, #{title => <<"SearchTestCal">>}),
|
||||
api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, Token,
|
||||
#{title => <<"Python Workshop">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60,
|
||||
tags => [<<"python">>, <<"workshop">>]}),
|
||||
api_test_runner:client_post(
|
||||
<<"/v1/calendars/", CalId/binary, "/events">>, Token,
|
||||
#{title => <<"JavaScript Meetup">>,
|
||||
start_time => <<"2026-06-15T10:00:00Z">>,
|
||||
duration => 60,
|
||||
tags => [<<"javascript">>]}),
|
||||
|
||||
test_basic_search(Token),
|
||||
test_type_filter(Token),
|
||||
test_tag_filter(Token),
|
||||
test_date_filter(Token),
|
||||
test_geo_search(Token),
|
||||
test_search_pagination(Token),
|
||||
test_search_unauthorized(),
|
||||
|
||||
ct:pal("=== All user search tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Вспомогательная функция
|
||||
%%%===================================================================
|
||||
|
||||
%% @private Извлекает список событий из результата поиска.
|
||||
%% Ожидает ответ вида {"results": {"events": [...], "calendars": [...]}}.
|
||||
-spec extract_events(map()) -> list().
|
||||
extract_events(#{<<"results">> := #{<<"events">> := Events}}) -> Events.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
test_basic_search(Token) ->
|
||||
ct:pal(" TEST: Basic search"),
|
||||
Events = extract_events(
|
||||
api_test_runner:client_get(<<"/v1/search?q=Python">>, Token)),
|
||||
?assert(is_list(Events)),
|
||||
?assert(length(Events) >= 1),
|
||||
ct:pal(" OK: ~p events found", [length(Events)]).
|
||||
|
||||
test_type_filter(Token) ->
|
||||
ct:pal(" TEST: Search with type filter"),
|
||||
Events = extract_events(
|
||||
api_test_runner:client_get(<<"/v1/search?q=Python&type=event">>, Token)),
|
||||
?assert(is_list(Events)),
|
||||
?assert(length(Events) >= 1),
|
||||
ct:pal(" OK: ~p events", [length(Events)]).
|
||||
|
||||
test_tag_filter(Token) ->
|
||||
ct:pal(" TEST: Search with tag filter"),
|
||||
Events = extract_events(
|
||||
api_test_runner:client_get(<<"/v1/search?tags=python">>, Token)),
|
||||
?assert(is_list(Events)),
|
||||
?assert(length(Events) >= 1),
|
||||
ct:pal(" OK: ~p events", [length(Events)]).
|
||||
|
||||
test_date_filter(Token) ->
|
||||
ct:pal(" TEST: Search with date range"),
|
||||
Events = extract_events(
|
||||
api_test_runner:client_get(
|
||||
<<"/v1/search?from=2026-06-01T00:00:00Z&to=2026-06-15T23:59:59Z">>, Token)),
|
||||
?assert(is_list(Events)),
|
||||
?assert(length(Events) >= 1),
|
||||
ct:pal(" OK: ~p events", [length(Events)]).
|
||||
|
||||
test_geo_search(Token) ->
|
||||
ct:pal(" TEST: Geo search"),
|
||||
Events = extract_events(
|
||||
api_test_runner:client_get(
|
||||
<<"/v1/search?lat=55.75&lon=37.61&radius=1">>, Token)),
|
||||
?assert(is_list(Events)),
|
||||
?assert(length(Events) >= 0),
|
||||
ct:pal(" OK: ~p events", [length(Events)]).
|
||||
|
||||
test_search_pagination(Token) ->
|
||||
ct:pal(" TEST: Search pagination"),
|
||||
Events1 = extract_events(
|
||||
api_test_runner:client_get(<<"/v1/search?limit=1&offset=0">>, Token)),
|
||||
?assertEqual(1, length(Events1)),
|
||||
Events2 = extract_events(
|
||||
api_test_runner:client_get(<<"/v1/search?limit=1&offset=1">>, Token)),
|
||||
?assert(length(Events2) >= 0),
|
||||
ct:pal(" OK").
|
||||
|
||||
test_search_unauthorized() ->
|
||||
ct:pal(" TEST: Search without token"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/search?q=test">>, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
78
test/api/users/user_subscription_tests.erl
Normal file
78
test/api/users/user_subscription_tests.erl
Normal file
@@ -0,0 +1,78 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для управления подпиской.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% GET /v1/subscription
|
||||
%%% POST /v1/subscription
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - получение информации о подписке (200)
|
||||
%%% - активацию пробного периода (start_trial, 201)
|
||||
%%% - ошибку 409 при повторной активации
|
||||
%%% - ошибку 401 без токена
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_subscription_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Subscription Tests ==="),
|
||||
% Создаём уникального пользователя – у него точно нет активной подписки
|
||||
Email = api_test_runner:unique_email(<<"subtest">>),
|
||||
Token = api_test_runner:register_and_login(Email, <<"StrongPass1!">>),
|
||||
|
||||
test_get_subscription(Token),
|
||||
test_start_trial(Token),
|
||||
test_start_trial_duplicate(Token),
|
||||
test_get_subscription_unauthorized(),
|
||||
|
||||
ct:pal("=== All user subscription tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc GET /v1/subscription – получение подписки (может быть пустой).
|
||||
-spec test_get_subscription(binary()) -> ok.
|
||||
test_get_subscription(Token) ->
|
||||
ct:pal(" TEST: Get subscription"),
|
||||
Sub = api_test_runner:client_get(<<"/v1/subscription">>, Token),
|
||||
?assert(is_map(Sub)),
|
||||
ct:pal(" OK: subscription info received").
|
||||
|
||||
%% @doc POST /v1/subscription – успешная активация пробного периода.
|
||||
-spec test_start_trial(binary()) -> ok.
|
||||
test_start_trial(Token) ->
|
||||
ct:pal(" TEST: Start trial"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/subscription">>, Token,
|
||||
jsx:encode(#{action => <<"start_trial">>})),
|
||||
{ok, 201, _, Body} = Resp,
|
||||
#{<<"status">> := Status, <<"plan">> := Plan} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assertEqual(<<"active">>, Status),
|
||||
?assert(is_binary(Plan)),
|
||||
ct:pal(" OK: trial activated with plan ~s", [Plan]).
|
||||
|
||||
%% @doc POST /v1/subscription – повторная активация (409).
|
||||
-spec test_start_trial_duplicate(binary()) -> ok.
|
||||
test_start_trial_duplicate(Token) ->
|
||||
ct:pal(" TEST: Start trial again (duplicate)"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/subscription">>, Token,
|
||||
jsx:encode(#{action => <<"start_trial">>})),
|
||||
?assertMatch({ok, 409, _, _}, Resp),
|
||||
ct:pal(" OK: got 409 conflict").
|
||||
|
||||
%% @doc GET /v1/subscription без токена (401).
|
||||
-spec test_get_subscription_unauthorized() -> ok.
|
||||
test_get_subscription_unauthorized() ->
|
||||
ct:pal(" TEST: Get subscription without token"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/subscription">>, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
137
test/api/users/user_tickets_tests.erl
Normal file
137
test/api/users/user_tickets_tests.erl
Normal file
@@ -0,0 +1,137 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Тесты клиентского API для работы с тикетами.
|
||||
%%%
|
||||
%%% Покрывает эндпоинты:
|
||||
%%% POST /v1/tickets
|
||||
%%% GET /v1/tickets
|
||||
%%% GET /v1/tickets/:id
|
||||
%%%
|
||||
%%% Проверяет:
|
||||
%%% - успешное создание тикета
|
||||
%%% - получение списка своих тикетов
|
||||
%%% - получение конкретного тикета по ID
|
||||
%%% - ошибку 400 при отсутствии обязательных полей
|
||||
%%% - ошибку 401 без токена
|
||||
%%% - ошибку 403 при попытке доступа к чужому тикету
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(user_tickets_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Главная тестовая функция
|
||||
%%%===================================================================
|
||||
|
||||
-spec test() -> ok.
|
||||
test() ->
|
||||
ct:pal("=== User Tickets Tests ==="),
|
||||
Token = api_test_runner:get_user_token(),
|
||||
StrangerEmail = api_test_runner:unique_email(<<"stranger">>),
|
||||
StrangerToken = api_test_runner:register_and_login(StrangerEmail, <<"pass">>),
|
||||
|
||||
% Создаём тикет
|
||||
#{<<"id">> := TicketId} = api_test_runner:client_post(<<"/v1/tickets">>, Token,
|
||||
#{error_message => <<"Something broke">>, stacktrace => <<"line 42">>}),
|
||||
|
||||
test_create_ticket(Token),
|
||||
test_create_ticket_missing_fields(Token),
|
||||
test_create_ticket_unauthorized(),
|
||||
test_list_tickets(Token, TicketId),
|
||||
test_list_tickets_unauthorized(),
|
||||
test_get_ticket(Token, TicketId),
|
||||
test_get_ticket_forbidden(StrangerToken, TicketId),
|
||||
test_get_ticket_not_found(Token),
|
||||
test_get_ticket_unauthorized(TicketId),
|
||||
|
||||
ct:pal("=== All user tickets tests passed ==="),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые функции
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Успешное создание тикета: 201 Created.
|
||||
-spec test_create_ticket(binary()) -> ok.
|
||||
test_create_ticket(Token) ->
|
||||
ct:pal(" TEST: Create a ticket"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/tickets">>, Token,
|
||||
jsx:encode(#{error_message => <<"Test bug">>, stacktrace => <<"trace">>})),
|
||||
{ok, 201, _, Body} = Resp,
|
||||
#{<<"id">> := Id, <<"status">> := Status} = jsx:decode(list_to_binary(Body), [return_maps]),
|
||||
?assert(is_binary(Id)),
|
||||
?assertEqual(<<"open">>, Status),
|
||||
ct:pal(" OK: ticket ~s created", [Id]).
|
||||
|
||||
%% @doc Отсутствие обязательного поля error_message: 400 Bad Request.
|
||||
-spec test_create_ticket_missing_fields(binary()) -> ok.
|
||||
test_create_ticket_missing_fields(Token) ->
|
||||
ct:pal(" TEST: Create ticket without error_message"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/tickets">>, Token,
|
||||
jsx:encode(#{stacktrace => <<"trace">>})),
|
||||
?assertMatch({ok, 400, _, _}, Resp),
|
||||
ct:pal(" OK: got 400").
|
||||
|
||||
%% @doc Создание тикета без токена: 401 Unauthorized.
|
||||
-spec test_create_ticket_unauthorized() -> ok.
|
||||
test_create_ticket_unauthorized() ->
|
||||
ct:pal(" TEST: Create ticket without token"),
|
||||
Resp = api_test_runner:client_request(post, <<"/v1/tickets">>, <<>>,
|
||||
jsx:encode(#{error_message => <<"bug">>})),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
|
||||
%% @doc GET /v1/tickets – получение списка своих тикетов.
|
||||
-spec test_list_tickets(binary(), binary()) -> ok.
|
||||
test_list_tickets(Token, ExpectedTicketId) ->
|
||||
ct:pal(" TEST: List my tickets"),
|
||||
Tickets = api_test_runner:client_get(<<"/v1/tickets">>, Token),
|
||||
?assert(is_list(Tickets)),
|
||||
?assert(length(Tickets) >= 1),
|
||||
?assert(lists:any(fun(T) -> maps:get(<<"id">>, T) =:= ExpectedTicketId end, Tickets)),
|
||||
ct:pal(" OK: my ticket found").
|
||||
|
||||
%% @doc GET /v1/tickets без токена: 401 Unauthorized.
|
||||
-spec test_list_tickets_unauthorized() -> ok.
|
||||
test_list_tickets_unauthorized() ->
|
||||
ct:pal(" TEST: List tickets without token"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/tickets">>, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
|
||||
%% @doc GET /v1/tickets/:id – получение своего тикета.
|
||||
-spec test_get_ticket(binary(), binary()) -> ok.
|
||||
test_get_ticket(Token, TicketId) ->
|
||||
ct:pal(" TEST: Get my ticket by ID"),
|
||||
Path = <<"/v1/tickets/", TicketId/binary>>,
|
||||
Ticket = api_test_runner:client_get(Path, Token),
|
||||
?assertEqual(TicketId, maps:get(<<"id">>, Ticket)),
|
||||
?assertEqual(<<"Something broke">>, maps:get(<<"error_message">>, Ticket)),
|
||||
ct:pal(" OK: got my ticket").
|
||||
|
||||
%% @doc GET /v1/tickets/:id – попытка доступа к чужому тикету (403).
|
||||
-spec test_get_ticket_forbidden(binary(), binary()) -> ok.
|
||||
test_get_ticket_forbidden(StrangerToken, TicketId) ->
|
||||
ct:pal(" TEST: Get ticket that is not mine"),
|
||||
Path = <<"/v1/tickets/", TicketId/binary>>,
|
||||
Resp = api_test_runner:client_request(get, Path, StrangerToken),
|
||||
?assertMatch({ok, 403, _, _}, Resp),
|
||||
ct:pal(" OK: got 403").
|
||||
|
||||
%% @doc GET /v1/tickets/:id – несуществующий тикет (404).
|
||||
-spec test_get_ticket_not_found(binary()) -> ok.
|
||||
test_get_ticket_not_found(Token) ->
|
||||
ct:pal(" TEST: Get non-existent ticket"),
|
||||
Resp = api_test_runner:client_request(get, <<"/v1/tickets/fakeid">>, Token),
|
||||
?assertMatch({ok, 404, _, _}, Resp),
|
||||
ct:pal(" OK: got 404").
|
||||
|
||||
%% @doc GET /v1/tickets/:id – без токена (401).
|
||||
-spec test_get_ticket_unauthorized(binary()) -> ok.
|
||||
test_get_ticket_unauthorized(TicketId) ->
|
||||
ct:pal(" TEST: Get ticket without token"),
|
||||
Path = <<"/v1/tickets/", TicketId/binary>>,
|
||||
Resp = api_test_runner:client_request(get, Path, <<>>),
|
||||
?assertMatch({ok, 401, _, _}, Resp),
|
||||
ct:pal(" OK: got 401").
|
||||
@@ -1,53 +1,42 @@
|
||||
-module(api_websocket_tests).
|
||||
-module(user_websocket_tests).
|
||||
-export([test/0]).
|
||||
|
||||
-define(BASE_URL, api_test_runner:get_base_url()).
|
||||
-define(WS_URL, api_test_runner:get_base_ws_url() ++ "/ws").
|
||||
-define(ADMIN_WS_URL, api_test_runner:get_admin_ws_url() ++ "/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(),
|
||||
|
||||
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),
|
||||
% Создаём календарь и событие через новый api_test_runner
|
||||
CalId = api_test_runner:create_calendar(UserToken, #{title => <<"WS Test Calendar">>, type => <<"commercial">>}),
|
||||
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),
|
||||
EventId = api_test_runner:create_event(UserToken, CalId, #{
|
||||
title => <<"WS Test Event">>,
|
||||
start_time => <<"2026-06-01T10:00:00Z">>,
|
||||
duration => 60
|
||||
}),
|
||||
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)]),
|
||||
WsUrl = api_test_runner:get_base_ws_url() ++ "/ws",
|
||||
AdminWsUrl = api_test_runner:get_admin_ws_url() ++ "/admin/ws",
|
||||
|
||||
case test_ws_connect_debug(?WS_URL, UserToken) of
|
||||
%% TEST 1: Connect to WebSocket with valid token
|
||||
ct:pal(" TEST 1: Connect WebSocket with valid token..."),
|
||||
ct:pal(" URL: ~s", [WsUrl]),
|
||||
ct:pal(" Token: ~s...", [binary_part(UserToken, 0, 30)]),
|
||||
case test_ws_connect_debug(WsUrl, UserToken) of
|
||||
{ok, WS} ->
|
||||
ct:pal(" OK - Connected"),
|
||||
|
||||
% TEST 2: Subscribe to calendar updates
|
||||
%% 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");
|
||||
@@ -59,7 +48,6 @@ test() ->
|
||||
error(timeout)
|
||||
end,
|
||||
|
||||
% Закрываем соединение
|
||||
test_ws_close(WS);
|
||||
{error, Reason} ->
|
||||
ct:pal(" ERROR: ~p", [Reason]),
|
||||
@@ -68,42 +56,40 @@ test() ->
|
||||
|
||||
ct:pal("~n✅ WebSocket API tests passed!"),
|
||||
|
||||
% ============ ТЕСТЫ АДМИНСКОГО WEBSOCKET ============
|
||||
%% ============ ТЕСТЫ АДМИНСКОГО WEBSOCKET ============
|
||||
ct:pal("~n=== ADMIN WEBSOCKET TESTS ==="),
|
||||
|
||||
% TEST 6: Admin WebSocket connection
|
||||
%% TEST 6: Admin WebSocket connection
|
||||
ct:pal(" TEST 6: Admin WebSocket connect..."),
|
||||
{ok, AdminWS} = test_ws_connect_debug(?ADMIN_WS_URL, AdminToken),
|
||||
{ok, AdminWS} = test_ws_connect_debug(AdminWsUrl, AdminToken),
|
||||
ct:pal(" OK - Admin connected"),
|
||||
|
||||
% TEST 7: Admin subscribe to reports channel
|
||||
%% 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
|
||||
%% 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
|
||||
%% 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),
|
||||
api_test_runner:client_post(<<"/v1/reports">>, UserToken,
|
||||
#{target_type => <<"event">>, target_id => EventId, reason => <<"Test report">>}),
|
||||
{ok, #{<<"type">> := <<"report_created">>}} = test_ws_recv(AdminWS, 5000),
|
||||
ct:pal(" OK - Received report notification"),
|
||||
|
||||
% TEST 10: Admin Ping/Pong
|
||||
%% 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
|
||||
%% 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),
|
||||
@@ -111,23 +97,23 @@ test() ->
|
||||
|
||||
test_ws_close(AdminWS),
|
||||
|
||||
% TEST 12: Admin WebSocket with user token (should fail)
|
||||
%% 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),
|
||||
{error, {403, _}} = test_ws_connect_debug(AdminWsUrl, UserToken),
|
||||
ct:pal(" OK - Rejected"),
|
||||
|
||||
% TEST 13: Admin WebSocket with invalid token
|
||||
%% 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),
|
||||
InvalidToken = << <<(binary:at(Chars, rand:uniform(byte_size(Chars)) - 1))>>
|
||||
|| _ <- lists:seq(1, 30) >>,
|
||||
{error, {401, _}} = test_ws_connect_debug(AdminWsUrl, 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] ->
|
||||
@@ -140,33 +126,21 @@ test_ws_connect_debug(Url, Token) ->
|
||||
_ ->
|
||||
"/ws?token=" ++ binary_to_list(Token)
|
||||
end,
|
||||
|
||||
{ok, Port} = extract_port(Url),
|
||||
{ok, Host} = extract_host(Url),
|
||||
|
||||
Opts = case Port of
|
||||
443 ->
|
||||
#{
|
||||
protocols => [http],
|
||||
transport => tls,
|
||||
tls_opts => [{verify, verify_none}]
|
||||
};
|
||||
_ -> #{ protocols => [http] }
|
||||
443 -> #{protocols => [http],
|
||||
transport => tls,
|
||||
tls_opts => [{verify, verify_none}]};
|
||||
_ -> #{protocols => [http]}
|
||||
end,
|
||||
|
||||
ct:pal(" Host: ~s", [Host]),
|
||||
ct:pal(" Port: ~p", [Port]),
|
||||
ct:pal(" Path: ~s", [Path]),
|
||||
|
||||
{ok, ConnPid} = gun:open(Host, Port, Opts),
|
||||
{ok, http} = gun:await_up(ConnPid, 5000),
|
||||
|
||||
Headers = [
|
||||
{<<"host">>, list_to_binary(Host ++ ":" ++ integer_to_list(Port))}
|
||||
],
|
||||
{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"),
|
||||
@@ -207,7 +181,6 @@ test_ws_send(ConnPid, Data) ->
|
||||
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]),
|
||||
@@ -245,7 +218,6 @@ test_ws_close(ConnPid) ->
|
||||
gun:close(ConnPid).
|
||||
|
||||
%% ========== URL parsing helpers ==========
|
||||
|
||||
normalize_scheme(S) when is_binary(S) -> S;
|
||||
normalize_scheme(S) when is_list(S) -> list_to_binary(S);
|
||||
normalize_scheme(S) when is_atom(S) -> atom_to_binary(S, utf8);
|
||||
@@ -1,130 +0,0 @@
|
||||
-module(api_SUITE).
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-export([all/0, init_per_suite/1, end_per_suite/1]).
|
||||
-export([auth_test/1, calendar_test/1, event_test/1, booking_test/1,
|
||||
search_test/1, reviews_test/1, moderation_test/1, tickets_test/1,
|
||||
subscription_test/1, admin_test/1, websocket_test/1]).
|
||||
-export([future_date/0]).
|
||||
|
||||
all() ->
|
||||
[
|
||||
auth_test,
|
||||
calendar_test,
|
||||
event_test,
|
||||
booking_test,
|
||||
search_test,
|
||||
reviews_test,
|
||||
moderation_test,
|
||||
tickets_test,
|
||||
subscription_test,
|
||||
admin_test,
|
||||
websocket_test
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
ct:pal("Start Api Testing ~n"),
|
||||
Mode = os:getenv("CT_MODE", "local"),
|
||||
ct:pal(" Mode: ~s", [Mode]),
|
||||
AdminURL = os:getenv("ADMIN_API_HOST"),
|
||||
ct:pal(" AdminURL: ~s", [AdminURL]),
|
||||
AdminWsURL = os:getenv("ADMIN_WS_HOST"),
|
||||
ct:pal(" AdminWsURL: ~s", [AdminWsURL]),
|
||||
UserURL = os:getenv("API_HOST"),
|
||||
ct:pal(" UserURL: ~s", [UserURL]),
|
||||
UserWsURL = os:getenv("WS_HOST"),
|
||||
ct:pal(" UserWsURL: ~s", [UserWsURL]),
|
||||
|
||||
case Mode of
|
||||
"remote" ->
|
||||
inets:start(),
|
||||
ssl:start(),
|
||||
% Отключаем авто-редирект и проверку сертификатов
|
||||
httpc:set_options([
|
||||
{autoredirect, false},
|
||||
{ssl, [{verify, verify_none}]}
|
||||
]),
|
||||
wait_for_server(),
|
||||
timer:sleep(1000),
|
||||
% Извлекаем учётные данные администраторов из переменных окружения
|
||||
% и сохраняем их в словаре процесса для api_test_runner
|
||||
put(admin_super_email,
|
||||
list_to_binary(os:getenv("ADMIN_SUPER_EMAIL", "superadmin@eventhub.local"))),
|
||||
put(admin_super_password,
|
||||
list_to_binary(os:getenv("ADMIN_SUPER_PASSWORD", "123456"))),
|
||||
put(admin_moder_email,
|
||||
list_to_binary(os:getenv("ADMIN_MODER_EMAIL", "moderator@eventhub.local"))),
|
||||
put(admin_moder_password,
|
||||
list_to_binary(os:getenv("ADMIN_MODER_PASSWORD", "123456"))),
|
||||
put(admin_support_email,
|
||||
list_to_binary(os:getenv("ADMIN_SUPPORT_EMAIL", "support@eventhub.local"))),
|
||||
put(admin_support_password,
|
||||
list_to_binary(os:getenv("ADMIN_SUPPORT_PASSWORD", "123456"))),
|
||||
Config;
|
||||
_ ->
|
||||
application:ensure_all_started(eventhub),
|
||||
timer:sleep(3000),
|
||||
check_admins(),
|
||||
Config
|
||||
end.
|
||||
|
||||
end_per_suite(Config) ->
|
||||
Mode = os:getenv("CT_MODE", "local"),
|
||||
case Mode of
|
||||
"remote" ->
|
||||
ok;
|
||||
_ ->
|
||||
application:stop(eventhub)
|
||||
end,
|
||||
Config.
|
||||
|
||||
%% ── Тестовые обёртки ──────────────────────────────────
|
||||
auth_test(_) -> api_auth_tests:test().
|
||||
calendar_test(_) -> api_calendar_tests:test().
|
||||
event_test(_) -> api_event_tests:test().
|
||||
booking_test(_) -> api_booking_tests:test().
|
||||
search_test(_) -> api_search_tests:test().
|
||||
reviews_test(_) -> api_reviews_tests:test().
|
||||
moderation_test(_) -> api_moderation_tests:test().
|
||||
tickets_test(_) -> api_tickets_tests:test().
|
||||
subscription_test(_) -> api_subscription_tests:test().
|
||||
admin_test(_) -> api_admin_tests:test().
|
||||
websocket_test(_) -> api_websocket_tests:test().
|
||||
|
||||
%% @doc Проверка наличия администраторов (только в remote‑режиме)
|
||||
%% Если таблица admin пуста – роняем тест явно, чтобы не гадать.
|
||||
check_admins() ->
|
||||
case core_admin:list_all() of
|
||||
[] ->
|
||||
ct:fail("No admins found in remote cluster. Run init_default_admins first.");
|
||||
Admins ->
|
||||
ct:pal("Admins present: ~p", [length(Admins)])
|
||||
end.
|
||||
|
||||
%% @doc Ожидание доступности healthcheck-эндпоинта (/health)
|
||||
wait_for_server() ->
|
||||
URL = case os:getenv("API_HOST") of
|
||||
false -> "http://localhost:8080/health";
|
||||
Host -> Host ++ "/health"
|
||||
end,
|
||||
wait_for_server(URL, 30).
|
||||
|
||||
wait_for_server(URL, 0) ->
|
||||
ct:fail("Healthcheck ~s not responding after 30 seconds", [URL]);
|
||||
wait_for_server(URL, Attempts) ->
|
||||
case httpc:request(get, {URL, []}, [{timeout, 2000}, {ssl, [{verify, verify_none}]}], []) of
|
||||
{ok, {{_, 200, _}, _, _}} ->
|
||||
ct:pal("Healthcheck OK", []);
|
||||
_ ->
|
||||
timer:sleep(1000),
|
||||
wait_for_server(URL, Attempts - 1)
|
||||
end.
|
||||
|
||||
future_date() ->
|
||||
Now = calendar:universal_time(),
|
||||
Tomorrow = calendar:gregorian_seconds_to_datetime(
|
||||
calendar:datetime_to_gregorian_seconds(Now) + 86400
|
||||
),
|
||||
{{Y, M, D}, {H, Min, S}} = Tomorrow,
|
||||
iolist_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
|
||||
[Y, M, D, H, Min, S])).
|
||||
143
test/api_admins_SUITE.erl
Normal file
143
test/api_admins_SUITE.erl
Normal file
@@ -0,0 +1,143 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Common Test Suite для административного API.
|
||||
%%%
|
||||
%%% Поддерживает два режима запуска:
|
||||
%%% - Локальный (CT_MODE=local или не задан): автоматически
|
||||
%%% стартует EventHub, выполняет тесты и останавливает приложение.
|
||||
%%% - Удалённый (CT_MODE=remote): подключается к уже работающему
|
||||
%%% кластеру по URL, определённым в переменных окружения
|
||||
%%% (API_HOST, ADMIN_API_HOST и т.д.).
|
||||
%%%
|
||||
%%% Запуск:
|
||||
%%% rebar3 ct --suite=api_admin_SUITE
|
||||
%%% или CT_MODE=remote rebar3 ct --suite=api_admin_SUITE
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(api_admins_SUITE).
|
||||
-compile(export_all).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% Common Test callbacks
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Возвращает список тестовых кейсов.
|
||||
all() ->
|
||||
[
|
||||
admin_test_events,
|
||||
admin_test_reviews,
|
||||
admin_test_users,
|
||||
admin_test_tickets,
|
||||
admin_test_reports,
|
||||
admin_test_subscriptions,
|
||||
admin_test_banned_words,
|
||||
admin_test_moderation,
|
||||
admin_test_audit,
|
||||
admin_test_stats,
|
||||
admin_test_me,
|
||||
admin_test_admins,
|
||||
admin_test_websocket
|
||||
].
|
||||
|
||||
%% @doc Инициализация сьюта.
|
||||
%% В локальном режиме запускает приложение EventHub.
|
||||
init_per_suite(Config) ->
|
||||
case os:getenv("CT_MODE", "local") of
|
||||
"remote" ->
|
||||
ct:pal("Remote mode: assuming application is already running"),
|
||||
wait_for_remote(),
|
||||
[{started_by_us, false} | Config];
|
||||
_ ->
|
||||
case lists:keymember(eventhub, 1, application:which_applications()) of
|
||||
true ->
|
||||
ct:pal("Local mode: application already running"),
|
||||
[{started_by_us, false} | Config];
|
||||
false ->
|
||||
ct:pal("Local mode: starting application..."),
|
||||
%% ok = application:load(eventhub),
|
||||
{ok, _} = application:ensure_all_started(eventhub),
|
||||
timer:sleep(1000),
|
||||
[{started_by_us, true} | Config]
|
||||
end
|
||||
end.
|
||||
|
||||
%% @doc Завершение сьюта. В локальном режиме останавливает приложение.
|
||||
end_per_suite(Config) ->
|
||||
case proplists:get_value(started_by_us, Config, false) andalso
|
||||
os:getenv("CT_MODE", "local") =/= "remote" of
|
||||
true ->
|
||||
application:stop(eventhub);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
Config.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые кейсы
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Тесты для событий.
|
||||
admin_test_events(_Config) ->
|
||||
admin_events_tests:test().
|
||||
|
||||
admin_test_reviews(_Config) ->
|
||||
admin_reviews_tests:test().
|
||||
|
||||
admin_test_users(_Config) ->
|
||||
admin_users_tests:test().
|
||||
|
||||
admin_test_tickets(_Config) ->
|
||||
admin_tickets_tests:test().
|
||||
|
||||
admin_test_reports(_Config) ->
|
||||
admin_reports_tests:test().
|
||||
|
||||
admin_test_subscriptions(_Config) ->
|
||||
admin_subscriptions_tests:test().
|
||||
|
||||
admin_test_banned_words(_Config) ->
|
||||
admin_banned_words_tests:test().
|
||||
|
||||
admin_test_moderation(_Config) ->
|
||||
admin_moderation_tests:test().
|
||||
|
||||
admin_test_audit(_Config) ->
|
||||
admin_audit_tests:test().
|
||||
|
||||
admin_test_stats(_Config) ->
|
||||
admin_stats_tests:test().
|
||||
|
||||
admin_test_me(_Config) ->
|
||||
admin_me_tests:test().
|
||||
|
||||
admin_test_admins(_Config) ->
|
||||
admin_admins_tests:test().
|
||||
|
||||
admin_test_websocket(_Config) ->
|
||||
admin_websocket_tests:test().
|
||||
|
||||
%%%===================================================================
|
||||
%%% Внутренние функции
|
||||
%%%===================================================================
|
||||
|
||||
-spec ct_mode() -> string().
|
||||
ct_mode() ->
|
||||
os:getenv("CT_MODE", "local").
|
||||
|
||||
%% @private Ожидание доступности удалённого API.
|
||||
-spec wait_for_remote() -> ok.
|
||||
wait_for_remote() ->
|
||||
URL = os:getenv("ADMIN_API_HOST", "http://localhost:8445") ++ "/admin/health",
|
||||
ct:pal("Waiting for remote API: ~s", [URL]),
|
||||
wait_for_health(URL, 30).
|
||||
|
||||
wait_for_health(_URL, 0) ->
|
||||
ct:fail("Remote API did not start within 30 seconds");
|
||||
wait_for_health(URL, Retries) ->
|
||||
case httpc:request(get, {URL, []}, [], []) of
|
||||
{ok, {{_, 200, _}, _, _}} -> ok;
|
||||
_ ->
|
||||
timer:sleep(1000),
|
||||
wait_for_health(URL, Retries - 1)
|
||||
end.
|
||||
171
test/api_users_SUITE.erl
Normal file
171
test/api_users_SUITE.erl
Normal file
@@ -0,0 +1,171 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Common Test Suite для клиентского API.
|
||||
%%%
|
||||
%%% Поддерживает два режима запуска:
|
||||
%%% - Локальный (CT_MODE=local или не задан): автоматически
|
||||
%%% стартует EventHub, выполняет тесты и останавливает приложение.
|
||||
%%% - Удалённый (CT_MODE=remote): подключается к уже работающему
|
||||
%%% кластеру по URL, определённым в переменных окружения
|
||||
%%% (API_HOST, ADMIN_API_HOST и т.д.).
|
||||
%%%
|
||||
%%% Запуск:
|
||||
%%% rebar3 ct --suite=api_client_SUITE
|
||||
%%% или CT_MODE=remote rebar3 ct --suite=api_client_SUITE
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(api_users_SUITE).
|
||||
-compile(export_all).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% Common Test callbacks
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Возвращает список тестовых кейсов.
|
||||
all() ->
|
||||
[
|
||||
user_test_register,
|
||||
user_test_login,
|
||||
user_test_user_me,
|
||||
user_test_calendars,
|
||||
user_test_calendar_by_id,
|
||||
user_test_calendar_view,
|
||||
user_test_event_by_id,
|
||||
user_test_events,
|
||||
user_test_occurrence_cancel,
|
||||
user_test_bookings,
|
||||
user_test_my_bookings,
|
||||
user_test_reviews,
|
||||
user_test_review_by_id,
|
||||
user_test_my_reviews,
|
||||
user_test_search,
|
||||
user_test_refresh,
|
||||
user_test_reports,
|
||||
user_test_tickets,
|
||||
user_test_subscription,
|
||||
user_test_websocket
|
||||
].
|
||||
|
||||
%% @doc Инициализация сьюта.
|
||||
%% В локальном режиме запускает приложение EventHub.
|
||||
init_per_suite(Config) ->
|
||||
case os:getenv("CT_MODE", "local") of
|
||||
"remote" ->
|
||||
ct:pal("Remote mode: assuming application is already running"),
|
||||
wait_for_remote(),
|
||||
[{started_by_us, false} | Config];
|
||||
_ ->
|
||||
case lists:keymember(eventhub, 1, application:which_applications()) of
|
||||
true ->
|
||||
ct:pal("Local mode: application already running"),
|
||||
[{started_by_us, false} | Config];
|
||||
false ->
|
||||
ct:pal("Local mode: starting application..."),
|
||||
%% ok = application:load(eventhub),
|
||||
{ok, _} = application:ensure_all_started(eventhub),
|
||||
timer:sleep(1000),
|
||||
[{started_by_us, true} | Config]
|
||||
end
|
||||
end.
|
||||
|
||||
%% @doc Завершение сьюта. В локальном режиме останавливает приложение.
|
||||
end_per_suite(Config) ->
|
||||
case proplists:get_value(started_by_us, Config, false) andalso
|
||||
os:getenv("CT_MODE", "local") =/= "remote" of
|
||||
true ->
|
||||
application:stop(eventhub);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
Config.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Тестовые кейсы
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Тесты регистрации.
|
||||
user_test_register(_Config) ->
|
||||
user_register_tests:test().
|
||||
|
||||
user_test_login(_Config) ->
|
||||
user_login_tests:test().
|
||||
|
||||
user_test_user_me(_Config) ->
|
||||
user_me_tests:test().
|
||||
|
||||
user_test_calendars(_Config) ->
|
||||
user_calendars_tests:test().
|
||||
|
||||
user_test_calendar_by_id(_Config) ->
|
||||
user_calendar_by_id_tests:test().
|
||||
|
||||
user_test_calendar_view(_Config) ->
|
||||
user_calendar_view_tests:test().
|
||||
|
||||
user_test_events(_Config) ->
|
||||
user_events_tests:test().
|
||||
|
||||
user_test_event_by_id(_Config) ->
|
||||
user_event_by_id_tests:test().
|
||||
|
||||
user_test_occurrence_cancel(_Config) ->
|
||||
user_occurrence_cancel_tests:test().
|
||||
|
||||
user_test_bookings(_Config) ->
|
||||
user_bookings_tests:test().
|
||||
|
||||
user_test_my_bookings(_Config) ->
|
||||
user_my_bookings_tests:test().
|
||||
|
||||
user_test_reviews(_Config) ->
|
||||
user_reviews_tests:test().
|
||||
|
||||
user_test_review_by_id(_Config) ->
|
||||
user_review_by_id_tests:test().
|
||||
|
||||
user_test_my_reviews(_Config) ->
|
||||
user_my_reviews_tests:test().
|
||||
|
||||
user_test_search(_Config) ->
|
||||
user_search_tests:test().
|
||||
|
||||
user_test_refresh(_Config) ->
|
||||
user_refresh_tests:test().
|
||||
|
||||
user_test_reports(_Config) ->
|
||||
user_reports_tests:test().
|
||||
|
||||
user_test_tickets(_Config) ->
|
||||
user_tickets_tests:test().
|
||||
|
||||
user_test_subscription(_Config) ->
|
||||
user_subscription_tests:test().
|
||||
|
||||
user_test_websocket(_Config) ->
|
||||
user_websocket_tests:test().
|
||||
|
||||
%%%===================================================================
|
||||
%%% Внутренние функции
|
||||
%%%===================================================================
|
||||
|
||||
-spec ct_mode() -> string().
|
||||
ct_mode() ->
|
||||
os:getenv("CT_MODE", "local").
|
||||
|
||||
%% @private Ожидание доступности удалённого API.
|
||||
-spec wait_for_remote() -> ok.
|
||||
wait_for_remote() ->
|
||||
URL = os:getenv("API_HOST", "http://localhost:8080") ++ "/health",
|
||||
ct:pal("Waiting for remote API: ~s", [URL]),
|
||||
wait_for_health(URL, 30).
|
||||
|
||||
wait_for_health(_URL, 0) ->
|
||||
ct:fail("Remote API did not start within 30 seconds");
|
||||
wait_for_health(URL, Retries) ->
|
||||
case httpc:request(get, {URL, []}, [], []) of
|
||||
{ok, {{_, 200, _}, _, _}} -> ok;
|
||||
_ ->
|
||||
timer:sleep(1000),
|
||||
wait_for_health(URL, Retries - 1)
|
||||
end.
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo "============================================================"
|
||||
echo " FULL TEST CYCLE"
|
||||
echo "============================================================"
|
||||
|
||||
# Остановка старых процессов
|
||||
echo "[1/4] Stopping old servers..."
|
||||
pkill -f "beam.*eventhub" 2>/dev/null || true
|
||||
rm -rf Mnesia.*
|
||||
sleep 2
|
||||
|
||||
# Запуск сервера в фоне
|
||||
echo "[2/4] Starting server..."
|
||||
./test/scripts/start_server_bg.sh
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to start server"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Запуск тестов
|
||||
echo "[3/4] Running tests..."
|
||||
chmod +x test/scripts/*.sh
|
||||
cd test/scripts
|
||||
|
||||
if [ -n "$1" ]; then
|
||||
./test_runner.sh -s "$1"
|
||||
else
|
||||
./test_runner.sh -s
|
||||
fi
|
||||
|
||||
TEST_RESULT=$?
|
||||
|
||||
# Остановка сервера
|
||||
echo ""
|
||||
echo "[4/4] Stopping server..."
|
||||
pkill -f "beam.*eventhub" 2>/dev/null || true
|
||||
|
||||
echo "============================================================"
|
||||
if [ $TEST_RESULT -eq 0 ]; then
|
||||
echo "🎉 ALL TESTS PASSED!"
|
||||
else
|
||||
echo "❌ TESTS FAILED"
|
||||
fi
|
||||
echo "============================================================"
|
||||
|
||||
exit $TEST_RESULT
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
echo "PROJECT_ROOT: $PROJECT_ROOT"
|
||||
# Очистка
|
||||
echo "Stopping old processes..."
|
||||
pkill beam 2>/dev/null || true
|
||||
rm -rf Mnesia.*
|
||||
sleep 2
|
||||
|
||||
# Компиляция
|
||||
echo "Compiling..."
|
||||
rebar3 compile > /dev/null 2>&1
|
||||
|
||||
# Запуск в фоне через erl
|
||||
echo "Starting server in background..."
|
||||
erl -sname eventhub_test \
|
||||
-pa _build/default/lib/*/ebin \
|
||||
-eval "application:ensure_all_started(eventhub)" \
|
||||
-noshell \
|
||||
-detached
|
||||
|
||||
# Ждём запуска
|
||||
echo "Waiting for server..."
|
||||
for i in {1..30}; do
|
||||
if curl -s http://localhost:8080/health 2>/dev/null | grep -q "ok"; then
|
||||
echo "✓ Server ready at http://localhost:8080"
|
||||
pgrep -f "beam.*eventhub_test"
|
||||
exit 0
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✗ Server failed to start"
|
||||
pkill beam 2>/dev/null || true
|
||||
exit 1
|
||||
@@ -1,256 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
ADMIN_URL="http://localhost:8445"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
extract_json_number() {
|
||||
echo "$1" | grep -o "\"$2\":[0-9]*" | head -1 | sed "s/\"$2\"://"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1; local token=$2
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" -H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
http_put() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
curl -s -X PUT "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
}
|
||||
|
||||
http_delete() {
|
||||
local url=$1; local token=$2
|
||||
curl -s -X DELETE "$url" -H "Authorization: Bearer $token"
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB ADMIN API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Checking if servers are running..."
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_error "Main server is not running on port 8080"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Main server is running"
|
||||
|
||||
if ! curl -s "$ADMIN_URL/admin/health" | grep -q "ok"; then
|
||||
log_error "Admin server is not running on port 8445"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Admin server is running"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 1: Create test users"
|
||||
log_info "============================================================"
|
||||
|
||||
# Админ (первый пользователь)
|
||||
ADMIN_EMAIL="admin_test_$(date +%s)@example.com"
|
||||
ADMIN_PASSWORD="admin123"
|
||||
|
||||
log_info "Creating admin user..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}" "")
|
||||
ADMIN_TOKEN=$(extract_json "$response" "token")
|
||||
ADMIN_ID=$(extract_json "$response" "id")
|
||||
log_success "Admin created: $ADMIN_ID"
|
||||
|
||||
# Обычный пользователь
|
||||
USER_EMAIL="user_test_$(date +%s)@example.com"
|
||||
USER_PASSWORD="user123"
|
||||
|
||||
log_info "Creating regular user..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$USER_EMAIL\",\"password\":\"$USER_PASSWORD\"}" "")
|
||||
USER_TOKEN=$(extract_json "$response" "token")
|
||||
USER_ID=$(extract_json "$response" "id")
|
||||
log_success "User created: $USER_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Admin healthcheck"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$ADMIN_URL/admin/health" "")
|
||||
if echo "$response" | grep -q "admin"; then
|
||||
log_success "Admin healthcheck passed: $response"
|
||||
else
|
||||
log_error "Admin healthcheck failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Admin stats (requires auth)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$ADMIN_URL/admin/stats" "$ADMIN_TOKEN")
|
||||
if echo "$response" | grep -q "users"; then
|
||||
log_success "Admin stats retrieved"
|
||||
USERS=$(extract_json_number "$response" "users")
|
||||
log_info "Users: $USERS"
|
||||
else
|
||||
log_error "Admin stats failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: Admin stats without token (should fail)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$ADMIN_URL/admin/stats" "")
|
||||
if echo "$response" | grep -q "Missing"; then
|
||||
log_success "Unauthorized access correctly rejected"
|
||||
else
|
||||
log_error "Should reject unauthorized: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: Admin stats with user token (should fail)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$ADMIN_URL/admin/stats" "$USER_TOKEN")
|
||||
if echo "$response" | grep -q "Admin access required"; then
|
||||
log_success "User token correctly rejected"
|
||||
else
|
||||
log_error "Should reject user token: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 5: List all users (admin)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$ADMIN_URL/admin/users" "$ADMIN_TOKEN")
|
||||
USER_COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
|
||||
if [ "$USER_COUNT" -ge 2 ]; then
|
||||
log_success "Admin sees $USER_COUNT users"
|
||||
else
|
||||
log_error "Admin should see at least 2 users: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 6: Get specific user (admin)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$ADMIN_URL/admin/users/$USER_ID" "$ADMIN_TOKEN")
|
||||
if echo "$response" | grep -q "$USER_ID"; then
|
||||
log_success "Admin can view user $USER_ID"
|
||||
else
|
||||
log_error "Admin cannot view user: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 7: Update user (admin)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_put "$ADMIN_URL/admin/users/$USER_ID" "{\"status\":\"frozen\"}" "$ADMIN_TOKEN")
|
||||
if echo "$response" | grep -q "frozen"; then
|
||||
log_success "User status updated to frozen"
|
||||
else
|
||||
log_error "Failed to update user: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 8: Verify user status changed"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$ADMIN_URL/admin/users/$USER_ID" "$ADMIN_TOKEN")
|
||||
if echo "$response" | grep -q "frozen"; then
|
||||
log_success "User status confirmed as frozen"
|
||||
else
|
||||
log_error "User status not updated: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 9: Restore user status"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_put "$ADMIN_URL/admin/users/$USER_ID" "{\"status\":\"active\"}" "$ADMIN_TOKEN")
|
||||
if echo "$response" | grep -q "active"; then
|
||||
log_success "User status restored to active"
|
||||
else
|
||||
log_error "Failed to restore user: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 10: User cannot access admin endpoints"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$ADMIN_URL/admin/users" "$USER_TOKEN")
|
||||
if echo "$response" | grep -q "Admin access required"; then
|
||||
log_success "User correctly denied access to admin users list"
|
||||
else
|
||||
log_error "User should be denied: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 11: Delete user (admin)"
|
||||
log_info "============================================================"
|
||||
|
||||
# Создаём пользователя для удаления
|
||||
DELETE_EMAIL="delete_me_$(date +%s)@example.com"
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$DELETE_EMAIL\",\"password\":\"pass123\"}" "")
|
||||
DELETE_ID=$(extract_json "$response" "id")
|
||||
log_info "Created user to delete: $DELETE_ID"
|
||||
|
||||
response=$(http_delete "$ADMIN_URL/admin/users/$DELETE_ID" "$ADMIN_TOKEN")
|
||||
if echo "$response" | grep -q "deleted"; then
|
||||
log_success "User deleted successfully"
|
||||
else
|
||||
log_error "Failed to delete user: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 12: Verify user deleted"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$ADMIN_URL/admin/users/$DELETE_ID" "$ADMIN_TOKEN")
|
||||
if echo "$response" | grep -q "not found"; then
|
||||
log_success "Deleted user not found"
|
||||
else
|
||||
log_error "Deleted user still accessible: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "ADMIN API TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo " Admin: $ADMIN_EMAIL"
|
||||
echo " User: $USER_EMAIL"
|
||||
echo ""
|
||||
@@ -1,298 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPTS_DIR/../.." && pwd)"
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
SKIPPED=0
|
||||
SERVER_STARTED=false
|
||||
SERVER_PID=""
|
||||
|
||||
# ============================================================================
|
||||
# Вспомогательные функции
|
||||
# ============================================================================
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
log_step() { echo -e "${CYAN}[STEP]${NC} $1"; }
|
||||
|
||||
# Очистка при выходе
|
||||
cleanup() {
|
||||
echo ""
|
||||
log_info "Cleaning up..."
|
||||
|
||||
if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||
log_info "Stopping server (PID: $SERVER_PID)..."
|
||||
kill "$SERVER_PID" 2>/dev/null
|
||||
wait "$SERVER_PID" 2>/dev/null
|
||||
|
||||
# Убеждаемся, что все beam процессы остановлены
|
||||
pkill -f "beam.*eventhub" 2>/dev/null || true
|
||||
log_success "Server stopped"
|
||||
fi
|
||||
|
||||
# Удаляем временные файлы
|
||||
rm -f /tmp/eventhub_test_*.log 2>/dev/null
|
||||
}
|
||||
|
||||
# Обработчик сигналов
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
# Проверка порта
|
||||
check_port() {
|
||||
local port=$1
|
||||
if lsof -i ":$port" > /dev/null 2>&1 || netstat -tuln 2>/dev/null | grep -q ":$port "; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Ожидание запуска сервера
|
||||
wait_for_server() {
|
||||
local max_attempts=30
|
||||
local attempt=0
|
||||
|
||||
log_info "Waiting for server to start..."
|
||||
|
||||
while [ $attempt -lt $max_attempts ]; do
|
||||
if curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_success "Server is ready (took $attempt seconds)"
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
((attempt++))
|
||||
echo -n "."
|
||||
done
|
||||
echo ""
|
||||
|
||||
log_error "Server failed to start within $max_attempts seconds"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Запуск сервера
|
||||
start_server() {
|
||||
echo -e "${CYAN}[STEP]${NC} Starting EventHub server..."
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
if [ ! -f "rebar.config" ]; then
|
||||
echo -e "${RED}[ERROR]${NC} rebar.config not found in $(pwd)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}[INFO]${NC} Project root: $(pwd)"
|
||||
|
||||
# Очищаем старые данные
|
||||
rm -rf Mnesia.* 2>/dev/null
|
||||
pkill -f "beam.*eventhub_test" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# Компилируем
|
||||
echo -e "${BLUE}[INFO]${NC} Compiling..."
|
||||
rebar3 compile > /dev/null 2>&1
|
||||
|
||||
# Запускаем сервер через erl (более надёжно для фона)
|
||||
LOG_FILE="/tmp/eventhub_test_server.log"
|
||||
echo -e "${BLUE}[INFO]${NC} Starting server..."
|
||||
|
||||
# Запускаем в фоне с перенаправлением вывода
|
||||
rebar3 shell --sname eventhub_test </dev/null > "$LOG_FILE" 2>&1 &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Даём процессу время запуститься
|
||||
sleep 3
|
||||
|
||||
# Проверяем, жив ли процесс
|
||||
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||
echo -e "${RED}[ERROR]${NC} Server process died immediately"
|
||||
echo -e "${YELLOW}[INFO]${NC} Check log: $LOG_FILE"
|
||||
cat "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}[INFO]${NC} Server PID: $SERVER_PID"
|
||||
|
||||
# Ждём готовности
|
||||
for i in {1..30}; do
|
||||
echo -n "."
|
||||
if curl -s "http://localhost:8080/health" 2>/dev/null | grep -q "ok"; then
|
||||
echo ""
|
||||
echo -e "${GREEN}[SUCCESS]${NC} Server ready at http://localhost:8080"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||
echo ""
|
||||
echo -e "${RED}[ERROR]${NC} Server died during startup"
|
||||
echo -e "${YELLOW}[INFO]${NC} Last lines of log:"
|
||||
tail -30 "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${RED}[ERROR]${NC} Server failed to respond"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Остановка сервера
|
||||
stop_server() {
|
||||
if [ "$SERVER_STARTED" = true ] && [ -n "$SERVER_PID" ]; then
|
||||
log_step "Stopping EventHub server..."
|
||||
|
||||
# Останавливаем нашу ноду
|
||||
if kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||
kill "$SERVER_PID" 2>/dev/null
|
||||
wait "$SERVER_PID" 2>/dev/null
|
||||
fi
|
||||
|
||||
# Останавливаем все связанные beam процессы
|
||||
pkill -f "beam.*eventhub_test" 2>/dev/null || true
|
||||
|
||||
SERVER_STARTED=false
|
||||
log_success "Server stopped"
|
||||
sleep 2
|
||||
fi
|
||||
}
|
||||
|
||||
# Проверка, запущен ли сервер
|
||||
is_server_running() {
|
||||
curl -s "$BASE_URL/health" | grep -q "ok"
|
||||
}
|
||||
|
||||
# Запуск одного тестового скрипта
|
||||
run_test_script() {
|
||||
local script_path=$1
|
||||
local script_name=$(basename "$script_path")
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo -e "${CYAN}[RUNNING]${NC} $script_name"
|
||||
echo "============================================================"
|
||||
|
||||
# Даём скрипту права на выполнение
|
||||
chmod +x "$script_path" 2>/dev/null
|
||||
|
||||
# Запускаем тест
|
||||
if bash "$script_path"; then
|
||||
echo ""
|
||||
echo -e "${GREEN}[PASSED]${NC} $script_name"
|
||||
return 0
|
||||
else
|
||||
echo ""
|
||||
echo -e "${RED}[FAILED]${NC} $script_name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Главная логика
|
||||
# ============================================================================
|
||||
main() {
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB FULL API TEST SUITE"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
# Проверяем, не запущен ли уже сервер
|
||||
if is_server_running; then
|
||||
log_warning "Server is already running on port 8080"
|
||||
read -p "Use existing server? [Y/n]: " USE_EXISTING
|
||||
if [[ "$USE_EXISTING" =~ ^[Nn] ]]; then
|
||||
log_error "Please stop the existing server first: make stop"
|
||||
exit 1
|
||||
fi
|
||||
log_info "Using existing server"
|
||||
else
|
||||
# Проверяем, свободен ли порт
|
||||
if check_port 8080; then
|
||||
log_error "Port 8080 is in use by another process"
|
||||
log_info "Please free the port or stop the other process"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Запускаем сервер
|
||||
if ! start_server; then
|
||||
log_error "Failed to start server"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "Server is ready at $BASE_URL"
|
||||
|
||||
# Получаем список всех тестовых скриптов
|
||||
TEST_SCRIPTS=$(find "$SCRIPTS_DIR" -maxdepth 1 -name "test_*.sh" ! -name "test_all.sh" ! -name "test_runner.sh" -type f | sort)
|
||||
|
||||
if [ -z "$TEST_SCRIPTS" ]; then
|
||||
log_warning "No test scripts found in $SCRIPTS_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "Found test scripts:"
|
||||
for script in $TEST_SCRIPTS; do
|
||||
echo " - $(basename "$script")"
|
||||
done
|
||||
|
||||
# Счётчики времени
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
# Запускаем все тесты
|
||||
for script in $TEST_SCRIPTS; do
|
||||
if run_test_script "$script"; then
|
||||
((PASSED++))
|
||||
else
|
||||
((FAILED++))
|
||||
fi
|
||||
done
|
||||
|
||||
END_TIME=$(date +%s)
|
||||
DURATION=$((END_TIME - START_TIME))
|
||||
|
||||
# Останавливаем сервер, если мы его запускали
|
||||
if [ "$SERVER_STARTED" = true ]; then
|
||||
echo ""
|
||||
stop_server
|
||||
fi
|
||||
|
||||
# Итоговый отчёт
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo " TEST SUMMARY"
|
||||
echo "============================================================"
|
||||
echo -e "Total scripts: $((PASSED + FAILED))"
|
||||
echo -e "${GREEN}Passed: $PASSED${NC}"
|
||||
echo -e "${RED}Failed: $FAILED${NC}"
|
||||
if [ $SKIPPED -gt 0 ]; then
|
||||
echo -e "${YELLOW}Skipped: $SKIPPED${NC}"
|
||||
fi
|
||||
echo -e "Duration: ${DURATION}s"
|
||||
echo "============================================================"
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo ""
|
||||
echo -e "${GREEN}🎉 ALL TESTS PASSED!${NC}"
|
||||
return 0
|
||||
else
|
||||
echo ""
|
||||
echo -e "${RED}❌ SOME TESTS FAILED${NC}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Запуск
|
||||
main "$@"
|
||||
exit $?
|
||||
@@ -1,217 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1
|
||||
local data=$2
|
||||
local token=$3
|
||||
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1
|
||||
local token=$2
|
||||
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" \
|
||||
-H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB AUTHENTICATION API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Checking if server is running..."
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_error "Server is not running"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Server is running"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Healthcheck"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/health" "")
|
||||
if echo "$response" | grep -q "ok"; then
|
||||
log_success "Healthcheck passed: $response"
|
||||
else
|
||||
log_error "Healthcheck failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Register new user"
|
||||
log_info "============================================================"
|
||||
|
||||
TEST_EMAIL="test_auth_$(date +%s)@example.com"
|
||||
TEST_PASSWORD="testpass123"
|
||||
|
||||
log_info "Registering $TEST_EMAIL..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$TEST_EMAIL\",\"password\":\"$TEST_PASSWORD\"}" "")
|
||||
|
||||
if echo "$response" | grep -q "token"; then
|
||||
TOKEN=$(extract_json "$response" "token")
|
||||
USER_ID=$(extract_json "$response" "id")
|
||||
log_success "Registration successful"
|
||||
log_info "User ID: $USER_ID"
|
||||
log_info "Token: ${TOKEN:0:30}..."
|
||||
else
|
||||
log_error "Registration failed: $response"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: Register with existing email (should fail)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$TEST_EMAIL\",\"password\":\"$TEST_PASSWORD\"}" "")
|
||||
if echo "$response" | grep -q "already exists"; then
|
||||
log_success "Duplicate registration correctly rejected"
|
||||
else
|
||||
log_error "Duplicate registration not rejected: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: Login with correct credentials"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/login" "{\"email\":\"$TEST_EMAIL\",\"password\":\"$TEST_PASSWORD\"}" "")
|
||||
|
||||
if echo "$response" | grep -q "token"; then
|
||||
LOGIN_TOKEN=$(extract_json "$response" "token")
|
||||
REFRESH_TOKEN=$(extract_json "$response" "refresh_token")
|
||||
log_success "Login successful"
|
||||
log_info "Refresh token received: ${REFRESH_TOKEN:0:30}..."
|
||||
else
|
||||
log_error "Login failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 5: Login with wrong password (should fail)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/login" "{\"email\":\"$TEST_EMAIL\",\"password\":\"wrongpassword\"}" "")
|
||||
if echo "$response" | grep -q "Invalid credentials"; then
|
||||
log_success "Wrong password correctly rejected"
|
||||
else
|
||||
log_error "Wrong password not rejected: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 6: Get user profile with valid token"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/user/me" "$TOKEN")
|
||||
if echo "$response" | grep -q "$TEST_EMAIL"; then
|
||||
log_success "Profile retrieved successfully"
|
||||
log_info "Response: $response"
|
||||
else
|
||||
log_error "Profile retrieval failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 7: Get user profile with invalid token"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/user/me" "invalid.token.here")
|
||||
if echo "$response" | grep -q "Invalid token"; then
|
||||
log_success "Invalid token correctly rejected"
|
||||
else
|
||||
log_error "Invalid token not rejected: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 8: Get user profile without token"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/user/me" "")
|
||||
if echo "$response" | grep -q "Missing or invalid Authorization"; then
|
||||
log_success "Missing token correctly rejected"
|
||||
else
|
||||
log_error "Missing token not rejected: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 9: Refresh token"
|
||||
log_info "============================================================"
|
||||
|
||||
if [ -n "$REFRESH_TOKEN" ]; then
|
||||
response=$(http_post "$BASE_URL/v1/refresh" "{\"refresh_token\":\"$REFRESH_TOKEN\"}" "")
|
||||
if echo "$response" | grep -q "token"; then
|
||||
NEW_TOKEN=$(extract_json "$response" "token")
|
||||
NEW_REFRESH=$(extract_json "$response" "refresh_token")
|
||||
log_success "Token refreshed successfully"
|
||||
log_info "New token: ${NEW_TOKEN:0:30}..."
|
||||
log_info "New refresh token: ${NEW_REFRESH:0:30}..."
|
||||
else
|
||||
log_error "Token refresh failed: $response"
|
||||
fi
|
||||
|
||||
log_info "Trying to reuse old refresh token (should fail)..."
|
||||
response=$(http_post "$BASE_URL/v1/refresh" "{\"refresh_token\":\"$REFRESH_TOKEN\"}" "")
|
||||
if echo "$response" | grep -q "Invalid refresh token"; then
|
||||
log_success "Old refresh token correctly rejected"
|
||||
else
|
||||
log_warning "Old refresh token not rejected: $response"
|
||||
fi
|
||||
else
|
||||
log_warning "No refresh token to test"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 10: Access protected endpoint with new token"
|
||||
log_info "============================================================"
|
||||
|
||||
if [ -n "$NEW_TOKEN" ]; then
|
||||
response=$(http_get "$BASE_URL/v1/user/me" "$NEW_TOKEN")
|
||||
if echo "$response" | grep -q "$TEST_EMAIL"; then
|
||||
log_success "Protected endpoint accessible with new token"
|
||||
else
|
||||
log_error "Protected endpoint not accessible: $response"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "AUTHENTICATION TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
@@ -1,265 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1
|
||||
local data=$2
|
||||
local token=$3
|
||||
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1
|
||||
local token=$2
|
||||
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" \
|
||||
-H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
http_put() {
|
||||
local url=$1
|
||||
local data=$2
|
||||
local token=$3
|
||||
|
||||
curl -s -X PUT "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-d "$data"
|
||||
}
|
||||
|
||||
http_delete() {
|
||||
local url=$1
|
||||
local token=$2
|
||||
|
||||
curl -s -X DELETE "$url" \
|
||||
-H "Authorization: Bearer $token"
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB BOOKING API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Checking if server is running..."
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_error "Server is not running"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Server is running"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 1: Create test users"
|
||||
log_info "============================================================"
|
||||
|
||||
OWNER_EMAIL="owner_test@example.com"
|
||||
OWNER_PASSWORD="owner123"
|
||||
PARTICIPANT_EMAIL="participant_test@example.com"
|
||||
PARTICIPANT_PASSWORD="participant123"
|
||||
|
||||
# Пробуем зарегистрировать владельца
|
||||
log_info "Creating calendar owner..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$OWNER_EMAIL\",\"password\":\"$OWNER_PASSWORD\"}" "")
|
||||
|
||||
if echo "$response" | grep -q "token"; then
|
||||
OWNER_TOKEN=$(extract_json "$response" "token")
|
||||
OWNER_ID=$(extract_json "$response" "id")
|
||||
log_success "Owner registered: $OWNER_EMAIL"
|
||||
else
|
||||
log_info "Owner exists, trying login..."
|
||||
response=$(http_post "$BASE_URL/v1/login" "{\"email\":\"$OWNER_EMAIL\",\"password\":\"$OWNER_PASSWORD\"}" "")
|
||||
OWNER_TOKEN=$(extract_json "$response" "token")
|
||||
OWNER_ID=$(extract_json "$response" "id")
|
||||
fi
|
||||
|
||||
if [ -z "$OWNER_TOKEN" ]; then
|
||||
log_error "Failed to get owner token"
|
||||
echo "$response"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Owner ready (ID: $OWNER_ID)"
|
||||
|
||||
# Пробуем зарегистрировать участника
|
||||
log_info "Creating participant..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$PARTICIPANT_EMAIL\",\"password\":\"$PARTICIPANT_PASSWORD\"}" "")
|
||||
|
||||
if echo "$response" | grep -q "token"; then
|
||||
PARTICIPANT_TOKEN=$(extract_json "$response" "token")
|
||||
PARTICIPANT_ID=$(extract_json "$response" "id")
|
||||
log_success "Participant registered: $PARTICIPANT_EMAIL"
|
||||
else
|
||||
log_info "Participant exists, trying login..."
|
||||
response=$(http_post "$BASE_URL/v1/login" "{\"email\":\"$PARTICIPANT_EMAIL\",\"password\":\"$PARTICIPANT_PASSWORD\"}" "")
|
||||
PARTICIPANT_TOKEN=$(extract_json "$response" "token")
|
||||
PARTICIPANT_ID=$(extract_json "$response" "id")
|
||||
fi
|
||||
|
||||
if [ -z "$PARTICIPANT_TOKEN" ]; then
|
||||
log_error "Failed to get participant token"
|
||||
echo "$response"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Participant ready (ID: $PARTICIPANT_ID)"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 2: Create calendars"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Creating AUTO calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"Auto Calendar\",\"confirmation\":\"auto\"}" "$OWNER_TOKEN")
|
||||
AUTO_CALENDAR_ID=$(extract_json "$response" "id")
|
||||
log_success "Auto calendar: $AUTO_CALENDAR_ID"
|
||||
|
||||
log_info "Creating MANUAL calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"Manual Calendar\",\"confirmation\":\"manual\"}" "$OWNER_TOKEN")
|
||||
MANUAL_CALENDAR_ID=$(extract_json "$response" "id")
|
||||
log_success "Manual calendar: $MANUAL_CALENDAR_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 3: Create events"
|
||||
log_info "============================================================"
|
||||
|
||||
EVENT_START="2026-05-01T10:00:00Z"
|
||||
|
||||
log_info "Creating event in AUTO calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$AUTO_CALENDAR_ID/events" \
|
||||
"{\"title\":\"Auto Event\",\"start_time\":\"$EVENT_START\",\"duration\":60,\"capacity\":10}" "$OWNER_TOKEN")
|
||||
AUTO_EVENT_ID=$(extract_json "$response" "id")
|
||||
log_success "Auto event: $AUTO_EVENT_ID"
|
||||
|
||||
log_info "Creating event in MANUAL calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$MANUAL_CALENDAR_ID/events" \
|
||||
"{\"title\":\"Manual Event\",\"start_time\":\"$EVENT_START\",\"duration\":60,\"capacity\":10}" "$OWNER_TOKEN")
|
||||
MANUAL_EVENT_ID=$(extract_json "$response" "id")
|
||||
log_success "Manual event: $MANUAL_EVENT_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 4: Test AUTO confirmation"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Participant booking AUTO event..."
|
||||
response=$(http_post "$BASE_URL/v1/events/$AUTO_EVENT_ID/bookings" "" "$PARTICIPANT_TOKEN")
|
||||
echo "Response: $response"
|
||||
AUTO_BOOKING_STATUS=$(extract_json "$response" "status")
|
||||
|
||||
if [ "$AUTO_BOOKING_STATUS" = "confirmed" ]; then
|
||||
log_success "Auto-booking confirmed immediately"
|
||||
else
|
||||
log_error "Auto-booking status: $AUTO_BOOKING_STATUS"
|
||||
fi
|
||||
|
||||
# Сохраняем ID авто-бронирования
|
||||
AUTO_BOOKING_ID=$(extract_json "$response" "id")
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 5: Test MANUAL confirmation"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Participant booking MANUAL event..."
|
||||
response=$(http_post "$BASE_URL/v1/events/$MANUAL_EVENT_ID/bookings" "" "$PARTICIPANT_TOKEN")
|
||||
MANUAL_BOOKING_ID=$(extract_json "$response" "id")
|
||||
MANUAL_BOOKING_STATUS=$(extract_json "$response" "status")
|
||||
|
||||
if [ "$MANUAL_BOOKING_STATUS" = "pending" ]; then
|
||||
log_success "Manual-booking is pending: $MANUAL_BOOKING_ID"
|
||||
else
|
||||
log_error "Manual-booking status: $MANUAL_BOOKING_STATUS"
|
||||
fi
|
||||
|
||||
log_info "Owner confirming booking..."
|
||||
response=$(http_put "$BASE_URL/v1/bookings/$MANUAL_BOOKING_ID" "{\"action\":\"confirm\"}" "$OWNER_TOKEN")
|
||||
CONFIRMED_STATUS=$(extract_json "$response" "status")
|
||||
|
||||
if [ "$CONFIRMED_STATUS" = "confirmed" ]; then
|
||||
log_success "Booking confirmed by owner"
|
||||
else
|
||||
log_error "Confirmation failed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 6: Test booking lists"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Owner viewing event bookings..."
|
||||
response=$(http_get "$BASE_URL/v1/events/$MANUAL_EVENT_ID/bookings" "$OWNER_TOKEN")
|
||||
echo "Response: $response"
|
||||
|
||||
log_info "Participant viewing their bookings..."
|
||||
response=$(http_get "$BASE_URL/v1/user/bookings" "$PARTICIPANT_TOKEN")
|
||||
echo "Response: $response"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 7: Test booking cancellation"
|
||||
log_info "============================================================"
|
||||
|
||||
# Используем первое бронирование для отмены
|
||||
if [ -n "$AUTO_BOOKING_ID" ]; then
|
||||
CANCEL_BOOKING_ID="$AUTO_BOOKING_ID"
|
||||
log_info "Using auto-booking for cancellation: $CANCEL_BOOKING_ID"
|
||||
else
|
||||
# Создаём новое событие для теста отмены
|
||||
log_info "Creating new event for cancellation test..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$MANUAL_CALENDAR_ID/events" \
|
||||
"{\"title\":\"Cancel Test Event\",\"start_time\":\"$EVENT_START\",\"duration\":60,\"capacity\":10}" "$OWNER_TOKEN")
|
||||
CANCEL_EVENT_ID=$(extract_json "$response" "id")
|
||||
log_info "Event created: $CANCEL_EVENT_ID"
|
||||
|
||||
log_info "Creating booking to cancel..."
|
||||
response=$(http_post "$BASE_URL/v1/events/$CANCEL_EVENT_ID/bookings" "" "$PARTICIPANT_TOKEN")
|
||||
CANCEL_BOOKING_ID=$(extract_json "$response" "id")
|
||||
log_info "Created: $CANCEL_BOOKING_ID"
|
||||
fi
|
||||
|
||||
if [ -n "$CANCEL_BOOKING_ID" ]; then
|
||||
log_info "Cancelling booking $CANCEL_BOOKING_ID..."
|
||||
response=$(http_delete "$BASE_URL/v1/bookings/$CANCEL_BOOKING_ID" "$PARTICIPANT_TOKEN")
|
||||
CANCELLED_STATUS=$(extract_json "$response" "status")
|
||||
|
||||
if [ "$CANCELLED_STATUS" = "cancelled" ]; then
|
||||
log_success "Booking cancelled"
|
||||
else
|
||||
log_error "Cancellation failed: $response"
|
||||
fi
|
||||
else
|
||||
log_error "No booking to cancel"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
@@ -1,217 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1
|
||||
local data=$2
|
||||
local token=$3
|
||||
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1
|
||||
local token=$2
|
||||
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" \
|
||||
-H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
http_put() {
|
||||
local url=$1
|
||||
local data=$2
|
||||
local token=$3
|
||||
|
||||
curl -s -X PUT "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-d "$data"
|
||||
}
|
||||
|
||||
http_delete() {
|
||||
local url=$1; local token=$2
|
||||
curl -s -X DELETE "$url" -H "Authorization: Bearer $token"
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB CALENDAR API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Setting up test users..."
|
||||
|
||||
# Создаём двух пользователей
|
||||
OWNER_EMAIL="calendar_owner_$(date +%s)@example.com"
|
||||
OWNER_PASS="owner123"
|
||||
OTHER_EMAIL="calendar_other_$(date +%s)@example.com"
|
||||
OTHER_PASS="other123"
|
||||
|
||||
# Владелец
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$OWNER_EMAIL\",\"password\":\"$OWNER_PASS\"}" "")
|
||||
OWNER_TOKEN=$(extract_json "$response" "token")
|
||||
OWNER_ID=$(extract_json "$response" "id")
|
||||
log_success "Owner created: $OWNER_ID"
|
||||
|
||||
# Другой пользователь
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$OTHER_EMAIL\",\"password\":\"$OTHER_PASS\"}" "")
|
||||
OTHER_TOKEN=$(extract_json "$response" "token")
|
||||
OTHER_ID=$(extract_json "$response" "id")
|
||||
log_success "Other user created: $OTHER_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Create calendar"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"My Personal Calendar\",\"description\":\"Test description\"}" "$OWNER_TOKEN")
|
||||
CALENDAR_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$CALENDAR_ID" ]; then
|
||||
log_success "Calendar created: $CALENDAR_ID"
|
||||
else
|
||||
log_error "Calendar creation failed: $response"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Create commercial calendar"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"Commercial Calendar\",\"type\":\"commercial\"}" "$OWNER_TOKEN")
|
||||
COMMERCIAL_ID=$(extract_json "$response" "id")
|
||||
log_success "Commercial calendar created: $COMMERCIAL_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: List calendars (owner)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/calendars" "$OWNER_TOKEN")
|
||||
COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
log_success "Owner sees $COUNT calendars"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: List calendars (other user - empty)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/calendars" "$OTHER_TOKEN")
|
||||
COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
log_success "Other user sees $COUNT calendars"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 5: Get calendar by ID (owner)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/calendars/$CALENDAR_ID" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "My Personal Calendar"; then
|
||||
log_success "Owner can access personal calendar"
|
||||
else
|
||||
log_error "Owner cannot access calendar: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 6: Get personal calendar (other user - denied)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/calendars/$CALENDAR_ID" "$OTHER_TOKEN")
|
||||
if echo "$response" | grep -q "Access denied"; then
|
||||
log_success "Other user correctly denied access to personal calendar"
|
||||
else
|
||||
log_error "Access control failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 7: Get commercial calendar (other user - allowed)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/calendars/$COMMERCIAL_ID" "$OTHER_TOKEN")
|
||||
if echo "$response" | grep -q "Commercial Calendar"; then
|
||||
log_success "Other user can access commercial calendar"
|
||||
else
|
||||
log_error "Other user cannot access commercial calendar: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 8: Update calendar (owner)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_put "$BASE_URL/v1/calendars/$CALENDAR_ID" "{\"title\":\"Updated Calendar\"}" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Updated Calendar"; then
|
||||
log_success "Calendar updated successfully"
|
||||
else
|
||||
log_error "Calendar update failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 9: Update calendar (other user - denied)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_put "$BASE_URL/v1/calendars/$CALENDAR_ID" "{\"title\":\"Hacked\"}" "$OTHER_TOKEN")
|
||||
if echo "$response" | grep -q "Access denied"; then
|
||||
log_success "Other user correctly denied update"
|
||||
else
|
||||
log_error "Access control failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 10: Delete calendar (owner)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_delete "$BASE_URL/v1/calendars/$CALENDAR_ID" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "deleted"; then
|
||||
log_success "Calendar deleted"
|
||||
else
|
||||
log_error "Calendar deletion failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 11: Get deleted calendar (should be denied)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/calendars/$CALENDAR_ID" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Access denied"; then
|
||||
log_success "Deleted calendar not accessible"
|
||||
else
|
||||
log_error "Deleted calendar still accessible: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "CALENDAR API TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
@@ -1,212 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1
|
||||
local data=$2
|
||||
local token=$3
|
||||
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1
|
||||
local token=$2
|
||||
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" \
|
||||
-H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
http_put() {
|
||||
local url=$1
|
||||
local data=$2
|
||||
local token=$3
|
||||
|
||||
curl -s -X PUT "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-d "$data"
|
||||
}
|
||||
|
||||
http_delete() {
|
||||
local url=$1; local token=$2
|
||||
curl -s -X DELETE "$url" -H "Authorization: Bearer $token"
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB EVENT API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Setting up test users and calendar..."
|
||||
|
||||
OWNER_EMAIL="event_owner_$(date +%s)@example.com"
|
||||
OWNER_PASS="owner123"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$OWNER_EMAIL\",\"password\":\"$OWNER_PASS\"}" "")
|
||||
OWNER_TOKEN=$(extract_json "$response" "token")
|
||||
OWNER_ID=$(extract_json "$response" "id")
|
||||
log_success "Owner created"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"Test Calendar\"}" "$OWNER_TOKEN")
|
||||
CALENDAR_ID=$(extract_json "$response" "id")
|
||||
log_success "Calendar created: $CALENDAR_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Create single event"
|
||||
log_info "============================================================"
|
||||
|
||||
EVENT_START="2026-06-01T10:00:00Z"
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$CALENDAR_ID/events" \
|
||||
"{\"title\":\"Single Event\",\"start_time\":\"$EVENT_START\",\"duration\":60}" "$OWNER_TOKEN")
|
||||
EVENT_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$EVENT_ID" ]; then
|
||||
log_success "Single event created: $EVENT_ID"
|
||||
else
|
||||
log_error "Event creation failed: $response"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Create event with capacity"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$CALENDAR_ID/events" \
|
||||
"{\"title\":\"Capacity Event\",\"start_time\":\"$EVENT_START\",\"duration\":60,\"capacity\":10}" "$OWNER_TOKEN")
|
||||
CAPACITY_EVENT_ID=$(extract_json "$response" "id")
|
||||
log_success "Event with capacity created: $CAPACITY_EVENT_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: Create event in past (should fail)"
|
||||
log_info "============================================================"
|
||||
|
||||
PAST_START="2020-01-01T10:00:00Z"
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$CALENDAR_ID/events" \
|
||||
"{\"title\":\"Past Event\",\"start_time\":\"$PAST_START\",\"duration\":60}" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "past"; then
|
||||
log_success "Past event correctly rejected"
|
||||
else
|
||||
log_error "Past event not rejected: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: List events"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/calendars/$CALENDAR_ID/events" "$OWNER_TOKEN")
|
||||
COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
log_success "Found $COUNT events"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 5: Get event by ID"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/events/$EVENT_ID" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Single Event"; then
|
||||
log_success "Event retrieved successfully"
|
||||
else
|
||||
log_error "Event retrieval failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 6: Update event"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_put "$BASE_URL/v1/events/$EVENT_ID" "{\"title\":\"Updated Event\"}" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Updated Event"; then
|
||||
log_success "Event updated"
|
||||
else
|
||||
log_error "Event update failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 7: Delete event"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_delete "$BASE_URL/v1/events/$EVENT_ID" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "deleted"; then
|
||||
log_success "Event deleted"
|
||||
else
|
||||
log_error "Event deletion failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 8: Get deleted event (should fail)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/events/$EVENT_ID" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "not found"; then
|
||||
log_success "Deleted event not found"
|
||||
else
|
||||
log_error "Deleted event still accessible: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 9: Create recurring event"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$CALENDAR_ID/events" \
|
||||
"{\"title\":\"Weekly Meeting\",\"start_time\":\"$EVENT_START\",\"duration\":60,\"recurrence\":{\"freq\":\"WEEKLY\",\"interval\":1}}" "$OWNER_TOKEN")
|
||||
RECURRING_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$RECURRING_ID" ]; then
|
||||
log_success "Recurring event created: $RECURRING_ID"
|
||||
else
|
||||
log_error "Recurring event creation failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 10: Get occurrences"
|
||||
log_info "============================================================"
|
||||
|
||||
FROM="2026-06-01T00:00:00Z"
|
||||
TO="2026-06-30T00:00:00Z"
|
||||
response=$(http_get "$BASE_URL/v1/events/$RECURRING_ID/occurrences?from=$FROM&to=$TO" "$OWNER_TOKEN")
|
||||
if [ -n "$response" ] && [ "$response" != "[]" ]; then
|
||||
log_success "Occurrences retrieved"
|
||||
else
|
||||
log_error "Occurrences retrieval failed: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "EVENT API TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
@@ -1,370 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1; local token=$2
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" -H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
http_put() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
curl -s -X PUT "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
}
|
||||
|
||||
http_delete() {
|
||||
local url=$1; local token=$2
|
||||
curl -s -X DELETE "$url" -H "Authorization: Bearer $token"
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB MODERATION API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Checking if server is running..."
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_error "Server is not running"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Server is running"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 1: Create test users"
|
||||
log_info "============================================================"
|
||||
|
||||
# Админ (первый пользователь)
|
||||
ADMIN_EMAIL="mod_admin_$(date +%s)@example.com"
|
||||
ADMIN_PASSWORD="admin123"
|
||||
|
||||
log_info "Creating admin user..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}" "")
|
||||
ADMIN_TOKEN=$(extract_json "$response" "token")
|
||||
ADMIN_ID=$(extract_json "$response" "id")
|
||||
log_success "Admin created"
|
||||
|
||||
# Владелец календаря
|
||||
OWNER_EMAIL="mod_owner_$(date +%s)@example.com"
|
||||
OWNER_PASSWORD="owner123"
|
||||
|
||||
log_info "Creating calendar owner..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$OWNER_EMAIL\",\"password\":\"$OWNER_PASSWORD\"}" "")
|
||||
OWNER_TOKEN=$(extract_json "$response" "token")
|
||||
OWNER_ID=$(extract_json "$response" "id")
|
||||
log_success "Owner created"
|
||||
|
||||
# Пользователь 1 (репортер)
|
||||
USER1_EMAIL="mod_user1_$(date +%s)@example.com"
|
||||
USER1_PASSWORD="user1_123"
|
||||
|
||||
log_info "Creating user 1..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$USER1_EMAIL\",\"password\":\"$USER1_PASSWORD\"}" "")
|
||||
USER1_TOKEN=$(extract_json "$response" "token")
|
||||
log_success "User 1 created"
|
||||
|
||||
# Пользователь 2 (репортер)
|
||||
USER2_EMAIL="mod_user2_$(date +%s)@example.com"
|
||||
USER2_PASSWORD="user2_123"
|
||||
|
||||
log_info "Creating user 2..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$USER2_EMAIL\",\"password\":\"$USER2_PASSWORD\"}" "")
|
||||
USER2_TOKEN=$(extract_json "$response" "token")
|
||||
log_success "User 2 created"
|
||||
|
||||
# Пользователь 3 (для третьего репорта)
|
||||
USER3_EMAIL="mod_user3_$(date +%s)@example.com"
|
||||
USER3_PASSWORD="user3_123"
|
||||
|
||||
log_info "Creating user 3..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$USER3_EMAIL\",\"password\":\"$USER3_PASSWORD\"}" "")
|
||||
USER3_TOKEN=$(extract_json "$response" "token")
|
||||
log_success "User 3 created"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 2: Create calendar and event"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Creating calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" \
|
||||
"{\"title\":\"Moderation Test Calendar\"}" "$OWNER_TOKEN")
|
||||
CALENDAR_ID=$(extract_json "$response" "id")
|
||||
log_success "Calendar created: $CALENDAR_ID"
|
||||
|
||||
log_info "Creating event..."
|
||||
EVENT_START="2026-06-01T10:00:00Z"
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$CALENDAR_ID/events" \
|
||||
"{\"title\":\"Test Event\",\"start_time\":\"$EVENT_START\",\"duration\":60}" "$OWNER_TOKEN")
|
||||
EVENT_ID=$(extract_json "$response" "id")
|
||||
log_success "Event created: $EVENT_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Create report for event"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User 1 reporting event..."
|
||||
response=$(http_post "$BASE_URL/v1/reports" \
|
||||
"{\"target_type\":\"event\",\"target_id\":\"$EVENT_ID\",\"reason\":\"Inappropriate content\"}" "$USER1_TOKEN")
|
||||
REPORT1_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$REPORT1_ID" ]; then
|
||||
log_success "Report created: $REPORT1_ID"
|
||||
else
|
||||
log_error "Failed to create report: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Create second report"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User 2 reporting same event..."
|
||||
response=$(http_post "$BASE_URL/v1/reports" \
|
||||
"{\"target_type\":\"event\",\"target_id\":\"$EVENT_ID\",\"reason\":\"Spam\"}" "$USER2_TOKEN")
|
||||
REPORT2_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$REPORT2_ID" ]; then
|
||||
log_success "Second report created: $REPORT2_ID"
|
||||
else
|
||||
log_error "Failed to create report: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: Admin views all reports"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin getting all reports..."
|
||||
response=$(http_get "$BASE_URL/v1/admin/reports" "$ADMIN_TOKEN")
|
||||
REPORT_COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
|
||||
if [ "$REPORT_COUNT" -ge 2 ]; then
|
||||
log_success "Admin sees $REPORT_COUNT reports"
|
||||
else
|
||||
log_error "Admin should see reports, found $REPORT_COUNT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: Admin views reports for specific event"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin getting reports for event..."
|
||||
response=$(http_get "$BASE_URL/v1/admin/reports?target_type=event&target_id=$EVENT_ID" "$ADMIN_TOKEN")
|
||||
EVENT_REPORT_COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
|
||||
if [ "$EVENT_REPORT_COUNT" -eq 2 ]; then
|
||||
log_success "Admin sees $EVENT_REPORT_COUNT reports for event"
|
||||
else
|
||||
log_error "Expected 2 reports, found $EVENT_REPORT_COUNT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 5: Auto-freeze by reports (threshold 3)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User 3 creating third report for event..."
|
||||
response=$(http_post "$BASE_URL/v1/reports" \
|
||||
"{\"target_type\":\"event\",\"target_id\":\"$EVENT_ID\",\"reason\":\"Bad content\"}" "$USER3_TOKEN")
|
||||
REPORT3_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$REPORT3_ID" ]; then
|
||||
log_success "Third report created: $REPORT3_ID"
|
||||
else
|
||||
log_error "Failed to create third report"
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
log_info "Checking if event was auto-frozen..."
|
||||
response=$(http_get "$BASE_URL/v1/events/$EVENT_ID" "$OWNER_TOKEN")
|
||||
EVENT_STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
|
||||
if [ "$EVENT_STATUS" = "frozen" ]; then
|
||||
log_success "Event auto-frozen after 3 reports"
|
||||
else
|
||||
log_error "Event not auto-frozen: status=$EVENT_STATUS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 6: Admin resolves report (review)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin reviewing first report..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/reports/$REPORT1_ID" \
|
||||
"{\"action\":\"review\"}" "$ADMIN_TOKEN")
|
||||
|
||||
STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
if [ "$STATUS" = "reviewed" ]; then
|
||||
log_success "Report marked as reviewed"
|
||||
else
|
||||
log_error "Failed to review report: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 7: Admin resolves report (dismiss)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin dismissing second report..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/reports/$REPORT2_ID" \
|
||||
"{\"action\":\"dismiss\"}" "$ADMIN_TOKEN")
|
||||
|
||||
STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
if [ "$STATUS" = "dismissed" ]; then
|
||||
log_success "Report dismissed"
|
||||
else
|
||||
log_error "Failed to dismiss report: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 8: Admin adds banned words"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin adding banned word 'spam'..."
|
||||
response=$(http_post "$BASE_URL/v1/admin/banned-words" \
|
||||
"{\"word\":\"spam\"}" "$ADMIN_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "added"; then
|
||||
log_success "Banned word added"
|
||||
else
|
||||
log_error "Failed to add banned word: $response"
|
||||
fi
|
||||
|
||||
log_info "Admin adding banned word 'inappropriate'..."
|
||||
http_post "$BASE_URL/v1/admin/banned-words" "{\"word\":\"inappropriate\"}" "$ADMIN_TOKEN" > /dev/null
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 9: Admin lists banned words"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin getting banned words..."
|
||||
response=$(http_get "$BASE_URL/v1/admin/banned-words" "$ADMIN_TOKEN")
|
||||
if echo "$response" | grep -q "spam"; then
|
||||
log_success "Banned words retrieved"
|
||||
else
|
||||
log_error "Failed to get banned words: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 10: Admin removes banned word"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin removing banned word 'inappropriate'..."
|
||||
response=$(http_delete "$BASE_URL/v1/admin/banned-words/inappropriate" "$ADMIN_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "removed"; then
|
||||
log_success "Banned word removed"
|
||||
else
|
||||
log_error "Failed to remove banned word: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 11: Admin freezes calendar"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin freezing calendar..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/calendars/$CALENDAR_ID" \
|
||||
"{\"action\":\"freeze\"}" "$ADMIN_TOKEN")
|
||||
|
||||
CAL_STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
if [ "$CAL_STATUS" = "frozen" ]; then
|
||||
log_success "Calendar frozen"
|
||||
else
|
||||
log_error "Failed to freeze calendar: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 12: Admin unfreezes calendar"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin unfreezing calendar..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/calendars/$CALENDAR_ID" \
|
||||
"{\"action\":\"unfreeze\"}" "$ADMIN_TOKEN")
|
||||
|
||||
CAL_STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
if [ "$CAL_STATUS" = "active" ]; then
|
||||
log_success "Calendar unfrozen"
|
||||
else
|
||||
log_error "Failed to unfreeze calendar: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 13: Admin freezes event"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin freezing event..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/events/$EVENT_ID" \
|
||||
"{\"action\":\"freeze\"}" "$ADMIN_TOKEN")
|
||||
|
||||
EVENT_STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
if [ "$EVENT_STATUS" = "frozen" ]; then
|
||||
log_success "Event frozen"
|
||||
else
|
||||
log_error "Failed to freeze event: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 14: Admin unfreezes event"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin unfreezing event..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/events/$EVENT_ID" \
|
||||
"{\"action\":\"unfreeze\"}" "$ADMIN_TOKEN")
|
||||
|
||||
EVENT_STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
if [ "$EVENT_STATUS" = "active" ]; then
|
||||
log_success "Event unfrozen"
|
||||
else
|
||||
log_error "Failed to unfreeze event: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "MODERATION API TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
echo "Summary of created resources:"
|
||||
echo " Admin: $ADMIN_EMAIL"
|
||||
echo " Owner: $OWNER_EMAIL"
|
||||
echo " Calendar: $CALENDAR_ID"
|
||||
echo " Event: $EVENT_ID"
|
||||
echo ""
|
||||
@@ -1,454 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
extract_json_number() {
|
||||
echo "$1" | grep -o "\"$2\":[0-9.]*" | head -1 | sed "s/\"$2\"://"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1; local token=$2
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" -H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
http_put() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
curl -s -X PUT "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
}
|
||||
|
||||
http_delete() {
|
||||
local url=$1; local token=$2
|
||||
curl -s -X DELETE "$url" -H "Authorization: Bearer $token"
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB REVIEWS API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Checking if server is running..."
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_error "Server is not running"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Server is running"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 1: Create test users"
|
||||
log_info "============================================================"
|
||||
|
||||
# Админ (создаётся первым)
|
||||
ADMIN_EMAIL="admin_$(date +%s)@example.com"
|
||||
ADMIN_PASSWORD="admin123"
|
||||
|
||||
log_info "Creating admin user (first user)..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}" "")
|
||||
ADMIN_TOKEN=$(extract_json "$response" "token")
|
||||
ADMIN_ID=$(extract_json "$response" "id")
|
||||
log_success "Admin created: $ADMIN_EMAIL"
|
||||
|
||||
# Владелец календаря
|
||||
OWNER_EMAIL="review_owner_$(date +%s)@example.com"
|
||||
OWNER_PASSWORD="owner123"
|
||||
|
||||
log_info "Creating calendar owner..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$OWNER_EMAIL\",\"password\":\"$OWNER_PASSWORD\"}" "")
|
||||
OWNER_TOKEN=$(extract_json "$response" "token")
|
||||
OWNER_ID=$(extract_json "$response" "id")
|
||||
log_success "Owner created: $OWNER_EMAIL"
|
||||
|
||||
# Участник 1
|
||||
PARTICIPANT1_EMAIL="review_p1_$(date +%s)@example.com"
|
||||
PARTICIPANT1_PASSWORD="p1_123"
|
||||
|
||||
log_info "Creating participant 1..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$PARTICIPANT1_EMAIL\",\"password\":\"$PARTICIPANT1_PASSWORD\"}" "")
|
||||
PARTICIPANT1_TOKEN=$(extract_json "$response" "token")
|
||||
PARTICIPANT1_ID=$(extract_json "$response" "id")
|
||||
log_success "Participant 1 created"
|
||||
|
||||
# Участник 2
|
||||
PARTICIPANT2_EMAIL="review_p2_$(date +%s)@example.com"
|
||||
PARTICIPANT2_PASSWORD="p2_123"
|
||||
|
||||
log_info "Creating participant 2..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$PARTICIPANT2_EMAIL\",\"password\":\"$PARTICIPANT2_PASSWORD\"}" "")
|
||||
PARTICIPANT2_TOKEN=$(extract_json "$response" "token")
|
||||
PARTICIPANT2_ID=$(extract_json "$response" "id")
|
||||
log_success "Participant 2 created"
|
||||
|
||||
# Сторонний пользователь (без бронирований)
|
||||
OTHER_EMAIL="review_other_$(date +%s)@example.com"
|
||||
OTHER_PASSWORD="other123"
|
||||
|
||||
log_info "Creating other user (no bookings)..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$OTHER_EMAIL\",\"password\":\"$OTHER_PASSWORD\"}" "")
|
||||
OTHER_TOKEN=$(extract_json "$response" "token")
|
||||
OTHER_ID=$(extract_json "$response" "id")
|
||||
log_success "Other user created"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 2: Create calendar and events"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Creating calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" \
|
||||
"{\"title\":\"Review Test Calendar\",\"description\":\"Calendar for review tests\"}" "$OWNER_TOKEN")
|
||||
CALENDAR_ID=$(extract_json "$response" "id")
|
||||
log_success "Calendar created: $CALENDAR_ID"
|
||||
|
||||
log_info "Creating event..."
|
||||
EVENT_START="2026-06-01T10:00:00Z"
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$CALENDAR_ID/events" \
|
||||
"{\"title\":\"Test Event\",\"start_time\":\"$EVENT_START\",\"duration\":60,\"capacity\":10}" "$OWNER_TOKEN")
|
||||
EVENT_ID=$(extract_json "$response" "id")
|
||||
log_success "Event created: $EVENT_ID"
|
||||
|
||||
log_info "Creating second event..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$CALENDAR_ID/events" \
|
||||
"{\"title\":\"Test Event 2\",\"start_time\":\"$EVENT_START\",\"duration\":60}" "$OWNER_TOKEN")
|
||||
EVENT2_ID=$(extract_json "$response" "id")
|
||||
log_success "Second event created: $EVENT2_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 3: Create bookings"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Participant 1 booking event..."
|
||||
response=$(http_post "$BASE_URL/v1/events/$EVENT_ID/bookings" "" "$PARTICIPANT1_TOKEN")
|
||||
BOOKING1_ID=$(extract_json "$response" "id")
|
||||
log_success "Booking created: $BOOKING1_ID"
|
||||
|
||||
log_info "Owner confirming participant 1 booking..."
|
||||
response=$(http_put "$BASE_URL/v1/bookings/$BOOKING1_ID" "{\"action\":\"confirm\"}" "$OWNER_TOKEN")
|
||||
log_success "Booking confirmed"
|
||||
|
||||
log_info "Participant 2 booking event..."
|
||||
response=$(http_post "$BASE_URL/v1/events/$EVENT_ID/bookings" "" "$PARTICIPANT2_TOKEN")
|
||||
BOOKING2_ID=$(extract_json "$response" "id")
|
||||
log_success "Booking created: $BOOKING2_ID"
|
||||
|
||||
log_info "Owner confirming participant 2 booking..."
|
||||
response=$(http_put "$BASE_URL/v1/bookings/$BOOKING2_ID" "{\"action\":\"confirm\"}" "$OWNER_TOKEN")
|
||||
log_success "Booking confirmed"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Create review for event (participant)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Participant 1 creating review..."
|
||||
response=$(http_post "$BASE_URL/v1/reviews" \
|
||||
"{\"target_type\":\"event\",\"target_id\":\"$EVENT_ID\",\"rating\":5,\"comment\":\"Excellent event!\"}" "$PARTICIPANT1_TOKEN")
|
||||
REVIEW1_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$REVIEW1_ID" ]; then
|
||||
log_success "Review created: $REVIEW1_ID"
|
||||
else
|
||||
log_error "Failed to create review: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Create review for event (second participant)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Participant 2 creating review..."
|
||||
response=$(http_post "$BASE_URL/v1/reviews" \
|
||||
"{\"target_type\":\"event\",\"target_id\":\"$EVENT_ID\",\"rating\":3,\"comment\":\"It was okay\"}" "$PARTICIPANT2_TOKEN")
|
||||
REVIEW2_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$REVIEW2_ID" ]; then
|
||||
log_success "Review created: $REVIEW2_ID"
|
||||
else
|
||||
log_error "Failed to create review: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: Cannot review twice"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Participant 1 trying to review again..."
|
||||
response=$(http_post "$BASE_URL/v1/reviews" \
|
||||
"{\"target_type\":\"event\",\"target_id\":\"$EVENT_ID\",\"rating\":4,\"comment\":\"Trying again\"}" "$PARTICIPANT1_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "Already reviewed"; then
|
||||
log_success "Duplicate review correctly rejected"
|
||||
else
|
||||
log_error "Duplicate review not rejected: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: Cannot review without booking"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Other user trying to review event..."
|
||||
response=$(http_post "$BASE_URL/v1/reviews" \
|
||||
"{\"target_type\":\"event\",\"target_id\":\"$EVENT_ID\",\"rating\":5,\"comment\":\"Wasn't there\"}" "$OTHER_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "Cannot review"; then
|
||||
log_success "Review without booking correctly rejected"
|
||||
else
|
||||
log_error "Review without booking not rejected: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 5: Create review for calendar"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Other user creating calendar review..."
|
||||
response=$(http_post "$BASE_URL/v1/reviews" \
|
||||
"{\"target_type\":\"calendar\",\"target_id\":\"$CALENDAR_ID\",\"rating\":4,\"comment\":\"Nice calendar!\"}" "$OTHER_TOKEN")
|
||||
CALENDAR_REVIEW_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$CALENDAR_REVIEW_ID" ]; then
|
||||
log_success "Calendar review created: $CALENDAR_REVIEW_ID"
|
||||
else
|
||||
log_error "Failed to create calendar review: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 6: Get reviews for event"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Getting reviews for event..."
|
||||
response=$(http_get "$BASE_URL/v1/reviews?target_type=event&target_id=$EVENT_ID" "$PARTICIPANT1_TOKEN")
|
||||
REVIEW_COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
|
||||
if [ "$REVIEW_COUNT" -eq 2 ]; then
|
||||
log_success "Found $REVIEW_COUNT reviews for event"
|
||||
else
|
||||
log_error "Expected 2 reviews, found $REVIEW_COUNT"
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 7: Check event rating updated"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Checking event rating..."
|
||||
response=$(http_get "$BASE_URL/v1/events/$EVENT_ID" "$OWNER_TOKEN")
|
||||
RATING_AVG=$(extract_json_number "$response" "rating_avg")
|
||||
RATING_COUNT=$(extract_json_number "$response" "rating_count")
|
||||
|
||||
if [ "$RATING_AVG" = "4.0" ] && [ "$RATING_COUNT" = "2" ]; then
|
||||
log_success "Event rating updated: $RATING_AVG ($RATING_COUNT reviews)"
|
||||
else
|
||||
log_error "Event rating incorrect: avg=$RATING_AVG, count=$RATING_COUNT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 8: Update own review"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Participant 1 updating review..."
|
||||
response=$(http_put "$BASE_URL/v1/reviews/$REVIEW1_ID" \
|
||||
"{\"rating\":4,\"comment\":\"Updated: Very good!\"}" "$PARTICIPANT1_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "\"id\""; then
|
||||
log_success "Review updated"
|
||||
else
|
||||
log_error "Review update failed: $response"
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
log_info "Checking event rating after update..."
|
||||
response=$(http_get "$BASE_URL/v1/events/$EVENT_ID" "$OWNER_TOKEN")
|
||||
NEW_RATING_AVG=$(extract_json_number "$response" "rating_avg")
|
||||
|
||||
if [ "$NEW_RATING_AVG" = "3.5" ]; then
|
||||
log_success "Event rating updated to $NEW_RATING_AVG"
|
||||
else
|
||||
log_error "Event rating incorrect: $NEW_RATING_AVG (expected 3.5)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 9: Cannot update others review"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Participant 2 trying to update participant 1 review..."
|
||||
response=$(http_put "$BASE_URL/v1/reviews/$REVIEW1_ID" \
|
||||
"{\"rating\":1,\"comment\":\"Hacked!\"}" "$PARTICIPANT2_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "Access denied"; then
|
||||
log_success "Update others review correctly rejected"
|
||||
else
|
||||
log_error "Update others review not rejected: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 10: Get user reviews"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Getting participant 1 reviews..."
|
||||
response=$(http_get "$BASE_URL/v1/user/reviews" "$PARTICIPANT1_TOKEN")
|
||||
USER_REVIEW_COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
|
||||
if [ "$USER_REVIEW_COUNT" -ge 1 ]; then
|
||||
log_success "Found $USER_REVIEW_COUNT reviews for user"
|
||||
else
|
||||
log_error "User reviews not found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 11: Admin hides review"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin hiding review $REVIEW2_ID..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/reviews/$REVIEW2_ID" \
|
||||
"{\"action\":\"hide\"}" "$ADMIN_TOKEN")
|
||||
|
||||
HIDDEN_STATUS=$(extract_json "$response" "status")
|
||||
if [ "$HIDDEN_STATUS" = "hidden" ]; then
|
||||
log_success "Review hidden by admin"
|
||||
else
|
||||
log_error "Failed to hide review: $response"
|
||||
fi
|
||||
|
||||
log_info "Participant 1 getting event reviews (hidden should not appear)..."
|
||||
response=$(http_get "$BASE_URL/v1/reviews?target_type=event&target_id=$EVENT_ID" "$PARTICIPANT1_TOKEN")
|
||||
VISIBLE_COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
|
||||
if [ "$VISIBLE_COUNT" -eq 1 ]; then
|
||||
log_success "Only 1 review visible (hidden filtered out)"
|
||||
else
|
||||
log_error "Expected 1 visible review, found $VISIBLE_COUNT"
|
||||
fi
|
||||
|
||||
log_info "Admin getting event reviews (should see all)..."
|
||||
response=$(http_get "$BASE_URL/v1/reviews?target_type=event&target_id=$EVENT_ID" "$ADMIN_TOKEN")
|
||||
ADMIN_COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
|
||||
if [ "$ADMIN_COUNT" -eq 2 ]; then
|
||||
log_success "Admin sees all 2 reviews"
|
||||
else
|
||||
log_error "Admin should see 2 reviews, found $ADMIN_COUNT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 12: Admin unhides review"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin unhiding review..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/reviews/$REVIEW2_ID" \
|
||||
"{\"action\":\"unhide\"}" "$ADMIN_TOKEN")
|
||||
|
||||
UNHIDDEN_STATUS=$(extract_json "$response" "status")
|
||||
if [ "$UNHIDDEN_STATUS" = "visible" ]; then
|
||||
log_success "Review unhidden by admin"
|
||||
else
|
||||
log_error "Failed to unhide review: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 13: Delete own review"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Participant 1 deleting review..."
|
||||
response=$(http_delete "$BASE_URL/v1/reviews/$REVIEW1_ID" "$PARTICIPANT1_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "deleted"; then
|
||||
log_success "Review deleted"
|
||||
else
|
||||
log_error "Failed to delete review: $response"
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
log_info "Checking event rating after deletion..."
|
||||
response=$(http_get "$BASE_URL/v1/events/$EVENT_ID" "$OWNER_TOKEN")
|
||||
FINAL_RATING_AVG=$(extract_json_number "$response" "rating_avg")
|
||||
FINAL_RATING_COUNT=$(extract_json_number "$response" "rating_count")
|
||||
|
||||
if [ "$FINAL_RATING_AVG" = "3.0" ] && [ "$FINAL_RATING_COUNT" = "1" ]; then
|
||||
log_success "Event rating updated: $FINAL_RATING_AVG ($FINAL_RATING_COUNT review)"
|
||||
else
|
||||
log_error "Event rating incorrect: avg=$FINAL_RATING_AVG, count=$FINAL_RATING_COUNT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 14: Get deleted review (should fail)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Trying to get deleted review..."
|
||||
response=$(http_get "$BASE_URL/v1/reviews/$REVIEW1_ID" "$PARTICIPANT1_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "not found"; then
|
||||
log_success "Deleted review not found"
|
||||
else
|
||||
log_error "Deleted review still accessible: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 15: Calendar rating updated"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Checking calendar rating..."
|
||||
response=$(http_get "$BASE_URL/v1/calendars/$CALENDAR_ID" "$OWNER_TOKEN")
|
||||
CAL_RATING_AVG=$(extract_json_number "$response" "rating_avg")
|
||||
CAL_RATING_COUNT=$(extract_json_number "$response" "rating_count")
|
||||
|
||||
if [ "$CAL_RATING_AVG" = "4.0" ] && [ "$CAL_RATING_COUNT" = "1" ]; then
|
||||
log_success "Calendar rating: $CAL_RATING_AVG ($CAL_RATING_COUNT review)"
|
||||
else
|
||||
log_error "Calendar rating incorrect: avg=$CAL_RATING_AVG, count=$CAL_RATING_COUNT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "REVIEWS API TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
echo "Summary of created resources:"
|
||||
echo " Admin: $ADMIN_EMAIL"
|
||||
echo " Owner: $OWNER_EMAIL"
|
||||
echo " Participant 1: $PARTICIPANT1_EMAIL"
|
||||
echo " Participant 2: $PARTICIPANT2_EMAIL"
|
||||
echo " Calendar: $CALENDAR_ID"
|
||||
echo " Event: $EVENT_ID"
|
||||
echo ""
|
||||
@@ -1,228 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPTS_DIR/../.." && pwd)"
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
SERVER_STARTED=false
|
||||
SERVER_PID=""
|
||||
|
||||
# ============================================================================
|
||||
# Функции
|
||||
# ============================================================================
|
||||
cleanup() {
|
||||
if [ "$SERVER_STARTED" = true ] && [ -n "$SERVER_PID" ]; then
|
||||
echo ""
|
||||
echo -e "${BLUE}[INFO]${NC} Stopping server..."
|
||||
kill "$SERVER_PID" 2>/dev/null
|
||||
wait "$SERVER_PID" 2>/dev/null
|
||||
pkill -f "beam.*eventhub_test" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [options] [pattern]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -h, --help Show this help"
|
||||
echo " -l, --list List available test scripts"
|
||||
echo " -v, --verbose Verbose output"
|
||||
echo " -s, --server Use existing server (don't start/stop)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 Run all tests"
|
||||
echo " $0 auth Run tests matching 'auth'"
|
||||
echo " $0 booking Run tests matching 'booking'"
|
||||
echo " $0 -l List all test scripts"
|
||||
echo " $0 -s Use already running server"
|
||||
}
|
||||
|
||||
list_scripts() {
|
||||
echo "Available test scripts:"
|
||||
find "$SCRIPTS_DIR" -maxdepth 1 -name "test_*.sh" ! -name "test_all.sh" ! -name "test_runner.sh" -type f | sort | while read script; do
|
||||
name=$(basename "$script")
|
||||
echo " - $name"
|
||||
done
|
||||
}
|
||||
|
||||
start_server() {
|
||||
echo -e "${CYAN}[STEP]${NC} Starting EventHub server..."
|
||||
|
||||
# Переходим в корень проекта
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Проверяем, что мы в правильной директории
|
||||
if [ ! -f "rebar.config" ]; then
|
||||
echo -e "${RED}[ERROR]${NC} rebar.config not found in $(pwd)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}[INFO]${NC} Project root: $(pwd)"
|
||||
|
||||
# Компилируем если нужно
|
||||
if [ ! -d "_build" ]; then
|
||||
echo -e "${BLUE}[INFO]${NC} Compiling project..."
|
||||
rebar3 compile
|
||||
fi
|
||||
|
||||
# Запускаем сервер
|
||||
LOG_FILE="/tmp/eventhub_test_server.log"
|
||||
echo -e "${BLUE}[INFO]${NC} Starting server, log: $LOG_FILE"
|
||||
|
||||
rebar3 shell --sname eventhub_test > "$LOG_FILE" 2>&1 &
|
||||
SERVER_PID=$!
|
||||
|
||||
echo -e "${BLUE}[INFO]${NC} Server PID: $SERVER_PID"
|
||||
|
||||
# Ждём готовности
|
||||
for i in {1..30}; do
|
||||
echo -n "."
|
||||
if curl -s "http://localhost:8080/health" 2>/dev/null | grep -q "ok"; then
|
||||
echo ""
|
||||
echo -e "${GREEN}[SUCCESS]${NC} Server ready (took $i seconds)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Проверяем, не умер ли процесс
|
||||
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||
echo ""
|
||||
echo -e "${RED}[ERROR]${NC} Server process died"
|
||||
echo -e "${YELLOW}[INFO]${NC} Last 20 lines of log:"
|
||||
tail -20 "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${RED}[ERROR]${NC} Server failed to start within 30 seconds"
|
||||
echo -e "${YELLOW}[INFO]${NC} Last 20 lines of log:"
|
||||
tail -20 "$LOG_FILE"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Парсинг аргументов
|
||||
# ============================================================================
|
||||
VERBOSE=false
|
||||
USE_EXISTING=false
|
||||
PATTERN=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-l|--list)
|
||||
list_scripts
|
||||
exit 0
|
||||
;;
|
||||
-v|--verbose)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
-s|--server)
|
||||
USE_EXISTING=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
PATTERN="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ============================================================================
|
||||
# Главная логика
|
||||
# ============================================================================
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB TEST RUNNER"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
if [ "$USE_EXISTING" = false ]; then
|
||||
if ! start_server; then
|
||||
exit 1
|
||||
fi
|
||||
SERVER_STARTED=true
|
||||
else
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
echo -e "${RED}[ERROR]${NC} Server is not running"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}[SUCCESS]${NC} Using existing server"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Находим тесты
|
||||
if [ -n "$PATTERN" ]; then
|
||||
TEST_SCRIPTS=$(find "$SCRIPTS_DIR" -maxdepth 1 -name "test_*${PATTERN}*.sh" ! -name "test_all.sh" ! -name "test_runner.sh" -type f | sort)
|
||||
else
|
||||
TEST_SCRIPTS=$(find "$SCRIPTS_DIR" -maxdepth 1 -name "test_*.sh" ! -name "test_all.sh" ! -name "test_runner.sh" -type f | sort)
|
||||
fi
|
||||
|
||||
if [ -z "$TEST_SCRIPTS" ]; then
|
||||
echo -e "${YELLOW}[WARNING]${NC} No test scripts found"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}[INFO]${NC} Running tests:"
|
||||
for script in $TEST_SCRIPTS; do
|
||||
echo " - $(basename "$script")"
|
||||
done
|
||||
echo ""
|
||||
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
for script in $TEST_SCRIPTS; do
|
||||
script_name=$(basename "$script")
|
||||
echo "============================================================"
|
||||
echo -e "${CYAN}[RUNNING]${NC} $script_name"
|
||||
echo "============================================================"
|
||||
|
||||
if $VERBOSE; then
|
||||
bash "$script"
|
||||
EXIT_CODE=$?
|
||||
else
|
||||
bash "$script"
|
||||
EXIT_CODE=$?
|
||||
fi
|
||||
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo -e "${GREEN}[PASSED]${NC} $script_name"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}[FAILED]${NC} $script_name"
|
||||
((FAILED++))
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "============================================================"
|
||||
echo " TEST SUMMARY"
|
||||
echo "============================================================"
|
||||
echo -e "Scripts run: $((PASSED + FAILED))"
|
||||
echo -e "${GREEN}Passed: $PASSED${NC}"
|
||||
echo -e "${RED}Failed: $FAILED${NC}"
|
||||
echo "============================================================"
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo -e "${GREEN}🎉 ALL TESTS PASSED!${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ SOME TESTS FAILED${NC}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,393 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1; local token=$2
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" -H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
http_put() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
curl -s -X PUT "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
}
|
||||
|
||||
url_encode() {
|
||||
echo -n "$1" | sed 's/ /%20/g;s/,/%2C/g'
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB SEARCH API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Checking if server is running..."
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_error "Server is not running"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Server is running"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 1: Create test users"
|
||||
log_info "============================================================"
|
||||
|
||||
OWNER_EMAIL="search_owner_$(date +%s)@example.com"
|
||||
OWNER_PASSWORD="owner123"
|
||||
|
||||
log_info "Creating calendar owner..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$OWNER_EMAIL\",\"password\":\"$OWNER_PASSWORD\"}" "")
|
||||
OWNER_TOKEN=$(extract_json "$response" "token")
|
||||
OWNER_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -z "$OWNER_TOKEN" ]; then
|
||||
log_error "Failed to create owner"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Owner created: $OWNER_EMAIL"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 2: Create calendar with tags"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Creating calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" \
|
||||
"{\"title\":\"Tech Events Calendar\",\"description\":\"Calendar for technology events and workshops\",\"tags\":[\"tech\",\"programming\",\"workshop\"]}" "$OWNER_TOKEN")
|
||||
CALENDAR_ID=$(extract_json "$response" "id")
|
||||
log_success "Calendar created with tags: $CALENDAR_ID"
|
||||
|
||||
# Добавляем теги через обновление
|
||||
http_put "$BASE_URL/v1/calendars/$CALENDAR_ID" "{\"tags\":[\"tech\",\"programming\",\"workshop\"]}" "$OWNER_TOKEN" > /dev/null
|
||||
log_success "Calendar created with tags: $CALENDAR_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 3: Create events with different properties"
|
||||
log_info "============================================================"
|
||||
|
||||
# Функция для создания события
|
||||
create_event() {
|
||||
local title=$1
|
||||
local description=$2
|
||||
local start_time=$3
|
||||
local tags=$4
|
||||
local lat=$5
|
||||
local lon=$6
|
||||
local address=$7
|
||||
|
||||
local location_json="null"
|
||||
if [ -n "$lat" ] && [ -n "$lon" ]; then
|
||||
location_json="{\"address\":\"$address\",\"lat\":$lat,\"lon\":$lon}"
|
||||
fi
|
||||
|
||||
local tags_json="[]"
|
||||
if [ -n "$tags" ]; then
|
||||
tags_json="$tags"
|
||||
fi
|
||||
|
||||
local data="{\"title\":\"$title\",\"description\":\"$description\",\"start_time\":\"$start_time\",\"duration\":60,\"tags\":$tags_json"
|
||||
if [ "$location_json" != "null" ]; then
|
||||
data="$data,\"location\":$location_json"
|
||||
fi
|
||||
data="$data}"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$CALENDAR_ID/events" "$data" "$OWNER_TOKEN")
|
||||
local event_id=$(extract_json "$response" "id")
|
||||
|
||||
echo "$event_id"
|
||||
}
|
||||
|
||||
log_info "Creating Python Workshop event..."
|
||||
WORKSHOP_ID=$(create_event "Python Workshop" "Learn Python programming basics" "2026-06-01T10:00:00Z" \
|
||||
"[\"python\",\"workshop\",\"programming\"]" "55.7558" "37.6173" "Moscow, Russia")
|
||||
log_success "Created: $WORKSHOP_ID"
|
||||
|
||||
log_info "Creating JavaScript Conference event..."
|
||||
JS_ID=$(create_event "JavaScript Conference" "Annual JS conference for developers" "2026-06-15T09:00:00Z" \
|
||||
"[\"javascript\",\"conference\",\"web\"]" "55.7558" "37.6173" "Moscow, Russia")
|
||||
log_success "Created: $JS_ID"
|
||||
|
||||
log_info "Creating Yoga Class event (no tags)..."
|
||||
YOGA_ID=$(create_event "Yoga Class" "Morning yoga session" "2026-06-10T08:00:00Z" \
|
||||
"" "" "" "")
|
||||
log_success "Created: $YOGA_ID"
|
||||
|
||||
log_info "Creating Tech Meetup in another city..."
|
||||
MEETUP_ID=$(create_event "Tech Meetup" "Networking for tech professionals" "2026-06-20T18:00:00Z" \
|
||||
"[\"networking\",\"tech\"]" "59.9343" "30.3351" "Saint Petersburg, Russia")
|
||||
log_success "Created: $MEETUP_ID"
|
||||
|
||||
log_info "Creating past event..."
|
||||
PAST_ID=$(create_event "Past Event" "This event already happened" "2020-01-01T10:00:00Z" \
|
||||
"[\"past\"]" "" "" "")
|
||||
log_success "Created: $PAST_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Search by text query"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Searching for 'Python'..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&q=Python" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Python Workshop"; then
|
||||
log_success "Found Python Workshop"
|
||||
else
|
||||
log_error "Python Workshop not found"
|
||||
fi
|
||||
|
||||
log_info "Searching for 'conference'..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&q=conference" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "JavaScript Conference"; then
|
||||
log_success "Found JavaScript Conference"
|
||||
else
|
||||
log_error "JavaScript Conference not found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Search by tags"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Searching for events with tag 'python'..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&tags=python" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Python Workshop"; then
|
||||
log_success "Found Python Workshop by tag"
|
||||
else
|
||||
log_error "Python Workshop not found by tag"
|
||||
fi
|
||||
|
||||
log_info "Searching for multiple tags 'tech,workshop'..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&tags=tech,workshop" "$OWNER_TOKEN")
|
||||
log_success "Multiple tag search completed"
|
||||
|
||||
log_info "Searching for tag 'yoga' (should be empty)..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&tags=yoga" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q '"total":0'; then
|
||||
log_success "Yoga tag correctly returned no results (no tags on event)"
|
||||
else
|
||||
log_warning "Yoga event has no tags, but might appear in results"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: Search by date range"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Searching events in June 2026..."
|
||||
FROM="2026-06-01T00:00:00Z"
|
||||
TO="2026-06-30T23:59:59Z"
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&from=$FROM&to=$TO" "$OWNER_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "Python Workshop"; then
|
||||
log_success "Found June events"
|
||||
else
|
||||
log_error "June events not found"
|
||||
fi
|
||||
|
||||
log_info "Searching past events only..."
|
||||
FROM="2019-01-01T00:00:00Z"
|
||||
TO="2021-01-01T00:00:00Z"
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&from=$FROM&to=$TO" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Past Event"; then
|
||||
log_success "Found past event"
|
||||
else
|
||||
log_error "Past event not found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: Geo-location search"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Searching events within 5km of Moscow center..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&lat=55.7558&lon=37.6173&radius=5" "$OWNER_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "Python Workshop"; then
|
||||
log_success "Found Moscow events"
|
||||
else
|
||||
log_error "Moscow events not found"
|
||||
fi
|
||||
|
||||
log_info "Searching events within 1km of Moscow (should find fewer)..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&lat=55.7558&lon=37.6173&radius=1" "$OWNER_TOKEN")
|
||||
log_success "Radius search completed"
|
||||
|
||||
log_info "Searching events in Saint Petersburg..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&lat=59.9343&lon=30.3351&radius=10" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Tech Meetup"; then
|
||||
log_success "Found Saint Petersburg event"
|
||||
else
|
||||
log_error "Saint Petersburg event not found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 5: Combined search"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Search: text 'Python' + tag 'workshop'..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&q=Python&tags=workshop" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Python Workshop"; then
|
||||
log_success "Combined text+tag search successful"
|
||||
else
|
||||
log_error "Combined search failed"
|
||||
fi
|
||||
|
||||
log_info "Search: tag 'javascript' + date range..."
|
||||
FROM="2026-06-01T00:00:00Z"
|
||||
TO="2026-06-30T23:59:59Z"
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&tags=javascript&from=$FROM&to=$TO" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "JavaScript Conference"; then
|
||||
log_success "Combined tag+date search successful"
|
||||
else
|
||||
log_error "Combined search failed"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 6: Pagination"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Search with limit=2..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&limit=2" "$OWNER_TOKEN")
|
||||
COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
if [ "$COUNT" -le 2 ]; then
|
||||
log_success "Pagination limit works (got $COUNT results)"
|
||||
else
|
||||
log_error "Pagination limit failed (got $COUNT results)"
|
||||
fi
|
||||
|
||||
log_info "Search with offset=2..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&limit=2&offset=2" "$OWNER_TOKEN")
|
||||
log_success "Pagination offset works"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 7: Sorting"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Sort by start_time ascending..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&sort=start_time&order=asc" "$OWNER_TOKEN")
|
||||
log_success "Sort ascending completed"
|
||||
|
||||
log_info "Sort by start_time descending..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&sort=start_time&order=desc" "$OWNER_TOKEN")
|
||||
log_success "Sort descending completed"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 8: Calendar search"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Searching calendars by text..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=calendar&q=Tech" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Tech Events Calendar"; then
|
||||
log_success "Found calendar by text"
|
||||
else
|
||||
log_error "Calendar not found by text"
|
||||
fi
|
||||
|
||||
log_info "Searching calendars by tag..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=calendar&tags=tech" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "Tech Events Calendar"; then
|
||||
log_success "Found calendar by tag"
|
||||
else
|
||||
log_error "Calendar not found by tag"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 9: Search all (events + calendars)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Searching all (no type specified)..."
|
||||
response=$(http_get "$BASE_URL/v1/search?q=Tech" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q "events" && echo "$response" | grep -q "calendars"; then
|
||||
log_success "All search returned both events and calendars"
|
||||
else
|
||||
log_warning "All search may not have returned both types"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 10: Empty search results"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Searching for non-existent text..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&q=nonexistenttext12345" "$OWNER_TOKEN")
|
||||
if echo "$response" | grep -q '"total":0'; then
|
||||
log_success "Empty search handled correctly"
|
||||
else
|
||||
log_error "Empty search not handled correctly"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 11: Commercial calendar visibility"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Creating commercial calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" \
|
||||
"{\"title\":\"Public Commercial Calendar\",\"type\":\"commercial\"}" "$OWNER_TOKEN")
|
||||
COMMERCIAL_ID=$(extract_json "$response" "id")
|
||||
log_success "Commercial calendar created: $COMMERCIAL_ID"
|
||||
|
||||
log_info "Creating event in commercial calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$COMMERCIAL_ID/events" \
|
||||
"{\"title\":\"Public Event\",\"start_time\":\"2026-06-01T10:00:00Z\",\"duration\":60}" "$OWNER_TOKEN")
|
||||
log_success "Public event created"
|
||||
|
||||
log_info "Creating another user to test visibility..."
|
||||
OTHER_EMAIL="search_other_$(date +%s)@example.com"
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$OTHER_EMAIL\",\"password\":\"test123\"}" "")
|
||||
OTHER_TOKEN=$(extract_json "$response" "token")
|
||||
|
||||
log_info "Other user searching for public event..."
|
||||
response=$(http_get "$BASE_URL/v1/search?type=event&q=Public" "$OTHER_TOKEN")
|
||||
if echo "$response" | grep -q "Public Event"; then
|
||||
log_success "Other user can see public event in commercial calendar"
|
||||
else
|
||||
log_error "Other user cannot see public event"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "SEARCH API TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
echo "Summary of created resources:"
|
||||
echo " Owner: $OWNER_EMAIL"
|
||||
echo " Calendar: $CALENDAR_ID"
|
||||
echo " Commercial Calendar: $COMMERCIAL_ID"
|
||||
echo " Events created: 5"
|
||||
echo ""
|
||||
@@ -1,217 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1; local token=$2
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" -H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB SUBSCRIPTION API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Checking if server is running..."
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_error "Server is not running"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Server is running"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 1: Create test users"
|
||||
log_info "============================================================"
|
||||
|
||||
# Пользователь 1 (будет использовать пробный период через commercial календарь)
|
||||
USER1_EMAIL="sub_user1_$(date +%s)@example.com"
|
||||
USER1_PASSWORD="user123"
|
||||
|
||||
log_info "Creating user 1..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$USER1_EMAIL\",\"password\":\"$USER1_PASSWORD\"}" "")
|
||||
USER1_TOKEN=$(extract_json "$response" "token")
|
||||
USER1_ID=$(extract_json "$response" "id")
|
||||
log_success "User 1 created"
|
||||
|
||||
# Пользователь 2 (для проверки, что пробный период используется один раз)
|
||||
USER2_EMAIL="sub_user2_$(date +%s)@example.com"
|
||||
USER2_PASSWORD="user123"
|
||||
|
||||
log_info "Creating user 2..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$USER2_EMAIL\",\"password\":\"$USER2_PASSWORD\"}" "")
|
||||
USER2_TOKEN=$(extract_json "$response" "token")
|
||||
USER2_ID=$(extract_json "$response" "id")
|
||||
log_success "User 2 created"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Get subscription (free)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/subscription" "$USER1_TOKEN")
|
||||
if echo "$response" | grep -q "free"; then
|
||||
log_success "User has free subscription"
|
||||
else
|
||||
log_error "Expected free subscription: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Create personal calendar (should work with free)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"Personal Calendar\",\"type\":\"personal\"}" "$USER1_TOKEN")
|
||||
PERSONAL_CALENDAR_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$PERSONAL_CALENDAR_ID" ]; then
|
||||
log_success "Personal calendar created (free subscription allows this)"
|
||||
else
|
||||
log_error "Failed to create personal calendar: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: Create commercial calendar (auto-activates trial)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User 1 creating commercial calendar (should auto-start trial)..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"Commercial Calendar\",\"type\":\"commercial\"}" "$USER1_TOKEN")
|
||||
COMMERCIAL_CALENDAR_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$COMMERCIAL_CALENDAR_ID" ]; then
|
||||
log_success "Commercial calendar created - trial auto-activated"
|
||||
else
|
||||
log_error "Failed to create commercial calendar: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: Get subscription (should be trial now)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/subscription" "$USER1_TOKEN")
|
||||
if echo "$response" | grep -q "trial"; then
|
||||
log_success "User now has trial subscription"
|
||||
else
|
||||
log_error "Expected trial subscription: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 5: Create second commercial calendar (should still work)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"Second Commercial\",\"type\":\"commercial\"}" "$USER1_TOKEN")
|
||||
SECOND_COMMERCIAL_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$SECOND_COMMERCIAL_ID" ]; then
|
||||
log_success "Second commercial calendar created"
|
||||
else
|
||||
log_error "Failed to create second commercial calendar: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 6: User 2 creates commercial calendar (gets trial)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User 2 creating commercial calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"User2 Commercial\",\"type\":\"commercial\"}" "$USER2_TOKEN")
|
||||
USER2_CALENDAR_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$USER2_CALENDAR_ID" ]; then
|
||||
log_success "User 2 commercial calendar created - trial activated"
|
||||
else
|
||||
log_error "User 2 failed: $response"
|
||||
fi
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/subscription" "$USER2_TOKEN")
|
||||
if echo "$response" | grep -q "trial"; then
|
||||
log_success "User 2 has trial subscription"
|
||||
else
|
||||
log_error "User 2 subscription: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 7: Activate paid subscription"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User 1 activating paid subscription..."
|
||||
response=$(http_post "$BASE_URL/v1/subscription" "{\"action\":\"activate\",\"plan\":\"monthly\",\"payment_info\":{\"card\":\"4242424242424242\"}}" "$USER1_TOKEN")
|
||||
if echo "$response" | grep -q "monthly"; then
|
||||
log_success "Paid subscription activated"
|
||||
else
|
||||
log_error "Failed to activate: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 8: Get subscription (should be active paid)"
|
||||
log_info "============================================================"
|
||||
|
||||
response=$(http_get "$BASE_URL/v1/subscription" "$USER1_TOKEN")
|
||||
if echo "$response" | grep -q "active" && echo "$response" | grep -q "monthly"; then
|
||||
log_success "User has active paid subscription"
|
||||
else
|
||||
log_error "Expected active paid subscription: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 9: User 3 (free) tries to use already consumed trial"
|
||||
log_info "============================================================"
|
||||
|
||||
# Создаём пользователя 3, который сначала использует trial, потом отменяет подписку
|
||||
USER3_EMAIL="sub_user3_$(date +%s)@example.com"
|
||||
USER3_PASSWORD="user123"
|
||||
|
||||
log_info "Creating user 3..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$USER3_EMAIL\",\"password\":\"$USER3_PASSWORD\"}" "")
|
||||
USER3_TOKEN=$(extract_json "$response" "token")
|
||||
log_success "User 3 created"
|
||||
|
||||
log_info "User 3 creating commercial calendar (uses trial)..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" "{\"title\":\"User3 Commercial\",\"type\":\"commercial\"}" "$USER3_TOKEN")
|
||||
USER3_CALENDAR_ID=$(extract_json "$response" "id")
|
||||
log_success "Commercial calendar created"
|
||||
|
||||
log_info "Simulating trial expiration (requires admin or time travel - skipped)"
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "SUBSCRIPTION API TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo " User 1: $USER1_EMAIL (trial -> paid)"
|
||||
echo " User 2: $USER2_EMAIL (trial)"
|
||||
echo " User 3: $USER3_EMAIL (trial)"
|
||||
echo ""
|
||||
@@ -1,282 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
extract_json_number() {
|
||||
echo "$1" | grep -o "\"$2\":[0-9]*" | head -1 | sed "s/\"$2\"://"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1; local token=$2
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" -H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
http_put() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
curl -s -X PUT "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB TICKETS API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
log_info "Checking if server is running..."
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_error "Server is not running"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Server is running"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 1: Create test users"
|
||||
log_info "============================================================"
|
||||
|
||||
# Админ (первый пользователь)
|
||||
ADMIN_EMAIL="ticket_admin_$(date +%s)@example.com"
|
||||
ADMIN_PASSWORD="admin123"
|
||||
|
||||
log_info "Creating admin user..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}" "")
|
||||
ADMIN_TOKEN=$(extract_json "$response" "token")
|
||||
ADMIN_ID=$(extract_json "$response" "id")
|
||||
log_success "Admin created"
|
||||
|
||||
# Обычный пользователь
|
||||
USER_EMAIL="ticket_user_$(date +%s)@example.com"
|
||||
USER_PASSWORD="user123"
|
||||
|
||||
log_info "Creating regular user..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$USER_EMAIL\",\"password\":\"$USER_PASSWORD\"}" "")
|
||||
USER_TOKEN=$(extract_json "$response" "token")
|
||||
USER_ID=$(extract_json "$response" "id")
|
||||
log_success "User created"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Report error (user)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User reporting error..."
|
||||
response=$(http_post "$BASE_URL/v1/tickets" \
|
||||
"{\"error_message\":\"Test error occurred\",\"stacktrace\":\"line 1\\nline 2\",\"context\":{\"user_id\":\"$USER_ID\"}}" "$USER_TOKEN")
|
||||
TICKET1_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$TICKET1_ID" ]; then
|
||||
log_success "Ticket created: $TICKET1_ID"
|
||||
else
|
||||
log_error "Failed to create ticket: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Report same error again (should increment count)"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User reporting same error..."
|
||||
response=$(http_post "$BASE_URL/v1/tickets" \
|
||||
"{\"error_message\":\"Test error occurred\",\"stacktrace\":\"line 1\\nline 2\"}" "$USER_TOKEN")
|
||||
COUNT=$(extract_json_number "$response" "count")
|
||||
|
||||
if [ "$COUNT" -eq 2 ]; then
|
||||
log_success "Ticket count incremented to $COUNT"
|
||||
else
|
||||
log_error "Count should be 2, got $COUNT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: Report different error"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User reporting different error..."
|
||||
response=$(http_post "$BASE_URL/v1/tickets" \
|
||||
"{\"error_message\":\"Another error\"}" "$USER_TOKEN")
|
||||
TICKET2_ID=$(extract_json "$response" "id")
|
||||
|
||||
if [ -n "$TICKET2_ID" ] && [ "$TICKET2_ID" != "$TICKET1_ID" ]; then
|
||||
log_success "New ticket created: $TICKET2_ID"
|
||||
else
|
||||
log_error "Failed to create new ticket"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: Admin views all tickets"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin getting all tickets..."
|
||||
response=$(http_get "$BASE_URL/v1/admin/tickets" "$ADMIN_TOKEN")
|
||||
TICKET_COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
|
||||
if [ "$TICKET_COUNT" -eq 2 ]; then
|
||||
log_success "Admin sees $TICKET_COUNT tickets"
|
||||
else
|
||||
log_error "Admin should see 2 tickets, found $TICKET_COUNT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 5: User cannot view tickets"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "User trying to view tickets..."
|
||||
response=$(http_get "$BASE_URL/v1/admin/tickets" "$USER_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "Admin access required"; then
|
||||
log_success "User correctly denied access"
|
||||
else
|
||||
log_error "User should be denied: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 6: Admin views tickets by status"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin getting open tickets..."
|
||||
response=$(http_get "$BASE_URL/v1/admin/tickets?status=open" "$ADMIN_TOKEN")
|
||||
OPEN_COUNT=$(echo "$response" | grep -o "\"id\"" | wc -l)
|
||||
|
||||
if [ "$OPEN_COUNT" -eq 2 ]; then
|
||||
log_success "Found $OPEN_COUNT open tickets"
|
||||
else
|
||||
log_error "Should find 2 open tickets, found $OPEN_COUNT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 7: Admin updates ticket status"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin marking ticket as in_progress..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/tickets/$TICKET1_ID" \
|
||||
"{\"action\":\"status\",\"status\":\"in_progress\"}" "$ADMIN_TOKEN")
|
||||
|
||||
STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
if [ "$STATUS" = "in_progress" ]; then
|
||||
log_success "Ticket status updated to in_progress"
|
||||
else
|
||||
log_error "Failed to update status: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 8: Admin assigns ticket"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin assigning ticket..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/tickets/$TICKET1_ID" \
|
||||
"{\"action\":\"assign\",\"admin_id\":\"$ADMIN_ID\"}" "$ADMIN_TOKEN")
|
||||
|
||||
ASSIGNED=$(echo "$response" | grep -o "\"assigned_to\":\"[^\"]*\"" | sed 's/"assigned_to":"//;s/"//')
|
||||
if [ "$ASSIGNED" = "$ADMIN_ID" ]; then
|
||||
log_success "Ticket assigned to admin"
|
||||
else
|
||||
log_error "Failed to assign ticket: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 9: Admin resolves ticket"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin resolving ticket..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/tickets/$TICKET1_ID" \
|
||||
"{\"action\":\"resolve\",\"note\":\"Fixed in version 1.0\"}" "$ADMIN_TOKEN")
|
||||
|
||||
STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
NOTE=$(echo "$response" | grep -o "\"resolution_note\":\"[^\"]*\"" | sed 's/"resolution_note":"//;s/"//')
|
||||
|
||||
if [ "$STATUS" = "resolved" ] && [ "$NOTE" = "Fixed in version 1.0" ]; then
|
||||
log_success "Ticket resolved with note"
|
||||
else
|
||||
log_error "Failed to resolve ticket: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 10: Admin closes ticket"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin closing ticket..."
|
||||
response=$(http_put "$BASE_URL/v1/admin/tickets/$TICKET1_ID" \
|
||||
"{\"action\":\"close\"}" "$ADMIN_TOKEN")
|
||||
|
||||
STATUS=$(echo "$response" | grep -o "\"status\":\"[^\"]*\"" | sed 's/"status":"//;s/"//')
|
||||
if [ "$STATUS" = "closed" ]; then
|
||||
log_success "Ticket closed"
|
||||
else
|
||||
log_error "Failed to close ticket: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 11: Admin views statistics"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin getting statistics..."
|
||||
response=$(http_get "$BASE_URL/v1/admin/tickets/stats" "$ADMIN_TOKEN")
|
||||
|
||||
TOTAL=$(extract_json_number "$response" "total_tickets")
|
||||
OPEN=$(extract_json_number "$response" "open")
|
||||
CLOSED=$(extract_json_number "$response" "closed")
|
||||
|
||||
if [ "$TOTAL" -eq 2 ] && [ "$OPEN" -eq 1 ] && [ "$CLOSED" -eq 1 ]; then
|
||||
log_success "Statistics: total=$TOTAL, open=$OPEN, closed=$CLOSED"
|
||||
else
|
||||
log_error "Statistics incorrect: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 12: Get single ticket"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Admin getting ticket $TICKET2_ID..."
|
||||
response=$(http_get "$BASE_URL/v1/admin/tickets/$TICKET2_ID" "$ADMIN_TOKEN")
|
||||
|
||||
if echo "$response" | grep -q "$TICKET2_ID"; then
|
||||
log_success "Ticket retrieved"
|
||||
else
|
||||
log_error "Failed to get ticket: $response"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "TICKETS API TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
echo "Summary of created resources:"
|
||||
echo " Admin: $ADMIN_EMAIL"
|
||||
echo " User: $USER_EMAIL"
|
||||
echo " Tickets: $TICKET1_ID, $TICKET2_ID"
|
||||
echo ""
|
||||
@@ -1,313 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
BASE_URL="http://localhost:8080"
|
||||
WS_URL="ws://localhost:8081/ws"
|
||||
ADMIN_WS_URL="ws://localhost:8446/admin/ws"
|
||||
|
||||
DEBUG=${DEBUG:-false}
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
log_debug() {
|
||||
if [ "$DEBUG" = "true" ]; then
|
||||
echo -e "${CYAN}[DEBUG]${NC} $1"
|
||||
fi
|
||||
}
|
||||
|
||||
extract_json() {
|
||||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed "s/\"$2\":\"//;s/\"$//"
|
||||
}
|
||||
|
||||
http_post() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
log_debug "POST $url"
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
else
|
||||
curl -s -X POST "$url" -H "Content-Type: application/json" -d "$data"
|
||||
fi
|
||||
}
|
||||
|
||||
http_get() {
|
||||
local url=$1; local token=$2
|
||||
log_debug "GET $url"
|
||||
if [ -n "$token" ]; then
|
||||
curl -s -X GET "$url" -H "Authorization: Bearer $token"
|
||||
else
|
||||
curl -s -X GET "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
http_put() {
|
||||
local url=$1; local data=$2; local token=$3
|
||||
log_debug "PUT $url"
|
||||
curl -s -X PUT "$url" -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$data"
|
||||
}
|
||||
|
||||
# Проверка curl WebSocket (ручная проверка заголовков)
|
||||
test_ws_curl() {
|
||||
local url=$1
|
||||
local token=$2
|
||||
local full_url="${url}?token=${token}"
|
||||
|
||||
log_debug "Testing WebSocket with curl: $full_url"
|
||||
|
||||
# Используем --include для заголовков, --no-buffer для немедленного вывода
|
||||
response=$(curl -s --include --no-buffer \
|
||||
-H "Connection: Upgrade" \
|
||||
-H "Upgrade: websocket" \
|
||||
-H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
|
||||
-H "Sec-WebSocket-Version: 13" \
|
||||
--max-time 2 \
|
||||
"$full_url" 2>&1)
|
||||
|
||||
log_debug "Response: $(echo "$response" | head -5)"
|
||||
|
||||
if echo "$response" | grep -q "101"; then
|
||||
log_debug "Got 101 Switching Protocols"
|
||||
return 0
|
||||
elif echo "$response" | grep -q "401"; then
|
||||
log_debug "Got 401 Unauthorized"
|
||||
return 1
|
||||
elif echo "$response" | grep -q "403"; then
|
||||
log_debug "Got 403 Forbidden"
|
||||
return 2
|
||||
elif echo "$response" | grep -q "404"; then
|
||||
log_debug "Got 404 Not Found"
|
||||
return 4
|
||||
elif echo "$response" | grep -q "Invalid token"; then
|
||||
log_debug "Got 'Invalid token' message"
|
||||
return 1
|
||||
else
|
||||
log_debug "Unknown response"
|
||||
return 3
|
||||
fi
|
||||
}
|
||||
|
||||
# Проверка наличия websocat
|
||||
check_websocat() {
|
||||
if ! command -v websocat &> /dev/null; then
|
||||
log_warning "websocat не установлен"
|
||||
echo "Установите websocat:"
|
||||
echo " cargo install websocat"
|
||||
echo " или скачайте с https://github.com/vi/websocat/releases"
|
||||
return 1
|
||||
fi
|
||||
log_debug "websocat found: $(which websocat)"
|
||||
return 0
|
||||
}
|
||||
|
||||
echo "============================================================"
|
||||
echo " EVENTHUB WEBSOCKET API TEST SCRIPT"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
if [ "$DEBUG" = "true" ]; then
|
||||
log_info "DEBUG MODE ENABLED"
|
||||
fi
|
||||
|
||||
log_info "Checking if servers are running..."
|
||||
if ! curl -s "$BASE_URL/health" | grep -q "ok"; then
|
||||
log_error "Main server is not running on port 8080"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Main server is running"
|
||||
|
||||
if ! curl -s "http://localhost:8445/admin/health" | grep -q "ok"; then
|
||||
log_warning "Admin server is not running on port 8445"
|
||||
else
|
||||
log_success "Admin server is running"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 1: Create test users"
|
||||
log_info "============================================================"
|
||||
|
||||
# Админ
|
||||
ADMIN_EMAIL="ws_admin_$(date +%s)@example.com"
|
||||
ADMIN_PASSWORD="admin123"
|
||||
|
||||
log_info "Creating admin user..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"$ADMIN_PASSWORD\"}" "")
|
||||
log_debug "Register response: $response"
|
||||
ADMIN_TOKEN=$(extract_json "$response" "token")
|
||||
ADMIN_ID=$(extract_json "$response" "id")
|
||||
log_success "Admin created: $ADMIN_EMAIL"
|
||||
log_debug "Admin token: ${ADMIN_TOKEN:0:30}..."
|
||||
|
||||
# Обычный пользователь
|
||||
USER_EMAIL="ws_user_$(date +%s)@example.com"
|
||||
USER_PASSWORD="user123"
|
||||
|
||||
log_info "Creating regular user..."
|
||||
response=$(http_post "$BASE_URL/v1/register" "{\"email\":\"$USER_EMAIL\",\"password\":\"$USER_PASSWORD\"}" "")
|
||||
USER_TOKEN=$(extract_json "$response" "token")
|
||||
USER_ID=$(extract_json "$response" "id")
|
||||
log_success "User created: $USER_EMAIL"
|
||||
log_debug "User token: ${USER_TOKEN:0:30}..."
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "STEP 2: Create calendar and event"
|
||||
log_info "============================================================"
|
||||
|
||||
log_info "Creating calendar..."
|
||||
response=$(http_post "$BASE_URL/v1/calendars" \
|
||||
"{\"title\":\"WS Test Calendar\"}" "$USER_TOKEN")
|
||||
CALENDAR_ID=$(extract_json "$response" "id")
|
||||
log_success "Calendar created: $CALENDAR_ID"
|
||||
log_debug "Calendar ID: $CALENDAR_ID"
|
||||
|
||||
log_info "Creating event..."
|
||||
EVENT_START="2026-06-01T10:00:00Z"
|
||||
response=$(http_post "$BASE_URL/v1/calendars/$CALENDAR_ID/events" \
|
||||
"{\"title\":\"WS Test Event\",\"start_time\":\"$EVENT_START\",\"duration\":60,\"capacity\":10}" "$USER_TOKEN")
|
||||
EVENT_ID=$(extract_json "$response" "id")
|
||||
log_success "Event created: $EVENT_ID"
|
||||
log_debug "Event ID: $EVENT_ID"
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 1: Connect to WebSocket with valid token (curl test)"
|
||||
log_info "============================================================"
|
||||
|
||||
test_ws_curl "$WS_URL" "$USER_TOKEN"
|
||||
CURL_RESULT=$?
|
||||
|
||||
case $CURL_RESULT in
|
||||
0)
|
||||
log_success "WebSocket upgrade successful (101 Switching Protocols)"
|
||||
;;
|
||||
1)
|
||||
log_error "WebSocket authentication failed (401 Unauthorized)"
|
||||
log_debug "Token might be invalid or expired"
|
||||
;;
|
||||
2)
|
||||
log_error "WebSocket access denied (403 Forbidden)"
|
||||
;;
|
||||
*)
|
||||
log_error "WebSocket connection failed (unknown error)"
|
||||
log_debug "Check if WebSocket server is running on port 8081"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 2: Connect with invalid token (curl test)"
|
||||
log_info "============================================================"
|
||||
|
||||
test_ws_curl "$WS_URL" "invalid.token.here"
|
||||
CURL_RESULT=$?
|
||||
|
||||
if [ $CURL_RESULT -eq 1 ]; then
|
||||
log_success "Invalid token correctly rejected (401 Unauthorized)"
|
||||
else
|
||||
log_error "Invalid token should be rejected with 401"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 3: Admin WebSocket with valid token (curl test)"
|
||||
log_info "============================================================"
|
||||
|
||||
test_ws_curl "$ADMIN_WS_URL" "$ADMIN_TOKEN"
|
||||
CURL_RESULT=$?
|
||||
|
||||
case $CURL_RESULT in
|
||||
0)
|
||||
log_success "Admin WebSocket upgrade successful"
|
||||
;;
|
||||
1)
|
||||
log_error "Admin WebSocket authentication failed"
|
||||
;;
|
||||
2)
|
||||
log_error "Admin WebSocket access denied (not admin)"
|
||||
log_debug "Check if token has admin role"
|
||||
;;
|
||||
*)
|
||||
log_error "Admin WebSocket connection failed"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "TEST 4: Admin WebSocket with user token (should fail)"
|
||||
log_info "============================================================"
|
||||
|
||||
test_ws_curl "$ADMIN_WS_URL" "$USER_TOKEN"
|
||||
CURL_RESULT=$?
|
||||
|
||||
if [ $CURL_RESULT -eq 2 ]; then
|
||||
log_success "User token correctly rejected for admin WebSocket (403 Forbidden)"
|
||||
elif [ $CURL_RESULT -eq 1 ]; then
|
||||
log_warning "User token rejected with 401 instead of 403"
|
||||
else
|
||||
log_error "User token should be rejected"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "WEBSOCKET API TESTS (curl validation) COMPLETED!"
|
||||
log_info "============================================================"
|
||||
|
||||
# Опциональные тесты с websocat
|
||||
if check_websocat; then
|
||||
echo ""
|
||||
log_info "============================================================"
|
||||
log_info "OPTIONAL: Testing with websocat"
|
||||
log_info "============================================================"
|
||||
|
||||
WS_URL_WITH_TOKEN="${WS_URL}?token=${USER_TOKEN}"
|
||||
log_debug "WebSocket URL: $WS_URL_WITH_TOKEN"
|
||||
|
||||
TEMP_FILE=$(mktemp)
|
||||
log_debug "Temp file: $TEMP_FILE"
|
||||
|
||||
# Запускаем websocat в фоне
|
||||
log_info "Connecting with websocat..."
|
||||
timeout 3 websocat "$WS_URL_WITH_TOKEN" > "$TEMP_FILE" 2>&1 &
|
||||
WS_PID=$!
|
||||
log_debug "WebSocket PID: $WS_PID"
|
||||
sleep 1
|
||||
|
||||
if kill -0 $WS_PID 2>/dev/null; then
|
||||
log_success "WebSocket connection established with websocat"
|
||||
|
||||
# Отправляем ping
|
||||
echo '{"action":"ping"}' | timeout 2 websocat "$WS_URL_WITH_TOKEN" > "$TEMP_FILE" 2>&1
|
||||
if grep -q "pong" "$TEMP_FILE"; then
|
||||
log_success "Ping-pong successful"
|
||||
fi
|
||||
|
||||
kill $WS_PID 2>/dev/null
|
||||
else
|
||||
log_warning "Websocket connection failed"
|
||||
fi
|
||||
|
||||
rm -f "$TEMP_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
log_success "ALL WEBSOCKET TESTS COMPLETED!"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo " Admin: $ADMIN_EMAIL"
|
||||
echo " User: $USER_EMAIL"
|
||||
echo " Calendar: $CALENDAR_ID"
|
||||
echo " Event: $EVENT_ID"
|
||||
echo ""
|
||||
echo "Run with DEBUG=true for more details:"
|
||||
echo " DEBUG=true ./test/scripts/test_websocket_api.sh"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user