Files
EventHubBack/test/unit/logic_search_tests.erl
T
aleksey 6b70e1336d
CI / test (push) Successful in 7m38s
CI / deploy-ift (push) Successful in 3m27s
CI / e2e-ift (push) Successful in 1m16s
CI / deploy-stage (push) Successful in 2m18s
CI / e2e-stage (push) Successful in 4m11s
feat(geo): Photon proxy, address-only location, nearby search. Refs EventHub/EventHubBack#77
2026-08-17 23:23:10 +03:00

359 lines
15 KiB
Erlang
Executable File

-module(logic_search_tests).
-include_lib("eunit/include/eunit.hrl").
-include("records.hrl").
-define(TABLES, [user, calendar, event, subscription, calendar_share]).
setup() ->
eh_test_support:start_mnesia(),
eh_test_support:ensure_tables(?TABLES),
ok.
cleanup(_) ->
eh_test_support:delete_tables(?TABLES),
eh_test_support:stop_mnesia(),
ok.
logic_search_test_() ->
{foreach,
fun setup/0,
fun cleanup/1,
[
{"Search events by text", fun test_search_events_by_text/0},
{"Search events by tags", fun test_search_events_by_tags/0},
{"Search events by date range", fun test_search_events_by_date/0},
{"Search events by location", fun test_search_events_by_location/0},
{"Search calendars by location", fun test_search_calendars_by_location/0},
{"Geo without radius keeps distant hits", fun test_geo_no_radius/0},
{"Geo sort by distance", fun test_geo_sort_distance/0},
{"Combined search", fun test_combined_search/0},
{"Search calendars", fun test_search_calendars/0},
{"Search calendars include image_url", fun test_search_calendars_image_url/0},
{"Search all", fun test_search_all/0},
{"Pagination", fun test_pagination/0},
{"Sorting", fun test_sorting/0},
{"Access control in search", fun test_access_control/0},
{"Empty search results", fun test_empty_search/0},
{"Discovery tops without filters", fun test_discovery_returns_tops/0},
{"Query switches to filtered search", fun test_discovery_with_q_uses_filter/0},
{"Personal excluded from search", fun test_personal_excluded_from_search/0}
]}.
%% Вспомогательные функции
create_test_user(Role) ->
UserId = base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}),
User = #user{
id = UserId,
email = <<UserId/binary, "@test.com">>,
password_hash = <<"hash">>,
role = Role,
status = active,
created_at = calendar:universal_time(),
updated_at = calendar:universal_time()
},
mnesia:dirty_write(User),
UserId.
create_test_calendar(OwnerId, Type, Tags) ->
case Type of
commercial ->
_ = core_subscription:create(OwnerId, monthly, true);
_ ->
ok
end,
{ok, Calendar} = core_calendar:create(OwnerId, <<"Test Calendar">>, <<"Description">>, manual),
core_calendar:update(Calendar#calendar.id, [{type, Type}, {tags, Tags}]),
{ok, Updated} = core_calendar:get_by_id(Calendar#calendar.id),
Updated#calendar.id.
create_test_event(CalendarId, Title, Description, StartTime, Tags, Location) ->
{ok, Event} = core_event:create(CalendarId, Title, StartTime, 60),
Updates = [{description, Description}, {tags, Tags}],
Updates2 = case Location of
undefined -> Updates;
_ -> [{location, Location} | Updates]
end,
core_event:update(Event#event.id, Updates2),
{ok, Updated} = core_event:get_by_id(Event#event.id),
Updated#event.id.
add_days(DateTime, Days) ->
Sec = calendar:datetime_to_gregorian_seconds(DateTime) + Days * 86400,
calendar:gregorian_seconds_to_datetime(Sec).
events_from({ok, Total, #{<<"events">> := Events}}) ->
{Total, Events}.
calendars_from({ok, Total, #{<<"calendars">> := Calendars}}) ->
{Total, Calendars}.
%% Тесты
test_search_events_by_text() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
create_test_event(CalendarId, <<"Python Workshop">>, <<"Learn Python">>, StartTime, [], undefined),
create_test_event(CalendarId, <<"JavaScript Conference">>, <<"JS for everyone">>, StartTime, [], undefined),
create_test_event(CalendarId, <<"Yoga Class">>, <<"Morning yoga">>, StartTime, [], undefined),
Params = #{},
{Total, Results} = events_from(logic_search:search(<<"event">>, <<"Python">>, OwnerId, Params)),
?assertEqual(1, Total),
?assertEqual(1, length(Results)),
{Total2, _} = events_from(logic_search:search(<<"event">>, <<"conference">>, OwnerId, Params)),
?assertEqual(1, Total2).
test_search_events_by_tags() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
create_test_event(CalendarId, <<"Event 1">>, <<"">>, StartTime, [<<"python">>, <<"workshop">>], undefined),
create_test_event(CalendarId, <<"Event 2">>, <<"">>, StartTime, [<<"javascript">>, <<"conference">>], undefined),
create_test_event(CalendarId, <<"Event 3">>, <<"">>, StartTime, [<<"python">>, <<"advanced">>], undefined),
{Total, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{tags => <<"python">>})),
?assertEqual(2, Total),
{Total2, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{tags => <<"workshop">>})),
?assertEqual(1, Total2),
{Total3, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{tags => <<"nonexistent">>})),
?assertEqual(0, Total3).
test_search_events_by_date() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
Base = eh_test_support:future_start(),
create_test_event(CalendarId, <<"Near Event">>, <<"">>, Base, [], undefined),
create_test_event(CalendarId, <<"Mid Event">>, <<"">>, add_days(Base, 30), [], undefined),
create_test_event(CalendarId, <<"Far Event">>, <<"">>, add_days(Base, 60), [], undefined),
Params = #{from => add_days(Base, -1), to => add_days(Base, 7)},
{Total, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, Params)),
?assertEqual(1, Total).
test_search_events_by_location() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
MoscowLoc = #location{address = <<"Moscow">>, lat = 55.7558, lon = 37.6173},
SpbLoc = #location{address = <<"SPb">>, lat = 59.9343, lon = 30.3351},
create_test_event(CalendarId, <<"Moscow Event">>, <<"">>, StartTime, [], MoscowLoc),
create_test_event(CalendarId, <<"SPb Event">>, <<"">>, StartTime, [], SpbLoc),
{Total, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
#{lat => 55.7558, lon => 37.6173, radius => 10})),
?assertEqual(1, Total),
{Total2, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
#{lat => 59.9343, lon => 30.3351, radius => 10})),
?assertEqual(1, Total2).
test_search_calendars_by_location() ->
OwnerId = create_test_user(user),
NearId = create_test_calendar(OwnerId, commercial, []),
FarId = create_test_calendar(OwnerId, commercial, []),
set_default_location(NearId, <<"Moscow">>, 55.7558, 37.6173),
set_default_location(FarId, <<"SPb">>, 59.9343, 30.3351),
{Total, Hits} = calendars_from(logic_search:search(<<"calendar">>, undefined, OwnerId,
#{lat => 55.7558, lon => 37.6173, radius => 10})),
?assertEqual(1, Total),
[Hit] = Hits,
?assertEqual(NearId, maps:get(id, Hit)),
?assert(maps:is_key(distance_km, Hit)).
test_geo_no_radius() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
MoscowLoc = #location{address = <<"Moscow">>, lat = 55.7558, lon = 37.6173},
SpbLoc = #location{address = <<"SPb">>, lat = 59.9343, lon = 30.3351},
create_test_event(CalendarId, <<"Moscow Event">>, <<"">>, StartTime, [], MoscowLoc),
create_test_event(CalendarId, <<"SPb Event">>, <<"">>, StartTime, [], SpbLoc),
{Total, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
#{lat => 55.7558, lon => 37.6173})),
?assertEqual(2, Total).
test_geo_sort_distance() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
MoscowLoc = #location{address = <<"Moscow">>, lat = 55.7558, lon = 37.6173},
SpbLoc = #location{address = <<"SPb">>, lat = 59.9343, lon = 30.3351},
create_test_event(CalendarId, <<"Moscow Event">>, <<"">>, StartTime, [], MoscowLoc),
create_test_event(CalendarId, <<"SPb Event">>, <<"">>, StartTime, [], SpbLoc),
{_, [First | _]} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
#{lat => 55.7558, lon => 37.6173})),
?assertMatch(#{title := <<"Moscow Event">>}, First),
?assert(maps:is_key(distance_km, First)).
set_default_location(CalendarId, Addr, Lat, Lon) ->
{ok, Cal} = core_calendar:get_by_id(CalendarId),
Settings0 = case Cal#calendar.settings of
M when is_map(M) -> M;
_ -> #{}
end,
Settings = Settings0#{
<<"default_location">> => #{
<<"address">> => Addr,
<<"lat">> => Lat,
<<"lon">> => Lon
}
},
core_calendar:update(CalendarId, [{settings, Settings}]).
test_combined_search() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
Base = eh_test_support:future_start(),
create_test_event(CalendarId, <<"Python Workshop">>, <<"Learn Python">>, Base,
[<<"python">>, <<"workshop">>], undefined),
create_test_event(CalendarId, <<"Python Advanced">>, <<"Advanced Python">>, add_days(Base, 30),
[<<"python">>, <<"advanced">>], undefined),
Params = #{from => add_days(Base, -1), to => add_days(Base, 7), tags => <<"python">>},
{Total, _} = events_from(logic_search:search(<<"event">>, <<"Workshop">>, OwnerId, Params)),
?assertEqual(1, Total).
test_search_calendars() ->
OwnerId = create_test_user(user),
create_test_calendar(OwnerId, commercial, [<<"tech">>, <<"programming">>]),
create_test_calendar(OwnerId, commercial, [<<"yoga">>, <<"wellness">>]),
%% personal never discoverable
create_test_calendar(OwnerId, personal, [<<"tech">>, <<"private">>]),
{Total, _} = calendars_from(logic_search:search(<<"calendar">>, undefined, OwnerId, #{tags => <<"tech">>})),
?assertEqual(1, Total),
{Total2, _} = calendars_from(logic_search:search(<<"calendar">>, <<"Calendar">>, OwnerId, #{})),
?assertEqual(2, Total2).
test_search_calendars_image_url() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, [<<"cover">>]),
{ok, _} = core_calendar:update(CalendarId, [{image_url, <<"https://cdn.example/cal.jpg">>}]),
{_, Cals} = calendars_from(logic_search:search(<<"calendar">>, <<"Calendar">>, OwnerId, #{})),
[Hit | _] = [C || C <- Cals, maps:get(id, C) =:= CalendarId],
?assertEqual(<<"https://cdn.example/cal.jpg">>, maps:get(image_url, Hit)).
test_search_all() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
create_test_event(CalendarId, <<"Test Event">>, <<"">>, StartTime, [], undefined),
{ok, Total, Results} = logic_search:search(undefined, <<"Test">>, OwnerId, #{}),
?assert(Total >= 2),
?assert(maps:is_key(<<"events">>, Results)),
?assert(maps:is_key(<<"calendars">>, Results)).
test_pagination() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
lists:foreach(fun(I) ->
Title = iolist_to_binary(["Event ", integer_to_binary(I)]),
create_test_event(CalendarId, Title, <<"">>, StartTime, [], undefined)
end, [1, 2, 3, 4, 5]),
{Total, Results} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{limit => 2, offset => 0})),
?assertEqual(5, Total),
?assertEqual(2, length(Results)),
{_, Results2} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{limit => 2, offset => 2})),
?assertEqual(2, length(Results2)),
{_, Results3} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{limit => 2, offset => 4})),
?assertEqual(1, length(Results3)).
test_sorting() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
Base = eh_test_support:future_start(),
create_test_event(CalendarId, <<"Early">>, <<"">>, Base, [], undefined),
create_test_event(CalendarId, <<"Late">>, <<"">>, add_days(Base, 29), [], undefined),
{_, [First | _]} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
#{sort => <<"start_time">>, order => <<"asc">>})),
?assertMatch(#{title := <<"Early">>}, First),
{_, [First2 | _]} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
#{sort => <<"start_time">>, order => <<"desc">>})),
?assertMatch(#{title := <<"Late">>}, First2).
test_access_control() ->
OwnerId = create_test_user(user),
OtherId = create_test_user(user),
PersonalId = create_test_calendar(OwnerId, personal, []),
CommercialId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
create_test_event(PersonalId, <<"Personal Event">>, <<"">>, StartTime, [], undefined),
create_test_event(CommercialId, <<"Public Event">>, <<"">>, StartTime, [], undefined),
{Total, Results} = events_from(logic_search:search(<<"event">>, undefined, OtherId, #{})),
?assertEqual(1, Total),
[Event] = Results,
?assertMatch(#{title := <<"Public Event">>}, Event).
test_empty_search() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, personal, []),
StartTime = eh_test_support:future_start(),
create_test_event(CalendarId, <<"Test">>, <<"">>, StartTime, [], undefined),
{Total, Results} = events_from(logic_search:search(<<"event">>, <<"nonexistent">>, OwnerId, #{})),
?assertEqual(0, Total),
?assertEqual([], Results).
test_discovery_returns_tops() ->
OwnerId = create_test_user(user),
ViewerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
LowId = create_test_event(CalendarId, <<"Low Rated">>, <<"">>, StartTime, [], undefined),
HighId = create_test_event(CalendarId, <<"High Rated">>, <<"">>, StartTime, [], undefined),
{ok, _} = core_event:update(LowId, [{rating_avg, 1.0}, {rating_count, 1}]),
{ok, _} = core_event:update(HighId, [{rating_avg, 5.0}, {rating_count, 10}]),
stats_tops:init_tables(),
stats_tops:rebuild(),
{Total, [First | _]} = events_from(logic_search:search(<<"event">>, undefined, ViewerId, #{})),
?assertEqual(2, Total),
?assertMatch(#{title := <<"High Rated">>}, First).
test_discovery_with_q_uses_filter() ->
OwnerId = create_test_user(user),
CalendarId = create_test_calendar(OwnerId, commercial, []),
StartTime = eh_test_support:future_start(),
LowId = create_test_event(CalendarId, <<"Alpha">>, <<"">>, StartTime, [], undefined),
HighId = create_test_event(CalendarId, <<"Beta">>, <<"">>, StartTime, [], undefined),
{ok, _} = core_event:update(LowId, [{rating_avg, 1.0}]),
{ok, _} = core_event:update(HighId, [{rating_avg, 5.0}]),
stats_tops:init_tables(),
stats_tops:rebuild(),
{Total, Results} = events_from(logic_search:search(<<"event">>, <<"Alpha">>, OwnerId, #{})),
?assertEqual(1, Total),
?assertMatch([#{title := <<"Alpha">>}], Results).
test_personal_excluded_from_search() ->
OwnerId = create_test_user(user),
PersonalId = create_test_calendar(OwnerId, personal, [<<"mine">>]),
StartTime = eh_test_support:future_start(),
create_test_event(PersonalId, <<"Secret Personal">>, <<"">>, StartTime, [], undefined),
{TotalE, _} = events_from(logic_search:search(<<"event">>, <<"Secret">>, OwnerId, #{})),
?assertEqual(0, TotalE),
{TotalC, _} = calendars_from(logic_search:search(<<"calendar">>, undefined, OwnerId, #{tags => <<"mine">>})),
?assertEqual(0, TotalC).