mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
269 lines
9.5 KiB
Erlang
269 lines
9.5 KiB
Erlang
%% SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
-module(guild_voice_access_revoke_tests).
|
|
-typing([eqwalizer]).
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
-define(GUILD, 42).
|
|
-define(USER, 10).
|
|
-define(CHANNEL, 500).
|
|
-define(ROLE, 999).
|
|
|
|
overwrite_deny_view_connect_disconnects_connected_user_test() ->
|
|
State = guild_state:update_state(channel_update, deny_overwrite_update(), base_state(true)),
|
|
Messages = collect_sync_messages(),
|
|
?assert(disconnected(Messages)),
|
|
?assertNot(granted_permissions(Messages)),
|
|
?assert(guild_virtual_channel_access:has_virtual_access(?USER, ?CHANNEL, State)),
|
|
?assertNot(guild_virtual_channel_access:has_voice_access(?USER, ?CHANNEL, State)).
|
|
|
|
overwrite_deny_connect_only_disconnects_connected_user_test() ->
|
|
Update = channel_update_with_user_deny(constants:connect_permission()),
|
|
_ = guild_state:update_state(channel_update, Update, base_state(true)),
|
|
?assert(disconnected(collect_sync_messages())).
|
|
|
|
overwrite_deny_view_connect_disconnects_user_without_session_test() ->
|
|
_ = guild_state:update_state(channel_update, deny_overwrite_update(), base_state(false)),
|
|
?assert(disconnected(collect_sync_messages())).
|
|
|
|
role_removal_disconnects_connected_user_test() ->
|
|
Update = #{<<"user">> => #{<<"id">> => integer_to_binary(?USER)}, <<"roles">> => []},
|
|
_ = guild_state:update_state(guild_member_update, Update, base_state(true)),
|
|
Messages = collect_sync_messages(),
|
|
?assert(disconnected(Messages)),
|
|
?assertNot(granted_permissions(Messages)).
|
|
|
|
moderator_granted_access_survives_revoke_test() ->
|
|
State0 = guild_virtual_channel_access:add_virtual_access(?USER, ?CHANNEL, base_state(true)),
|
|
State = guild_state:update_state(channel_update, deny_overwrite_update(), State0),
|
|
Messages = collect_sync_messages(),
|
|
?assertNot(disconnected(Messages)),
|
|
?assert(guild_virtual_channel_access:has_voice_access(?USER, ?CHANNEL, State)).
|
|
|
|
view_only_access_grants_no_publish_rights_test() ->
|
|
ViewConnect = constants:view_channel_permission() bor constants:connect_permission(),
|
|
State0 = with_role_permissions(ViewConnect, base_state(true)),
|
|
State1 = guild_virtual_channel_access:add_view_only_access(?USER, ?CHANNEL, State0),
|
|
State = with_overwrites([user_deny(ViewConnect)], State1),
|
|
?assertEqual(
|
|
#{can_speak => false, can_stream => false, can_video => false},
|
|
voice_utils:compute_voice_permissions(?USER, ?CHANNEL, State)
|
|
).
|
|
|
|
explicit_access_replaces_view_only_mark_test() ->
|
|
State0 = guild_virtual_channel_access:add_view_only_access(?USER, ?CHANNEL, #{}),
|
|
?assert(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL, State0)),
|
|
State1 = guild_virtual_channel_access:add_virtual_access(?USER, ?CHANNEL, State0),
|
|
?assertNot(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL, State1)),
|
|
?assert(guild_virtual_channel_access:has_voice_access(?USER, ?CHANNEL, State1)).
|
|
|
|
removing_access_clears_view_only_mark_test() ->
|
|
State0 = guild_virtual_channel_access:add_view_only_access(?USER, ?CHANNEL, #{}),
|
|
State1 = guild_virtual_channel_access:add_view_only_access(?USER, ?CHANNEL + 1, State0),
|
|
State2 = guild_virtual_channel_access:remove_virtual_access(?USER, ?CHANNEL, State1),
|
|
?assertNot(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL, State2)),
|
|
?assert(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL + 1, State2)),
|
|
State3 = guild_virtual_channel_access:remove_virtual_access(?USER, ?CHANNEL + 1, State2),
|
|
?assertNot(guild_virtual_channel_access:is_view_only(?USER, ?CHANNEL + 1, State3)).
|
|
|
|
channel_delete_disconnects_everyone_in_channel_test() ->
|
|
Self = self(),
|
|
VoicePid = spawn(fun() ->
|
|
receive
|
|
Message -> Self ! {voice_server_got, Message}
|
|
end
|
|
end),
|
|
State0 = (base_state(true))#{voice_server_pid => VoicePid},
|
|
_ = guild_state:update_state(
|
|
channel_delete,
|
|
#{<<"id">> => integer_to_binary(?CHANNEL), <<"type">> => 2},
|
|
State0
|
|
),
|
|
receive
|
|
{voice_server_got, {'$gen_cast', {disconnect_all_voice_users_in_channel, Request}}} ->
|
|
?assertEqual(#{channel_id => ?CHANNEL}, Request)
|
|
after 500 ->
|
|
exit(VoicePid, kill),
|
|
?assert(false)
|
|
end.
|
|
|
|
voice_server_disconnects_all_users_in_channel_test() ->
|
|
Self = self(),
|
|
ForceFun = fun(GId, ChId, UId, ConnId) ->
|
|
Self ! {force_disconnect, GId, ChId, UId, ConnId},
|
|
{ok, #{success => true}}
|
|
end,
|
|
GuildPid = spawn(fun() -> guild_state_reply_loop(ForceFun) end),
|
|
State = #{
|
|
guild_id => ?GUILD,
|
|
guild_pid => GuildPid,
|
|
voice_states => #{
|
|
<<"conn-a">> => voice_state(<<"conn-a">>, 5, ?CHANNEL),
|
|
<<"conn-b">> => voice_state(<<"conn-b">>, 6, ?CHANNEL),
|
|
<<"conn-c">> => voice_state(<<"conn-c">>, 7, ?CHANNEL + 1)
|
|
},
|
|
pending_voice_connections => #{},
|
|
recently_disconnected_voice_states => #{},
|
|
e2ee_room_keys => #{}
|
|
},
|
|
try
|
|
{noreply, NewState} = guild_voice_server:handle_cast(
|
|
{disconnect_all_voice_users_in_channel, #{channel_id => ?CHANNEL}}, State
|
|
),
|
|
?assertEqual([<<"conn-c">>], maps:keys(maps:get(voice_states, NewState))),
|
|
Disconnected = lists:sort([receive_force_disconnect(), receive_force_disconnect()]),
|
|
?assertEqual(
|
|
[{?GUILD, ?CHANNEL, 5, <<"conn-a">>}, {?GUILD, ?CHANNEL, 6, <<"conn-b">>}],
|
|
Disconnected
|
|
)
|
|
after
|
|
exit(GuildPid, kill)
|
|
end.
|
|
|
|
receive_force_disconnect() ->
|
|
receive
|
|
{force_disconnect, GId, ChId, UId, ConnId} -> {GId, ChId, UId, ConnId}
|
|
after 500 -> erlang:error(missing_force_disconnect)
|
|
end.
|
|
|
|
guild_state_reply_loop(ForceFun) ->
|
|
GuildState = #{
|
|
id => ?GUILD,
|
|
data => #{<<"guild">> => #{<<"owner_id">> => <<"1">>}},
|
|
sessions => #{},
|
|
test_force_disconnect_fun => ForceFun
|
|
},
|
|
receive
|
|
{'$gen_call', From, {get_voice_guild_state}} ->
|
|
gen_server:reply(From, GuildState),
|
|
guild_state_reply_loop(ForceFun);
|
|
_ ->
|
|
guild_state_reply_loop(ForceFun)
|
|
end.
|
|
|
|
voice_state(ConnId, UserId, ChannelId) ->
|
|
#{
|
|
<<"guild_id">> => integer_to_binary(?GUILD),
|
|
<<"user_id">> => integer_to_binary(UserId),
|
|
<<"channel_id">> => integer_to_binary(ChannelId),
|
|
<<"connection_id">> => ConnId
|
|
}.
|
|
|
|
base_state(WithSession) ->
|
|
ok = drain(),
|
|
Self = self(),
|
|
SyncFun = fun(GId, ChId, UId, ConnId, Perms) ->
|
|
Self ! {synced, GId, ChId, UId, ConnId, Perms}
|
|
end,
|
|
Sessions =
|
|
case WithSession of
|
|
true ->
|
|
#{
|
|
<<"sess">> => #{
|
|
session_id => <<"sess">>,
|
|
user_id => ?USER,
|
|
pid => spawn(fun sink/0),
|
|
viewable_channels => #{?CHANNEL => true}
|
|
}
|
|
};
|
|
false ->
|
|
#{}
|
|
end,
|
|
RolePerms =
|
|
constants:view_channel_permission() bor constants:connect_permission() bor
|
|
constants:speak_permission(),
|
|
#{
|
|
id => ?GUILD,
|
|
sessions => Sessions,
|
|
voice_states => #{
|
|
<<"conn">> => (voice_state(<<"conn">>, ?USER, ?CHANNEL))#{<<"deaf">> => false}
|
|
},
|
|
member_list_subscriptions => guild_member_list_subs:new(),
|
|
test_permission_sync_fun => SyncFun,
|
|
data => #{
|
|
<<"guild">> => #{<<"owner_id">> => <<"1">>},
|
|
<<"roles">> => roles(RolePerms),
|
|
<<"members">> => [
|
|
#{
|
|
<<"user">> => #{<<"id">> => integer_to_binary(?USER)},
|
|
<<"roles">> => [integer_to_binary(?ROLE)]
|
|
}
|
|
],
|
|
<<"channels">> => [voice_channel([])]
|
|
}
|
|
}.
|
|
|
|
voice_channel(Overwrites) ->
|
|
#{
|
|
<<"id">> => integer_to_binary(?CHANNEL),
|
|
<<"type">> => 2,
|
|
<<"permission_overwrites">> => Overwrites
|
|
}.
|
|
|
|
with_overwrites(Overwrites, State) ->
|
|
Data = maps:get(data, State),
|
|
State#{data => Data#{<<"channels">> => [voice_channel(Overwrites)]}}.
|
|
|
|
with_role_permissions(Permissions, State) ->
|
|
Data = maps:get(data, State),
|
|
State#{data => Data#{<<"roles">> => roles(Permissions)}}.
|
|
|
|
roles(Permissions) ->
|
|
[
|
|
#{
|
|
<<"id">> => integer_to_binary(?ROLE),
|
|
<<"permissions">> => integer_to_binary(Permissions)
|
|
},
|
|
#{<<"id">> => integer_to_binary(?GUILD), <<"permissions">> => <<"0">>}
|
|
].
|
|
|
|
user_deny(Deny) ->
|
|
#{
|
|
<<"id">> => integer_to_binary(?USER),
|
|
<<"type">> => 1,
|
|
<<"allow">> => <<"0">>,
|
|
<<"deny">> => integer_to_binary(Deny)
|
|
}.
|
|
|
|
channel_update_with_user_deny(Deny) ->
|
|
voice_channel([user_deny(Deny)]).
|
|
|
|
deny_overwrite_update() ->
|
|
channel_update_with_user_deny(
|
|
constants:view_channel_permission() bor constants:connect_permission()
|
|
).
|
|
|
|
sink() ->
|
|
receive
|
|
stop -> ok;
|
|
_ -> sink()
|
|
end.
|
|
|
|
drain() ->
|
|
receive
|
|
_ -> drain()
|
|
after 0 -> ok
|
|
end.
|
|
|
|
collect_sync_messages() ->
|
|
collect_sync_messages([]).
|
|
|
|
collect_sync_messages(Acc) ->
|
|
receive
|
|
{synced, _, _, _, _, _} = Message -> collect_sync_messages([Message | Acc])
|
|
after 300 -> lists:reverse(Acc)
|
|
end.
|
|
|
|
disconnected(Messages) ->
|
|
lists:any(
|
|
fun({synced, _, _, _, _, Perms}) -> maps:get(disconnected, Perms, false) =:= true end,
|
|
Messages
|
|
).
|
|
|
|
granted_permissions(Messages) ->
|
|
lists:any(
|
|
fun({synced, _, _, _, _, Perms}) -> maps:is_key(can_speak, Perms) end,
|
|
Messages
|
|
).
|