feat(archive): month snapshots on the same node, drop extra-node and HTML /view.
Fixes EventHub/EventHubBack#76 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,61 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @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").
|
||||
@@ -32,7 +32,6 @@ all() ->
|
||||
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,
|
||||
@@ -122,9 +121,6 @@ user_test_calendars(_Config) ->
|
||||
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().
|
||||
|
||||
|
||||
@@ -224,7 +224,11 @@ def do_random_action(bot):
|
||||
resp_cal = request("GET", f"{base}/v1/calendars", headers=headers)
|
||||
if resp_cal and resp_cal.status_code == 200 and resp_cal.json():
|
||||
cal = random.choice(resp_cal.json())
|
||||
request("GET", f"{base}/v1/calendars/{cal['id']}/view?month=2026-06", headers=headers)
|
||||
request(
|
||||
"GET",
|
||||
f"{base}/v1/calendars/{cal['id']}/events?from=2026-06-01T00:00:00Z&to=2026-06-30T23:59:59Z",
|
||||
headers=headers,
|
||||
)
|
||||
elif action == 14:
|
||||
request("POST", f"{base}/v1/refresh", json={"refresh_token": "dummy"}, headers=headers)
|
||||
except Exception as e:
|
||||
|
||||
@@ -55,9 +55,9 @@
|
||||
</request>
|
||||
<thinktime min="500" max="2000" random="true"/>
|
||||
|
||||
<!-- 5. GET /v1/calendars/:id/view?month=2026-06 – HTML-представление -->
|
||||
<!-- 5. GET /v1/calendars/:id/events – месяц (hot + archive merge) -->
|
||||
<request subst="true">
|
||||
<http url="/v1/calendars/%%_calendar_id%%/view?month=2026-06" method="GET">
|
||||
<http url="/v1/calendars/%%_calendar_id%%/events?from=2026-06-01T00:00:00Z&to=2026-06-30T23:59:59Z" method="GET">
|
||||
<http_header name="Authorization" value="Bearer %%_token%%"/>
|
||||
</http>
|
||||
</request>
|
||||
|
||||
@@ -99,7 +99,8 @@ ensure_indexes(Tables) when is_list(Tables) ->
|
||||
{push_subscription, [user_id, endpoint]},
|
||||
{auth_session, [family_id, subject_id]},
|
||||
{report, [resolved_by]},
|
||||
{ticket, [assigned_to]}
|
||||
{ticket, [assigned_to]},
|
||||
{month_snapshot, [calendar_id, year_month]}
|
||||
]),
|
||||
ok.
|
||||
|
||||
@@ -204,7 +205,9 @@ table_opts(node_metric) ->
|
||||
[{ram_copies, [node()]}, {local_content, true},
|
||||
{attributes, record_info(fields, node_metric)}];
|
||||
table_opts(schema_migration) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, schema_migration)}].
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, schema_migration)}];
|
||||
table_opts(month_snapshot) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, month_snapshot)}].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Domain seeds
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
-module(logic_month_archive_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [event, month_snapshot, booking, calendar]).
|
||||
-define(CAL, <<"cal_arch_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
Tmp = filename:join("/tmp", "eh_arch_" ++ integer_to_list(erlang:unique_integer([positive]))),
|
||||
ok = filelib:ensure_dir(filename:join(Tmp, "dummy")),
|
||||
true = os:putenv("UPLOAD_DIR", Tmp),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:clear_tables(?TABLES),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
logic_month_archive_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"closed month is not writable", fun test_assert_writable/0},
|
||||
{"snapshot removes hot event and serves json", fun test_snapshot_roundtrip/0}
|
||||
]}.
|
||||
|
||||
test_assert_writable() ->
|
||||
{{Y, M, _}, _} = calendar:universal_time(),
|
||||
?assertEqual(ok, logic_month_archive:assert_writable({{Y, M, 15}, {12, 0, 0}})),
|
||||
?assertEqual({error, archived},
|
||||
logic_month_archive:assert_writable({{2020, 1, 10}, {12, 0, 0}})).
|
||||
|
||||
test_snapshot_roundtrip() ->
|
||||
{ok, Ev} = core_event:create(?CAL, <<"old">>, {{2020, 6, 10}, {10, 0, 0}}, 60),
|
||||
ok = logic_month_archive:snapshot_month(?CAL, 2020, 6),
|
||||
?assertEqual([], mnesia:dirty_read(event, Ev#event.id)),
|
||||
Json = logic_month_archive:events_json_in_range(
|
||||
?CAL, {{2020, 6, 1}, {0, 0, 0}}, {{2020, 6, 30}, {23, 59, 59}}),
|
||||
?assertEqual(1, length(Json)),
|
||||
[Map] = Json,
|
||||
Id = maps:get(<<"id">>, Map, maps:get(id, Map, undefined)),
|
||||
?assertEqual(Ev#event.id, Id).
|
||||
@@ -18,7 +18,8 @@
|
||||
"20260814193000_waitlist_entry",
|
||||
"20260815200000_push_subscription",
|
||||
"20260815220000_calendar_share_invite",
|
||||
"20260816180000_auth_session_device"
|
||||
"20260816180000_auth_session_device",
|
||||
"20260817140000_month_snapshot"
|
||||
]).
|
||||
|
||||
setup() ->
|
||||
|
||||
Reference in New Issue
Block a user