-module(logic_search_tests). -include_lib("eunit/include/eunit.hrl"). -include("records.hrl"). setup() -> mnesia:start(), mnesia:create_table(user, [ {attributes, record_info(fields, user)}, {ram_copies, [node()]} ]), mnesia:create_table(calendar, [ {attributes, record_info(fields, calendar)}, {ram_copies, [node()]} ]), mnesia:create_table(event, [ {attributes, record_info(fields, event)}, {ram_copies, [node()]} ]), ok. cleanup(_) -> mnesia:delete_table(event), mnesia:delete_table(calendar), mnesia:delete_table(user), mnesia:stop(), 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}, {"Combined search", fun test_combined_search/0}, {"Search calendars", fun test_search_calendars/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} ]}. %% Вспомогательные функции create_test_user(Role) -> UserId = base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}), User = #user{ id = UserId, email = <>, 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) -> {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. %% Тесты test_search_events_by_text() -> OwnerId = create_test_user(user), CalendarId = create_test_calendar(OwnerId, personal, []), StartTime = {{2026, 6, 1}, {10, 0, 0}}, 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 = #{}, {ok, Total, Results} = logic_search:search(<<"event">>, <<"Python">>, OwnerId, Params), ?assertEqual(1, Total), ?assertEqual(1, length(Results)), {ok, Total2, _Results2} = 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, personal, []), StartTime = {{2026, 6, 1}, {10, 0, 0}}, 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), % Поиск по одному тегу Params = #{tags => <<"python">>}, {ok, Total, _} = logic_search:search(<<"event">>, undefined, OwnerId, Params), ?assertEqual(2, Total), % Event 1 и Event 3 % Поиск по тегу, который есть только у одного события Params2 = #{tags => <<"workshop">>}, {ok, Total2, _} = logic_search:search(<<"event">>, undefined, OwnerId, Params2), ?assertEqual(1, Total2), % Только Event 1 % Поиск по тегу, которого нет Params3 = #{tags => <<"nonexistent">>}, {ok, Total3, _} = logic_search:search(<<"event">>, undefined, OwnerId, Params3), ?assertEqual(0, Total3). test_search_events_by_date() -> OwnerId = create_test_user(user), CalendarId = create_test_calendar(OwnerId, personal, []), create_test_event(CalendarId, <<"June Event">>, <<"">>, {{2026, 6, 15}, {10, 0, 0}}, [], undefined), create_test_event(CalendarId, <<"July Event">>, <<"">>, {{2026, 7, 15}, {10, 0, 0}}, [], undefined), create_test_event(CalendarId, <<"August Event">>, <<"">>, {{2026, 8, 15}, {10, 0, 0}}, [], undefined), Params = #{from => {{2026, 6, 1}, {0, 0, 0}}, to => {{2026, 6, 30}, {23, 59, 59}}}, {ok, Total, _} = 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, personal, []), StartTime = {{2026, 6, 1}, {10, 0, 0}}, 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), Params = #{lat => 55.7558, lon => 37.6173, radius => 10}, {ok, Total, _Results} = logic_search:search(<<"event">>, undefined, OwnerId, Params), ?assertEqual(1, Total), Params2 = #{lat => 59.9343, lon => 30.3351, radius => 10}, {ok, Total2, _} = logic_search:search(<<"event">>, undefined, OwnerId, Params2), ?assertEqual(1, Total2). test_combined_search() -> OwnerId = create_test_user(user), CalendarId = create_test_calendar(OwnerId, personal, []), StartTime = {{2026, 6, 15}, {10, 0, 0}}, create_test_event(CalendarId, <<"Python Workshop">>, <<"Learn Python">>, StartTime, [<<"python">>, <<"workshop">>], undefined), create_test_event(CalendarId, <<"Python Advanced">>, <<"Advanced Python">>, {{2026, 7, 15}, {10, 0, 0}}, [<<"python">>, <<"advanced">>], undefined), Params = #{from => {{2026, 6, 1}, {0, 0, 0}}, to => {{2026, 6, 30}, {23, 59, 59}}, tags => <<"python">>}, {ok, Total, _} = logic_search:search(<<"event">>, <<"Workshop">>, OwnerId, Params), ?assertEqual(1, Total). test_search_calendars() -> OwnerId = create_test_user(user), create_test_calendar(OwnerId, personal, [<<"tech">>, <<"programming">>]), create_test_calendar(OwnerId, commercial, [<<"yoga">>, <<"wellness">>]), Params = #{tags => <<"tech">>}, {ok, Total, _} = logic_search:search(<<"calendar">>, undefined, OwnerId, Params), ?assertEqual(1, Total), {ok, Total2, _} = logic_search:search(<<"calendar">>, <<"Calendar">>, OwnerId, #{}), ?assertEqual(2, Total2). test_search_all() -> OwnerId = create_test_user(user), CalendarId = create_test_calendar(OwnerId, personal, []), StartTime = {{2026, 6, 1}, {10, 0, 0}}, 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, personal, []), StartTime = {{2026, 6, 1}, {10, 0, 0}}, 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]), Params = #{limit => 2, offset => 0}, {ok, Total, Results} = logic_search:search(<<"event">>, undefined, OwnerId, Params), ?assertEqual(5, Total), ?assertEqual(2, length(Results)), Params2 = #{limit => 2, offset => 2}, {ok, _, Results2} = logic_search:search(<<"event">>, undefined, OwnerId, Params2), ?assertEqual(2, length(Results2)), Params3 = #{limit => 2, offset => 4}, {ok, _, Results3} = logic_search:search(<<"event">>, undefined, OwnerId, Params3), ?assertEqual(1, length(Results3)). test_sorting() -> OwnerId = create_test_user(user), CalendarId = create_test_calendar(OwnerId, personal, []), create_test_event(CalendarId, <<"Early">>, <<"">>, {{2026, 6, 1}, {10, 0, 0}}, [], undefined), create_test_event(CalendarId, <<"Late">>, <<"">>, {{2026, 6, 30}, {10, 0, 0}}, [], undefined), Params = #{sort => <<"start_time">>, order => <<"asc">>}, {ok, _, [First | _]} = logic_search:search(<<"event">>, undefined, OwnerId, Params), ?assertMatch(#{title := <<"Early">>}, First), Params2 = #{sort => <<"start_time">>, order => <<"desc">>}, {ok, _, [First2 | _]} = logic_search:search(<<"event">>, undefined, OwnerId, Params2), ?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 = {{2026, 6, 1}, {10, 0, 0}}, create_test_event(PersonalId, <<"Personal Event">>, <<"">>, StartTime, [], undefined), create_test_event(CommercialId, <<"Public Event">>, <<"">>, StartTime, [], undefined), % Другой пользователь видит только коммерческие события {ok, Total, Results} = 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 = {{2026, 6, 1}, {10, 0, 0}}, create_test_event(CalendarId, <<"Test">>, <<"">>, StartTime, [], undefined), {ok, Total, Results} = logic_search:search(<<"event">>, <<"nonexistent">>, OwnerId, #{}), ?assertEqual(0, Total), ?assertEqual([], Results).