%% SPDX-License-Identifier: AGPL-3.0-or-later -module(guild_data_tests). -typing([eqwalizer]). -include_lib("eunit/include/eunit.hrl"). get_guild_data_membership_gate_test() -> State = test_state(), {reply, Reply1, _} = guild_data:get_guild_data(#{user_id => 999}, State), ?assertEqual(null, maps:get(guild_data, Reply1)), ?assertEqual(<<"forbidden">>, maps:get(error_reason, Reply1)), {reply, Reply2, _} = guild_data:get_guild_data(#{user_id => 200}, State), Guild = maps:get(guild_data, Reply2), ?assertEqual(<<"Fluxer">>, maps:get(<<"name">>, Guild)), Roles = maps:get(<<"roles">>, Guild, []), ?assertMatch([_ | _], Roles). get_guild_state_filters_channels_test() -> State = test_state(), GuildState = guild_data:get_guild_state(200, State), Channels = maps:get(<<"channels">>, GuildState), ?assert(lists:any(fun(Chan) -> maps:get(<<"id">>, Chan) =:= 500 end, Channels)), ?assertEqual(<<"2024-01-01T00:00:00Z">>, maps:get(<<"joined_at">>, GuildState)). has_own_member(UserId, GuildState) -> Bin = integer_to_binary(UserId), lists:any( fun(M) -> maps:get(<<"id">>, maps:get(<<"user">>, M, #{}), undefined) =:= Bin end, maps:get(<<"members">>, GuildState, []) ). get_guild_state_non_member_returns_partial_guild_create_test() -> State = test_state(), MemberView = guild_data:get_guild_state(200, State), NonMemberView = guild_data:get_guild_state(999, State), ?assert(has_own_member(200, MemberView)), ?assertMatch([_ | _], maps:get(<<"channels">>, MemberView, [])), ?assertNot(has_own_member(999, NonMemberView)), ?assertEqual([], maps:get(<<"channels">>, NonMemberView, [])), ?assertEqual(false, maps:get(<<"unavailable">>, NonMemberView, false)), {reply, Reply, _} = guild_data:get_guild_data(#{user_id => 999}, State), ?assertEqual(<<"forbidden">>, maps:get(error_reason, Reply)). find_everyone_viewable_text_channel_test() -> State = test_state(), Data = guild_data_index:ensure_data_map(State), Channels = maps:get(<<"channels">>, Data), ChannelId = guild_data:find_everyone_viewable_text_channel(Channels, State), ?assertEqual(500, ChannelId). find_everyone_viewable_text_channel_uses_category_order_test() -> GuildId = 100, ViewPerm = constants:view_channel_permission(), State = #{ id => GuildId, data => #{ <<"roles">> => [ #{ <<"id">> => integer_to_binary(GuildId), <<"permissions">> => integer_to_binary(ViewPerm) } ] } }, Channels = [ #{ <<"id">> => <<"10">>, <<"type">> => 4, <<"position">> => 1, <<"permission_overwrites">> => [] }, #{ <<"id">> => <<"20">>, <<"type">> => 4, <<"position">> => 2, <<"permission_overwrites">> => [] }, #{ <<"id">> => <<"21">>, <<"type">> => 0, <<"parent_id">> => <<"20">>, <<"position">> => 3, <<"permission_overwrites">> => [] }, #{ <<"id">> => <<"11">>, <<"type">> => 0, <<"parent_id">> => <<"10">>, <<"position">> => 50, <<"permission_overwrites">> => [] } ], ChannelId = guild_data:find_everyone_viewable_text_channel(Channels, State), ?assertEqual(11, ChannelId). find_everyone_viewable_text_channel_prefers_root_before_categories_test() -> GuildId = 100, ViewPerm = constants:view_channel_permission(), State = #{ id => GuildId, data => #{ <<"roles">> => [ #{ <<"id">> => integer_to_binary(GuildId), <<"permissions">> => integer_to_binary(ViewPerm) } ] } }, Channels = [ #{ <<"id">> => <<"10">>, <<"type">> => 4, <<"position">> => 1, <<"permission_overwrites">> => [] }, #{ <<"id">> => <<"11">>, <<"type">> => 0, <<"parent_id">> => <<"10">>, <<"position">> => 2, <<"permission_overwrites">> => [] }, #{ <<"id">> => <<"9">>, <<"type">> => 0, <<"position">> => 50, <<"permission_overwrites">> => [] } ], ChannelId = guild_data:find_everyone_viewable_text_channel(Channels, State), ?assertEqual(9, ChannelId). find_everyone_viewable_text_channel_skips_invalid_channel_id_test() -> GuildId = 100, ViewPerm = constants:view_channel_permission(), State = #{ id => GuildId, data => #{ <<"roles">> => [ #{ <<"id">> => integer_to_binary(GuildId), <<"permissions">> => integer_to_binary(ViewPerm) } ] } }, Channels = [ #{ <<"id">> => <<"001">>, <<"type">> => 0, <<"position">> => 1, <<"permission_overwrites">> => [] }, #{ <<"id">> => <<"12">>, <<"type">> => 0, <<"position">> => 2, <<"permission_overwrites">> => [] } ], ChannelId = guild_data:find_everyone_viewable_text_channel(Channels, State), ?assertEqual(12, ChannelId). find_everyone_viewable_text_channel_ignores_user_overwrite_for_guild_id_test() -> GuildId = 100, ViewPerm = constants:view_channel_permission(), State = #{ id => GuildId, data => #{ <<"roles">> => [ #{ <<"id">> => integer_to_binary(GuildId), <<"permissions">> => integer_to_binary(ViewPerm) } ] } }, Channels = [ #{ <<"id">> => <<"12">>, <<"type">> => 0, <<"permission_overwrites">> => [ #{ <<"id">> => integer_to_binary(GuildId), <<"type">> => 1, <<"allow">> => <<"0">>, <<"deny">> => integer_to_binary(ViewPerm) } ] } ], ChannelId = guild_data:find_everyone_viewable_text_channel(Channels, State), ?assertEqual(12, ChannelId). voice_members_from_states_reads_embedded_member_test() -> EmbeddedMember = #{<<"user">> => #{<<"id">> => <<"300">>}, <<"roles">> => []}, IndexedMember = #{<<"user">> => #{<<"id">> => <<"200">>}, <<"roles">> => []}, VoiceStates = [ #{<<"user_id">> => <<"300">>, <<"member">> => EmbeddedMember}, #{<<"user_id">> => <<"200">>} ], VoiceMembers = guild_data_channels:voice_members_from_states(VoiceStates, [IndexedMember]), ?assertEqual([EmbeddedMember, IndexedMember], VoiceMembers). paginate_members_test() -> Members = [#{<<"id">> => 1}, #{<<"id">> => 2}, #{<<"id">> => 3}], ?assertEqual( [#{<<"id">> => 1}, #{<<"id">> => 2}], guild_data_members:paginate_members(Members, 2, 0) ), ?assertEqual( [#{<<"id">> => 2}, #{<<"id">> => 3}], guild_data_members:paginate_members(Members, 2, 1) ), ?assertEqual( [#{<<"id">> => 3}], guild_data_members:paginate_members(Members, 2, 2) ), ?assertEqual( [], guild_data_members:paginate_members(Members, 2, 5) ). search_guild_members_limits_prefix_matches_without_paginating_all_test() -> State = #{ data => #{ <<"members">> => [ member(1, <<"Alice">>), member(2, <<"Alicia">>), member(3, <<"Bob">>) ] } }, {reply, Reply, _State} = guild_data:search_guild_members( #{query => <<"ali">>, limit => 1}, State ), Members = maps:get(members, Reply), ?assertEqual(1, length(Members)), ?assertEqual(3, maps:get(total, Reply)), ?assert( lists:all( fun(Member) -> guild_request_members_search:member_matches_normalized_query(Member, <<"ali">>) end, Members ) ). get_guild_state_includes_parent_category_when_child_channel_is_visible_test() -> GuildId = 50, UserId = 300, RoleId = 77, CategoryId = 600, ChannelId = 601, ViewPerm = constants:view_channel_permission(), State = parent_category_visible_state( GuildId, UserId, RoleId, CategoryId, ChannelId, ViewPerm ), GuildState = guild_data:get_guild_state(UserId, State), Channels = maps:get(<<"channels">>, GuildState), ChannelIds = guild_state_channel_ids(Channels), ?assertEqual([CategoryId, ChannelId], ChannelIds). parent_category_visible_state(GuildId, UserId, RoleId, CategoryId, ChannelId, ViewPerm) -> #{ id => GuildId, member_presence => ets:new(test_member_presence, [set, public]), data => #{ <<"guild">> => #{<<"name">> => <<"Test Guild">>}, <<"roles">> => [ #{<<"id">> => integer_to_binary(GuildId), <<"permissions">> => <<"0">>}, #{ <<"id">> => integer_to_binary(RoleId), <<"permissions">> => integer_to_binary(ViewPerm) } ], <<"channels">> => [ parent_category_channel(CategoryId, RoleId, ViewPerm), child_visible_channel(ChannelId, CategoryId, RoleId, ViewPerm) ], <<"members">> => [ #{ <<"user">> => #{<<"id">> => integer_to_binary(UserId)}, <<"roles">> => [integer_to_binary(RoleId)], <<"joined_at">> => <<"2024-01-01T00:00:00Z">> } ], <<"emojis">> => [], <<"stickers">> => [] } }. parent_category_channel(CategoryId, RoleId, ViewPerm) -> #{ <<"id">> => integer_to_binary(CategoryId), <<"type">> => 4, <<"permission_overwrites">> => [ role_overwrite(RoleId, <<"0">>, integer_to_binary(ViewPerm)) ] }. child_visible_channel(ChannelId, CategoryId, RoleId, ViewPerm) -> #{ <<"id">> => integer_to_binary(ChannelId), <<"type">> => 0, <<"parent_id">> => integer_to_binary(CategoryId), <<"permission_overwrites">> => [ role_overwrite(RoleId, integer_to_binary(ViewPerm), <<"0">>) ] }. role_overwrite(RoleId, Allow, Deny) -> #{ <<"id">> => integer_to_binary(RoleId), <<"type">> => 0, <<"allow">> => Allow, <<"deny">> => Deny }. guild_state_channel_ids(Channels) -> lists:sort([snowflake_id:parse(maps:get(<<"id">>, C)) || C <- Channels]). member(UserId, Username) -> #{ <<"user">> => #{ <<"id">> => integer_to_binary(UserId), <<"username">> => Username }, <<"roles">> => [] }. test_state() -> GuildId = 100, ViewPerm = constants:view_channel_permission(), #{ id => GuildId, member_presence => ets:new(test_member_presence, [set, public]), data => #{ <<"guild">> => #{<<"name">> => <<"Fluxer">>}, <<"roles">> => [ #{ <<"id">> => integer_to_binary(GuildId), <<"permissions">> => integer_to_binary(ViewPerm) } ], <<"channels">> => [ #{<<"id">> => <<"500">>, <<"type">> => 0, <<"permission_overwrites">> => []}, #{<<"id">> => <<"501">>, <<"type">> => 2, <<"permission_overwrites">> => []} ], <<"members">> => [ #{ <<"user">> => #{<<"id">> => <<"200">>}, <<"roles">> => [integer_to_binary(GuildId)], <<"joined_at">> => <<"2024-01-01T00:00:00Z">> } ], <<"emojis">> => [], <<"stickers">> => [] } }.