Files
EventHubBack/test/unit/stats_tops_tests.erl
aleksey a6dadcb9d5
CI / test (push) Successful in 21m3s
CI / deploy-ift (push) Failing after 11m23s
CI / e2e-ift (push) Has been skipped
CI / deploy-stage (push) Has been skipped
CI / e2e-stage (push) Has been skipped
fix(test): wait for full stats_tops review/report topology
Partial waits on top-1 raced under CI load and flaked after stats_collector subscribe reorder.
2026-07-19 00:31:55 +03:00

194 lines
7.5 KiB
Erlang

-module(stats_tops_tests).
-include_lib("eunit/include/eunit.hrl").
-include("records.hrl").
-define(TABLES, [event, calendar, review, report, stats_counter, stats_daily]).
setup() ->
eh_test_support:start_mnesia(),
eh_test_support:ensure_tables(?TABLES),
{ok, _} = stats_collector:start_link(),
ok = stats_collector:subscribe(),
ok.
cleanup(_) ->
case whereis(stats_collector) of
undefined -> ok;
Pid ->
unlink(Pid),
exit(Pid, kill),
wait_dead(Pid, 50)
end,
eh_test_support:delete_tables(?TABLES),
eh_test_support:stop_mnesia(),
ok.
wait_dead(_Pid, 0) -> ok;
wait_dead(Pid, N) ->
case is_process_alive(Pid) of
true -> timer:sleep(20), wait_dead(Pid, N - 1);
false -> ok
end.
stats_tops_test_() ->
{foreach, fun setup/0, fun cleanup/1, [
{"top events by rating order", fun test_event_rating_top/0},
{"top calendars by rating and reviews", fun test_calendar_tops/0},
{"rating update reorders top", fun test_rating_update/0},
{"review target tops all/pos/neg", fun test_review_target_tops/0},
{"report target tops", fun test_report_target_tops/0},
{"calendar pos/neg from reviews", fun test_calendar_pos_neg/0}
]}.
test_event_rating_top() ->
Now = calendar:universal_time(),
ok = mnesia:dirty_write(make_event(<<"e_low">>, 2.0, Now)),
ok = mnesia:dirty_write(make_event(<<"e_high">>, 5.0, Now)),
ok = mnesia:dirty_write(make_event(<<"e_mid">>, 3.5, Now)),
wait_top_event(<<"e_high">>, 30),
Top = core_event:get_top_events_by_rating(2),
Ids = [E#event.id || E <- Top],
?assertEqual([<<"e_high">>, <<"e_mid">>], Ids).
test_calendar_tops() ->
Now = calendar:universal_time(),
ok = mnesia:dirty_write(make_calendar(<<"c1">>, 4.0, 10, Now)),
ok = mnesia:dirty_write(make_calendar(<<"c2">>, 5.0, 2, Now)),
ok = mnesia:dirty_write(make_calendar(<<"c3">>, 3.0, 20, Now)),
wait_top_cal_rating(<<"c2">>, 30),
ByRating = [C#calendar.id || C <- core_calendar:get_top_calendars_by_rating(2)],
?assertEqual([<<"c2">>, <<"c1">>], ByRating),
ByReviews = [C#calendar.id || C <- core_calendar:get_top_calendars_by_reviews(2)],
?assertEqual([<<"c3">>, <<"c1">>], ByReviews).
test_rating_update() ->
Now = calendar:universal_time(),
E1 = make_event(<<"eu1">>, 4.0, Now),
E2 = make_event(<<"eu2">>, 3.0, Now),
ok = mnesia:dirty_write(E1),
ok = mnesia:dirty_write(E2),
wait_top_event(<<"eu1">>, 30),
ok = mnesia:dirty_write(E2#event{rating_avg = 4.5}),
wait_top_event(<<"eu2">>, 30),
Top = core_event:get_top_events_by_rating(1),
?assertEqual([<<"eu2">>], [E#event.id || E <- Top]).
test_review_target_tops() ->
Now = calendar:universal_time(),
ok = mnesia:dirty_write(make_review(<<"r1">>, calendar, <<"cA">>, 5, Now)),
ok = mnesia:dirty_write(make_review(<<"r2">>, calendar, <<"cA">>, 5, Now)),
ok = mnesia:dirty_write(make_review(<<"r3">>, event, <<"eA">>, 1, Now)),
ok = mnesia:dirty_write(make_review(<<"r4">>, calendar, <<"cB">>, 2, Now)),
wait_until(fun() ->
All = core_review:get_top_targets_by_reviews(3),
Keys = [{T, I} || {T, I, _} <- All],
lists:member({calendar, <<"cA">>}, Keys)
andalso lists:member({event, <<"eA">>}, Keys)
andalso lists:member({calendar, <<"cB">>}, Keys)
end, 40),
All = core_review:get_top_targets_by_reviews(3),
?assertMatch([{calendar, <<"cA">>, 2} | _], All),
AllKeys = [{T, I} || {T, I, _} <- All],
?assert(lists:member({event, <<"eA">>}, AllKeys)),
?assert(lists:member({calendar, <<"cB">>}, AllKeys)),
Pos = core_review:get_top_targets_by_positive_reviews(1),
?assertEqual([{calendar, <<"cA">>, 2}], Pos),
Neg = core_review:get_top_targets_by_negative_reviews(2),
NegIds = [{T, I} || {T, I, _} <- Neg],
?assert(lists:member({event, <<"eA">>}, NegIds)),
?assert(lists:member({calendar, <<"cB">>}, NegIds)).
test_report_target_tops() ->
Now = calendar:universal_time(),
ok = mnesia:dirty_write(make_report(<<"p1">>, calendar, <<"cX">>, Now)),
ok = mnesia:dirty_write(make_report(<<"p2">>, calendar, <<"cX">>, Now)),
ok = mnesia:dirty_write(make_report(<<"p3">>, event, <<"eX">>, Now)),
wait_until(fun() ->
core_report:get_top_targets_by_reports(2)
=:= [{calendar, <<"cX">>, 2}, {event, <<"eX">>, 1}]
end, 40),
Top = core_report:get_top_targets_by_reports(2),
?assertEqual([{calendar, <<"cX">>, 2}, {event, <<"eX">>, 1}], Top).
test_calendar_pos_neg() ->
Now = calendar:universal_time(),
ok = mnesia:dirty_write(make_calendar(<<"cp1">>, 0.0, 0, Now)),
ok = mnesia:dirty_write(make_calendar(<<"cp2">>, 0.0, 0, Now)),
ok = mnesia:dirty_write(make_review(<<"rp1">>, calendar, <<"cp1">>, 5, Now)),
ok = mnesia:dirty_write(make_review(<<"rp2">>, calendar, <<"cp1">>, 4, Now)),
ok = mnesia:dirty_write(make_review(<<"rn1">>, calendar, <<"cp2">>, 1, Now)),
wait_cal_pos(<<"cp1">>, 30),
Pos = [C#calendar.id || C <- core_calendar:get_top_calendars_by_positive_reviews(1)],
?assertEqual([<<"cp1">>], Pos),
Neg = [C#calendar.id || C <- core_calendar:get_top_calendars_by_negative_reviews(1)],
?assertEqual([<<"cp2">>], Neg).
make_review(Id, Type, TargetId, Rating, Now) ->
#review{
id = Id, user_id = <<"u1">>, target_type = Type, target_id = TargetId,
rating = Rating, comment = <<>>, status = visible, reason = <<>>,
likes = 0, dislikes = 0, created_at = Now, updated_at = Now
}.
make_report(Id, Type, TargetId, Now) ->
#report{
id = Id, reporter_id = <<"u1">>, target_type = Type, target_id = TargetId,
reason = <<"spam">>, status = pending, created_at = Now,
resolved_at = undefined, resolved_by = <<>>
}.
make_event(Id, Avg, Now) ->
#event{
id = Id, calendar_id = <<"cal">>, title = Id, description = <<>>,
event_type = single, start_time = Now, duration = 60,
recurrence_rule = <<>>, master_id = <<>>, is_instance = false,
specialist_id = <<>>, location = undefined, tags = [],
capacity = 10, online_link = <<>>, status = active, reason = <<>>,
rating_avg = Avg, rating_count = 1, attachments = [], edit_history = [],
created_at = Now, updated_at = Now
}.
make_calendar(Id, Avg, Count, Now) ->
#calendar{
id = Id, owner_id = <<"u1">>, title = Id, description = <<>>,
short_name = Id, category = <<>>, color = <<>>, image_url = <<>>,
settings = #{}, tags = [], type = personal, confirmation = auto,
rating_avg = Avg, rating_count = Count, status = active, reason = <<>>,
created_at = Now, updated_at = Now
}.
wait_top_event(ExpectedId, 0) ->
Top = core_event:get_top_events_by_rating(1),
?assertEqual([ExpectedId], [E#event.id || E <- Top]);
wait_top_event(ExpectedId, N) ->
case core_event:get_top_events_by_rating(1) of
[#event{id = ExpectedId}] -> ok;
_ -> timer:sleep(25), wait_top_event(ExpectedId, N - 1)
end.
wait_top_cal_rating(ExpectedId, 0) ->
Top = core_calendar:get_top_calendars_by_rating(1),
?assertEqual([ExpectedId], [C#calendar.id || C <- Top]);
wait_top_cal_rating(ExpectedId, N) ->
case core_calendar:get_top_calendars_by_rating(1) of
[#calendar{id = ExpectedId}] -> ok;
_ -> timer:sleep(25), wait_top_cal_rating(ExpectedId, N - 1)
end.
wait_until(Pred, 0) ->
?assertEqual(true, Pred());
wait_until(Pred, N) ->
case Pred() of
true -> ok;
false -> timer:sleep(25), wait_until(Pred, N - 1)
end.
wait_cal_pos(ExpectedId, 0) ->
Top = core_calendar:get_top_calendars_by_positive_reviews(1),
?assertEqual([ExpectedId], [C#calendar.id || C <- Top]);
wait_cal_pos(ExpectedId, N) ->
case core_calendar:get_top_calendars_by_positive_reviews(1) of
[#calendar{id = ExpectedId}] -> ok;
_ -> timer:sleep(25), wait_cal_pos(ExpectedId, N - 1)
end.