feat: upsert stats counters, daily buckets and ETS tops for admin metrics. Refs EventHub/EventHubBack#42 #43 #44 #45 #46
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
-module(stats_collector_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [user, event, calendar, booking, review, report, ticket,
|
||||
subscription, 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_collector_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"backfill from existing rows", fun test_backfill/0},
|
||||
{"live write updates dims and daily", fun test_live_write/0},
|
||||
{"status transition moves counters", fun test_status_move/0},
|
||||
{"core_stats reads via collector", fun test_core_stats_api/0},
|
||||
{"ticket avg resolution from counters", fun test_ticket_avg_resolution/0},
|
||||
{"report avg resolution from counters", fun test_report_avg_resolution/0}
|
||||
]}.
|
||||
|
||||
test_backfill() ->
|
||||
Now = calendar:universal_time(),
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => <<"u1">>, status => pending, role => user, created_at => Now
|
||||
})),
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => <<"u2">>, status => active, role => user, created_at => Now
|
||||
})),
|
||||
ok = stats_collector:rebuild(),
|
||||
?assertEqual(1, core_stats:with_counter({user_by_status, pending}, fun() -> -1 end)),
|
||||
?assertEqual(1, core_stats:with_counter({user_by_status, active}, fun() -> -1 end)),
|
||||
{Date, _} = Now,
|
||||
From = {Date, {0, 0, 0}},
|
||||
To = {Date, {23, 59, 59}},
|
||||
Daily = core_stats:with_daily(users_created, From, To, fun() -> [] end),
|
||||
?assertEqual([{Date, 2}], Daily).
|
||||
|
||||
test_live_write() ->
|
||||
Now = calendar:universal_time(),
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => <<"live1">>, status => active, created_at => Now
|
||||
})),
|
||||
wait_counter({user_by_status, active}, 1, 20),
|
||||
?assertEqual(1, core_stats:with_counter({user_by_status, active}, fun() -> 0 end)).
|
||||
|
||||
test_status_move() ->
|
||||
Now = calendar:universal_time(),
|
||||
User = eh_test_support:make_user(#{
|
||||
id => <<"move1">>, status => pending, created_at => Now
|
||||
}),
|
||||
ok = mnesia:dirty_write(User),
|
||||
wait_counter({user_by_status, pending}, 1, 20),
|
||||
ok = mnesia:dirty_write(User#user{status = active}),
|
||||
wait_counter({user_by_status, pending}, 0, 20),
|
||||
wait_counter({user_by_status, active}, 1, 20),
|
||||
?assertEqual(0, core_stats:with_counter({user_by_status, pending}, fun() -> -1 end)),
|
||||
?assertEqual(1, core_stats:with_counter({user_by_status, active}, fun() -> -1 end)).
|
||||
|
||||
test_core_stats_api() ->
|
||||
?assert(core_stats:available()),
|
||||
Now = calendar:universal_time(),
|
||||
ok = mnesia:dirty_write(#report{
|
||||
id = <<"r1">>,
|
||||
reporter_id = <<"u1">>,
|
||||
target_type = calendar,
|
||||
target_id = <<"c1">>,
|
||||
reason = <<"spam">>,
|
||||
status = pending,
|
||||
created_at = Now,
|
||||
resolved_at = undefined,
|
||||
resolved_by = <<>>
|
||||
}),
|
||||
wait_counter({report_by_status, pending}, 1, 20),
|
||||
{ok, Dim} = core_stats:get_dim(report, status),
|
||||
?assertEqual(1, proplists:get_value(pending, Dim, 0)).
|
||||
|
||||
test_ticket_avg_resolution() ->
|
||||
First = {{2026, 1, 1}, {0, 0, 0}},
|
||||
Closed = {{2026, 1, 1}, {2, 0, 0}}, %% 2 hours
|
||||
ok = mnesia:dirty_write(#ticket{
|
||||
id = <<"t_avg">>, reporter_id = <<"u1">>, error_hash = <<"h">>,
|
||||
error_message = <<"e">>, stacktrace = <<>>, context = <<>>,
|
||||
count = 1, first_seen = First, last_seen = Closed, status = closed,
|
||||
assigned_to = <<>>, resolution_note = <<>>, closed_at = Closed,
|
||||
source = <<"backend">>
|
||||
}),
|
||||
wait_counter(ticket_resolved_count, 1, 20),
|
||||
Avg = core_ticket:avg_resolution_time(),
|
||||
?assertEqual(2.0, Avg).
|
||||
|
||||
test_report_avg_resolution() ->
|
||||
Created = {{2026, 1, 1}, {0, 0, 0}},
|
||||
Resolved = {{2026, 1, 1}, {3, 0, 0}}, %% 3 hours
|
||||
ok = mnesia:dirty_write(#report{
|
||||
id = <<"r_avg">>, reporter_id = <<"u1">>, target_type = calendar,
|
||||
target_id = <<"c1">>, reason = <<"spam">>, status = reviewed,
|
||||
created_at = Created, resolved_at = Resolved, resolved_by = <<"adm">>
|
||||
}),
|
||||
wait_counter({report_resolved_count, reviewed}, 1, 20),
|
||||
Avg = core_report:avg_resolution_time(reviewed),
|
||||
?assertEqual(3.0, Avg).
|
||||
|
||||
wait_counter(Key, Expected, 0) ->
|
||||
Actual = core_stats:with_counter(Key, fun() -> -999 end),
|
||||
?assertEqual(Expected, Actual);
|
||||
wait_counter(Key, Expected, N) ->
|
||||
case core_stats:with_counter(Key, fun() -> -999 end) of
|
||||
Expected -> ok;
|
||||
_ ->
|
||||
timer:sleep(25),
|
||||
wait_counter(Key, Expected, N - 1)
|
||||
end.
|
||||
Reference in New Issue
Block a user