%%%------------------------------------------------------------------- %%% @doc Тесты административного API для получения статистики. %%% %%% Покрывает эндпоинты: %%% GET /v1/admin/stats %%% GET /v1/admin/users/stats %%% GET /v1/admin/events/stats %%% GET /v1/admin/reports/stats %%% GET /v1/admin/reviews/stats %%% GET /v1/admin/subscriptions/stats %%% GET /v1/admin/calendars/stats %%% %%% Проверяет: %%% - получение общей статистики для всех ролей администраторов %%% - для superadmin и admin – наличие ключевых метрик, включая pending_users_total, *_by_day %%% - для moderator и support – ответ непустой %%% - работу с фильтром по датам (from, to) %%% - статистику по пользователям (users/stats) %%% - статистику по событиям (events/stats) %%% - статистику по жалобам (reports/stats) %%% - статистику по отзывам (reviews/stats) %%% - статистику по подпискам (subscriptions/stats) %%% - статистику по календарям (calendars/stats) %%% @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(), % Создаём тестовые данные для ненулевой статистики UserToken = api_test_runner:get_user_token(), CalId = api_test_runner:create_calendar(UserToken, #{title => <<"StatsCal">>}), EventId = api_test_runner:create_event(UserToken, CalId, #{ title => <<"Stats Event">>, start_time => api_test_runner:future_date(), duration => 60 }), % Бронируем и подтверждаем, чтобы можно было оставить отзыв #{<<"id">> := BookingId} = api_test_runner:client_post( <<"/v1/events/", EventId/binary, "/bookings">>, UserToken, #{}), api_test_runner:client_put(<<"/v1/bookings/", BookingId/binary>>, UserToken, #{action => <<"confirm">>}), % Оставляем отзыв api_test_runner:client_post(<<"/v1/reviews">>, UserToken, #{target_type => <<"event">>, target_id => EventId, rating => 5, comment => <<"Great!">>}), % Жалоба api_test_runner:client_post(<<"/v1/reports">>, UserToken, #{<<"target_type">> => <<"event">>, <<"target_id">> => EventId, <<"reason">> => <<"Test">>}), % Подписка SubUserToken = api_test_runner:get_user_token(), api_test_runner:client_post(<<"/v1/subscription">>, SubUserToken, #{<<"action">> => <<"start_trial">>}), % Общая статистика 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), % Детальная статистика test_user_stats(SuperToken), test_event_stats(SuperToken), test_report_stats(SuperToken), test_review_stats(SuperToken), test_subscription_stats(SuperToken), test_calendar_stats(SuperToken), ct:pal("=== All admin stats tests passed ==="), ok. %%%=================================================================== %%% Тестовые функции %%%=================================================================== -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 -> % Тотальные значения ?assert(maps:is_key(<<"users_total">>, Stats)), ?assert(maps:is_key(<<"pending_users_total">>, Stats)), ?assert(maps:is_key(<<"events_total">>, Stats)), ?assert(maps:is_key(<<"calendars_total">>, Stats)), ?assert(maps:is_key(<<"reviews_total">>, Stats)), ?assert(maps:is_key(<<"reports_total">>, Stats)), ?assert(maps:is_key(<<"reports_pending">>, Stats)), ?assert(maps:is_key(<<"reports_reviewed">>, Stats)), ?assert(maps:is_key(<<"reports_dismissed">>, Stats)), ?assert(maps:is_key(<<"tickets_total">>, Stats)), ?assert(maps:is_key(<<"tickets_open">>, Stats)), ?assert(maps:is_key(<<"subscriptions_total">>, Stats)), ?assert(maps:is_key(<<"trial_subscriptions">>, Stats)), % Массивы по дням ?assert(maps:is_key(<<"registrations_by_day">>, Stats)), ?assert(maps:is_key(<<"events_by_day">>, Stats)), ?assert(maps:is_key(<<"reviews_by_day">>, Stats)), ?assert(maps:is_key(<<"calendars_by_day">>, Stats)), ?assert(maps:is_key(<<"reports_by_day">>, Stats)), ?assert(maps:is_key(<<"tickets_by_day">>, Stats)), ?assert(maps:is_key(<<"subscriptions_by_day">>, Stats)), ?assert(maps:is_key(<<"pending_users_by_day">>, Stats)), % Проверка, что они действительно списки ?assert(is_list(maps:get(<<"registrations_by_day">>, Stats))), ?assert(is_list(maps:get(<<"events_by_day">>, Stats))), ?assert(is_list(maps:get(<<"reviews_by_day">>, Stats))), ?assert(is_list(maps:get(<<"calendars_by_day">>, Stats))), ?assert(is_list(maps:get(<<"reports_by_day">>, Stats))), ?assert(is_list(maps:get(<<"tickets_by_day">>, Stats))), ?assert(is_list(maps:get(<<"subscriptions_by_day">>, Stats))), ?assert(is_list(maps:get(<<"pending_users_by_day">>, Stats))); loose -> ?assert(map_size(Stats) > 0) end, ct:pal(" OK: ~p keys", [length(maps:keys(Stats))]). -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"). %% @doc Тестирует эндпоинт GET /v1/admin/users/stats. -spec test_user_stats(binary()) -> ok. test_user_stats(Token) -> ct:pal(" TEST: User statistics"), Stats = api_test_runner:admin_get(<<"/v1/admin/users/stats">>, Token), ?assert(is_map(Stats)), ?assert(maps:is_key(<<"total_users">>, Stats)), ?assert(maps:is_key(<<"users_by_role">>, Stats)), ?assert(maps:is_key(<<"users_by_status">>, Stats)), ?assert(maps:is_key(<<"pending_users">>, Stats)), ct:pal(" OK: total=~p, pending=~p", [maps:get(<<"total_users">>, Stats), maps:get(<<"pending_users">>, Stats)]). %% @doc Тестирует эндпоинт GET /v1/admin/events/stats. -spec test_event_stats(binary()) -> ok. test_event_stats(Token) -> ct:pal(" TEST: Event statistics"), Stats = api_test_runner:admin_get(<<"/v1/admin/events/stats">>, Token), ?assert(is_map(Stats)), ?assert(maps:is_key(<<"total_events">>, Stats)), ?assert(maps:is_key(<<"events_by_type">>, Stats)), ?assert(maps:is_key(<<"events_by_status">>, Stats)), ?assert(maps:is_key(<<"top_events_by_rating">>, Stats)), ?assert(is_list(maps:get(<<"top_events_by_rating">>, Stats))), ct:pal(" OK: total=~p", [maps:get(<<"total_events">>, Stats)]). %% @doc Тестирует эндпоинт GET /v1/admin/reports/stats. -spec test_report_stats(binary()) -> ok. test_report_stats(Token) -> ct:pal(" TEST: Report statistics"), Stats = api_test_runner:admin_get(<<"/v1/admin/reports/stats">>, Token), ?assert(is_map(Stats)), ?assert(maps:is_key(<<"total_reports">>, Stats)), ?assert(maps:is_key(<<"reports_by_target_type">>, Stats)), ?assert(maps:is_key(<<"reports_by_status">>, Stats)), ?assert(maps:is_key(<<"top_targets_by_reports">>, Stats)), ?assert(is_list(maps:get(<<"top_targets_by_reports">>, Stats))), ct:pal(" OK: total=~p", [maps:get(<<"total_reports">>, Stats)]). %% @doc Тестирует эндпоинт GET /v1/admin/reviews/stats. -spec test_review_stats(binary()) -> ok. test_review_stats(Token) -> ct:pal(" TEST: Review statistics"), Stats = api_test_runner:admin_get(<<"/v1/admin/reviews/stats">>, Token), ?assert(is_map(Stats)), ?assert(maps:is_key(<<"total_reviews">>, Stats)), ?assert(maps:is_key(<<"reviews_by_target_type">>, Stats)), ?assert(maps:is_key(<<"reviews_by_status">>, Stats)), ?assert(maps:is_key(<<"top_targets_by_reviews">>, Stats)), ?assert(maps:is_key(<<"top_targets_by_positive_reviews">>, Stats)), ?assert(maps:is_key(<<"top_targets_by_negative_reviews">>, Stats)), ct:pal(" OK: total=~p", [maps:get(<<"total_reviews">>, Stats)]). %% @doc Тестирует эндпоинт GET /v1/admin/subscriptions/stats. -spec test_subscription_stats(binary()) -> ok. test_subscription_stats(Token) -> ct:pal(" TEST: Subscription statistics"), Stats = api_test_runner:admin_get(<<"/v1/admin/subscriptions/stats">>, Token), ?assert(is_map(Stats)), ?assert(maps:is_key(<<"total_subscriptions">>, Stats)), ?assert(maps:is_key(<<"subscriptions_by_plan">>, Stats)), ?assert(maps:is_key(<<"subscriptions_by_status">>, Stats)), ?assert(maps:is_key(<<"trial_subscriptions">>, Stats)), ?assert(maps:is_key(<<"ending_paid_subscriptions">>, Stats)), ?assert(is_list(maps:get(<<"ending_paid_subscriptions">>, Stats))), ct:pal(" OK: total=~p, trial=~p", [maps:get(<<"total_subscriptions">>, Stats), maps:get(<<"trial_subscriptions">>, Stats)]). %% @doc Тестирует эндпоинт GET /v1/admin/calendars/stats. -spec test_calendar_stats(binary()) -> ok. test_calendar_stats(Token) -> ct:pal(" TEST: Calendar statistics"), Stats = api_test_runner:admin_get(<<"/v1/admin/calendars/stats">>, Token), ?assert(is_map(Stats)), ?assert(maps:is_key(<<"total_calendars">>, Stats)), ?assert(maps:is_key(<<"calendars_by_type">>, Stats)), ?assert(maps:is_key(<<"calendars_by_status">>, Stats)), ?assert(maps:is_key(<<"top_calendars_by_reviews">>, Stats)), ?assert(maps:is_key(<<"top_calendars_by_positive_reviews">>, Stats)), ?assert(maps:is_key(<<"top_calendars_by_negative_reviews">>, Stats)), ?assert(maps:is_key(<<"top_calendars_by_rating">>, Stats)), ct:pal(" OK: total=~p", [maps:get(<<"total_calendars">>, Stats)]).