mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(gateway): resync permissions and validate voice leaves (#2492)
This commit is contained in:
@@ -96,6 +96,7 @@ handle_post_update(guild_member_update, EventData, OldState, NewState) ->
|
||||
guild_state_member:sync_member(EventData, NewState),
|
||||
UserId = guild_state_member:extract_user_id(EventData),
|
||||
State1 = dispatch_member_update_visibility(UserId, OldState, NewState),
|
||||
ok = guild_voice_permission_sync:maybe_sync_permissions_on_member_update(EventData, State1),
|
||||
refresh_member_session_cache(UserId, State1);
|
||||
handle_post_update(Event, EventData, OldState, NewState) when
|
||||
Event =:= guild_role_create;
|
||||
@@ -122,15 +123,25 @@ post_update_role(guild_role_create, _EventData, _OldState, NewState) ->
|
||||
post_update_role(guild_role_update, EventData, OldState, NewState) ->
|
||||
NewState1 = guild_state_roles:sync_hoisted_roles(NewState),
|
||||
RoleIds = guild_state_roles:extract_role_ids_from_role_update(EventData),
|
||||
guild_state_roles:recompute_visibility_for_roles(RoleIds, OldState, NewState1);
|
||||
resync_roles_after_permission_change(RoleIds, OldState, NewState1);
|
||||
post_update_role(guild_role_update_bulk, EventData, OldState, NewState) ->
|
||||
NewState1 = guild_state_roles:sync_hoisted_roles(NewState),
|
||||
RoleIds = guild_state_roles:extract_role_ids_from_role_update_bulk(EventData),
|
||||
guild_state_roles:recompute_visibility_for_roles(RoleIds, OldState, NewState1);
|
||||
resync_roles_after_permission_change(RoleIds, OldState, NewState1);
|
||||
post_update_role(guild_role_delete, EventData, OldState, NewState) ->
|
||||
NewState1 = guild_state_roles:sync_hoisted_roles(NewState),
|
||||
RoleIds = guild_state_roles:extract_role_ids_from_role_delete(EventData),
|
||||
guild_state_roles:recompute_visibility_for_roles(RoleIds, OldState, NewState1).
|
||||
resync_roles_after_permission_change(RoleIds, OldState, NewState1).
|
||||
|
||||
-spec resync_roles_after_permission_change([integer()], guild_state(), guild_state()) ->
|
||||
guild_state().
|
||||
resync_roles_after_permission_change(RoleIds, OldState, NewState) ->
|
||||
Recomputed = guild_state_roles:recompute_visibility_for_roles(RoleIds, OldState, NewState),
|
||||
lists:foreach(
|
||||
fun(RoleId) -> guild_voice_permission_sync:sync_users_with_role(RoleId, Recomputed) end,
|
||||
RoleIds
|
||||
),
|
||||
Recomputed.
|
||||
|
||||
-spec post_update_channel(event(), event_data(), guild_state(), guild_state()) -> guild_state().
|
||||
post_update_channel(channel_create, _EventData, _OldState, NewState) ->
|
||||
@@ -138,24 +149,31 @@ post_update_channel(channel_create, _EventData, _OldState, NewState) ->
|
||||
NewState;
|
||||
post_update_channel(channel_update, EventData, OldState, NewState) ->
|
||||
ChanIds = guild_state_channels:extract_channel_ids_from_channel_update(EventData),
|
||||
NewState1 = guild_member_list_write:rebuild_channels_for_permission_change(
|
||||
ChanIds, NewState
|
||||
),
|
||||
guild_visibility:compute_and_dispatch_visibility_changes_for_channels(
|
||||
ChanIds, OldState, NewState1
|
||||
);
|
||||
resync_channels_after_permission_change(ChanIds, OldState, NewState);
|
||||
post_update_channel(channel_update_bulk, EventData, OldState, NewState) ->
|
||||
ChanIds = guild_state_channels:extract_channel_ids_from_channel_update_bulk(EventData),
|
||||
NewState1 = guild_member_list_write:rebuild_channels_for_permission_change(
|
||||
ChanIds, NewState
|
||||
),
|
||||
guild_visibility:compute_and_dispatch_visibility_changes_for_channels(
|
||||
ChanIds, OldState, NewState1
|
||||
);
|
||||
resync_channels_after_permission_change(ChanIds, OldState, NewState);
|
||||
post_update_channel(channel_delete, _EventData, _OldState, NewState) ->
|
||||
maybe_sync_member_list_permission_state(NewState),
|
||||
NewState.
|
||||
|
||||
-spec resync_channels_after_permission_change([integer()], guild_state(), guild_state()) ->
|
||||
guild_state().
|
||||
resync_channels_after_permission_change(ChanIds, OldState, NewState) ->
|
||||
Rebuilt = guild_member_list_write:rebuild_channels_for_permission_change(ChanIds, NewState),
|
||||
Dispatched = guild_visibility:compute_and_dispatch_visibility_changes_for_channels(
|
||||
ChanIds, OldState, Rebuilt
|
||||
),
|
||||
lists:foreach(
|
||||
fun(ChanId) ->
|
||||
guild_voice_permission_sync:sync_all_voice_permissions_for_channel(
|
||||
ChanId, Dispatched
|
||||
)
|
||||
end,
|
||||
ChanIds
|
||||
),
|
||||
Dispatched.
|
||||
|
||||
-spec post_update_member_remove(event_data(), guild_state()) -> guild_state().
|
||||
post_update_member_remove(EventData, NewState) ->
|
||||
UserId = guild_state_member:extract_user_id(EventData),
|
||||
|
||||
@@ -104,7 +104,7 @@ handle_member_voice(Context, Member, VoiceStates, State) ->
|
||||
voice_reply().
|
||||
handle_disconnect(Context, VoiceStates, State) ->
|
||||
guild_voice_disconnect:handle_voice_disconnect(
|
||||
maps:get(raw_connection_id, Context),
|
||||
maps:get(connection_id, Context),
|
||||
maps:get(session_id, Context),
|
||||
maps:get(user_id, Context),
|
||||
VoiceStates,
|
||||
|
||||
@@ -43,7 +43,6 @@ build_context(Request0) ->
|
||||
Request = map_utils:ensure_map(Request0),
|
||||
Norm = fun guild_voice_connection_normalize:normalize_boolean/1,
|
||||
Coord = fun guild_voice_connection_normalize:normalize_coordinate/1,
|
||||
RawConnectionId = maps:get(connection_id, Request, undefined),
|
||||
#{
|
||||
user_id => guild_voice_connection_normalize:normalize_user_id(
|
||||
maps:get(user_id, Request, undefined)
|
||||
@@ -53,9 +52,8 @@ build_context(Request0) ->
|
||||
),
|
||||
session_id => maps:get(session_id, Request, undefined),
|
||||
connection_id => guild_voice_connection_normalize:normalize_connection_id(
|
||||
RawConnectionId
|
||||
maps:get(connection_id, Request, undefined)
|
||||
),
|
||||
raw_connection_id => RawConnectionId,
|
||||
self_mute => Norm(maps:get(self_mute, Request, false)),
|
||||
self_deaf => Norm(maps:get(self_deaf, Request, false)),
|
||||
self_video => Norm(maps:get(self_video, Request, false)),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
-export([
|
||||
sync_user_voice_permissions/2,
|
||||
sync_all_voice_permissions_for_channel/2,
|
||||
sync_users_with_role/2,
|
||||
maybe_sync_permissions_on_role_update/2,
|
||||
maybe_sync_permissions_on_member_update/2
|
||||
]).
|
||||
@@ -129,6 +130,7 @@ sync_validated_voice_state(GuildId, UserId, ChId, ConnId, VoiceState, State) ->
|
||||
VoicePermissions = VoicePermissions0#{
|
||||
deaf => maps:get(<<"deaf">>, VoiceState, false)
|
||||
},
|
||||
ok = maybe_clear_self_stream(ChId, VoiceState, VoicePermissions, State),
|
||||
dispatch_permission_update(
|
||||
GuildId, ChId, UserId, ConnId, VoicePermissions, State
|
||||
);
|
||||
@@ -136,6 +138,27 @@ sync_validated_voice_state(GuildId, UserId, ChId, ConnId, VoiceState, State) ->
|
||||
dispatch_force_disconnect(GuildId, ChId, UserId, ConnId, State)
|
||||
end.
|
||||
|
||||
-spec maybe_clear_self_stream(
|
||||
channel_id(), voice_state(), voice_utils:voice_permissions(), guild_state()
|
||||
) -> ok.
|
||||
maybe_clear_self_stream(ChId, VoiceState, #{can_stream := false}, State) ->
|
||||
SelfStream = maps:get(<<"self_stream">>, VoiceState, false),
|
||||
SelfVideo = maps:get(<<"self_video">>, VoiceState, false),
|
||||
case SelfStream =:= true orelse SelfVideo =:= true of
|
||||
false ->
|
||||
ok;
|
||||
true ->
|
||||
Cleared = VoiceState#{
|
||||
<<"self_stream">> => false,
|
||||
<<"self_video">> => false
|
||||
},
|
||||
guild_voice_broadcast:broadcast_voice_state_update(
|
||||
Cleared, State, integer_to_binary(ChId)
|
||||
)
|
||||
end;
|
||||
maybe_clear_self_stream(_ChId, _VoiceState, _VoicePermissions, _State) ->
|
||||
ok.
|
||||
|
||||
-spec user_has_base_voice_access(user_id(), channel_id(), guild_state()) -> boolean().
|
||||
user_has_base_voice_access(UserId, ChannelId, State) ->
|
||||
case guild_virtual_channel_access:has_virtual_access(UserId, ChannelId, State) of
|
||||
@@ -319,6 +342,7 @@ build_sync_test_data(GuildId, RoleId, UserId, ChannelId, Permissions) ->
|
||||
}.
|
||||
|
||||
sync_user_voice_permissions_syncs_connected_user_test() ->
|
||||
ok = drain_mailbox(),
|
||||
TestFun = make_sync_test_fun(),
|
||||
State = build_sync_test_state(TestFun),
|
||||
ok = sync_user_voice_permissions(10, State),
|
||||
@@ -393,6 +417,7 @@ maybe_sync_permissions_on_role_update_uses_role_index_test() ->
|
||||
end.
|
||||
|
||||
sync_disconnects_when_connect_permission_lost_test() ->
|
||||
ok = drain_mailbox(),
|
||||
TestFun = make_sync_test_fun(),
|
||||
UserId = 10,
|
||||
ChannelId = 500,
|
||||
@@ -410,6 +435,80 @@ sync_disconnects_when_connect_permission_lost_test() ->
|
||||
?assert(false)
|
||||
end.
|
||||
|
||||
sync_clears_self_stream_when_stream_permission_lost_test() ->
|
||||
ok = drain_mailbox(),
|
||||
TestFun = make_sync_test_fun(),
|
||||
UserId = 10,
|
||||
ChannelId = 500,
|
||||
GuildId = 42,
|
||||
RoleId = 999,
|
||||
WithoutStream =
|
||||
constants:view_channel_permission() bor
|
||||
constants:connect_permission() bor
|
||||
constants:speak_permission(),
|
||||
State = build_streaming_sync_test_state(
|
||||
TestFun, GuildId, RoleId, UserId, ChannelId, WithoutStream
|
||||
),
|
||||
ok = sync_user_voice_permissions(UserId, State),
|
||||
Broadcast = receive_voice_state_dispatch(),
|
||||
?assertEqual(false, maps:get(<<"self_stream">>, Broadcast)),
|
||||
?assertEqual(false, maps:get(<<"self_video">>, Broadcast)),
|
||||
?assertEqual(<<"test-conn">>, maps:get(<<"connection_id">>, Broadcast)).
|
||||
|
||||
sync_keeps_self_stream_when_stream_permission_held_test() ->
|
||||
ok = drain_mailbox(),
|
||||
TestFun = make_sync_test_fun(),
|
||||
UserId = 10,
|
||||
ChannelId = 500,
|
||||
GuildId = 42,
|
||||
RoleId = 999,
|
||||
WithStream =
|
||||
constants:view_channel_permission() bor
|
||||
constants:connect_permission() bor
|
||||
constants:speak_permission() bor
|
||||
constants:stream_permission(),
|
||||
State = build_streaming_sync_test_state(
|
||||
TestFun, GuildId, RoleId, UserId, ChannelId, WithStream
|
||||
),
|
||||
ok = sync_user_voice_permissions(UserId, State),
|
||||
?assertEqual(undefined, receive_optional_voice_state_dispatch()).
|
||||
|
||||
build_streaming_sync_test_state(TestFun, GuildId, RoleId, UserId, ChannelId, Permissions) ->
|
||||
VoiceState = #{
|
||||
<<"user_id">> => integer_to_binary(UserId),
|
||||
<<"channel_id">> => integer_to_binary(ChannelId),
|
||||
<<"connection_id">> => <<"test-conn">>,
|
||||
<<"self_stream">> => true,
|
||||
<<"self_video">> => true
|
||||
},
|
||||
#{
|
||||
id => GuildId,
|
||||
voice_states => #{<<"conn">> => VoiceState},
|
||||
sessions => #{
|
||||
<<"s1">> => #{
|
||||
pid => self(),
|
||||
user_id => UserId,
|
||||
viewable_channels => #{ChannelId => true}
|
||||
}
|
||||
},
|
||||
test_permission_sync_fun => TestFun,
|
||||
data => build_sync_test_data(GuildId, RoleId, UserId, ChannelId, Permissions)
|
||||
}.
|
||||
|
||||
receive_voice_state_dispatch() ->
|
||||
case receive_optional_voice_state_dispatch() of
|
||||
undefined -> error(voice_state_update_not_received);
|
||||
Payload -> Payload
|
||||
end.
|
||||
|
||||
receive_optional_voice_state_dispatch() ->
|
||||
receive
|
||||
{'$gen_cast', {dispatch, voice_state_update, {pre_encoded, Bin}}} -> json:decode(Bin);
|
||||
{'$gen_cast', {dispatch, voice_state_update, Payload}} -> Payload
|
||||
after 200 ->
|
||||
undefined
|
||||
end.
|
||||
|
||||
make_sync_test_fun() ->
|
||||
Self = self(),
|
||||
fun(GId, ChId, UId, ConnId, Perms) ->
|
||||
@@ -461,4 +560,11 @@ role_sync_test_data(
|
||||
]
|
||||
}).
|
||||
|
||||
drain_mailbox() ->
|
||||
receive
|
||||
_ -> drain_mailbox()
|
||||
after 0 ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-endif.
|
||||
|
||||
@@ -40,7 +40,21 @@ apply_voice_permissions_to_flags(Flags, VoicePermissions) ->
|
||||
false -> true;
|
||||
_ -> false
|
||||
end,
|
||||
Flags#{suppress => Suppress}.
|
||||
SelfStream =
|
||||
case maps:get(can_stream, VoicePermissions, true) of
|
||||
false -> false;
|
||||
_ -> maps:get(self_stream, Flags, false) =:= true
|
||||
end,
|
||||
SelfVideo =
|
||||
case maps:get(can_video, VoicePermissions, true) of
|
||||
false -> false;
|
||||
_ -> maps:get(self_video, Flags, false) =:= true
|
||||
end,
|
||||
Flags#{
|
||||
suppress => Suppress,
|
||||
self_stream => SelfStream,
|
||||
self_video => SelfVideo
|
||||
}.
|
||||
|
||||
-spec build_voice_token_rpc_request(
|
||||
integer() | null,
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
%% SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
-module(guild_state_channel_resync_tests).
|
||||
-typing([eqwalizer]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-define(GUILD_ID, 9100).
|
||||
-define(CHANNEL_ID, 9500).
|
||||
-define(ROLE_ID, 9200).
|
||||
-define(USER_A, 9010).
|
||||
-define(USER_B, 9020).
|
||||
-define(USER_C, 9030).
|
||||
|
||||
channel_update_syncs_every_subscriber_after_permission_change_test() ->
|
||||
with_relay_mock(fun run_channel_update_syncs_every_subscriber/0).
|
||||
|
||||
channel_update_bulk_syncs_every_subscriber_after_permission_change_test() ->
|
||||
with_relay_mock(fun run_channel_update_bulk_syncs_every_subscriber/0).
|
||||
|
||||
run_channel_update_syncs_every_subscriber() ->
|
||||
run_resync_case(channel_update, denied_channel()).
|
||||
|
||||
run_channel_update_bulk_syncs_every_subscriber() ->
|
||||
run_resync_case(channel_update_bulk, #{<<"channels">> => [denied_channel()]}).
|
||||
|
||||
run_resync_case(Event, EventData) ->
|
||||
PidA = spawn(fun idle/0),
|
||||
PidB = spawn(fun idle/0),
|
||||
SubsTab = guild_member_list_subs:new(),
|
||||
State0 = base_state(SubsTab, PidA, PidB),
|
||||
{State1, _SyncA, _RangesA} = guild_member_list:subscribe_ranges(
|
||||
<<"s_a">>, list_id(), [{0, 99}], State0
|
||||
),
|
||||
{State2, _SyncB, _RangesB} = guild_member_list:subscribe_ranges(
|
||||
<<"s_b">>, list_id(), [{0, 99}], State1
|
||||
),
|
||||
try
|
||||
?assertEqual({3, 0}, guild_member_list:get_counts(list_id(), State2)),
|
||||
{noreply, UpdatedState} = guild_dispatch:handle_dispatch(Event, EventData, State2),
|
||||
?assertEqual({2, 0}, guild_member_list:get_counts(list_id(), UpdatedState)),
|
||||
{Pids, Raw, Payload} = receive_member_list_dispatch(),
|
||||
?assertEqual(lists:sort([PidA, PidB]), lists:sort(Pids)),
|
||||
?assertEqual(list_id(), maps:get(<<"channel_id">>, Payload)),
|
||||
?assertEqual(list_id(), maps:get(<<"id">>, Payload)),
|
||||
?assertEqual(2, maps:get(<<"member_count">>, Payload)),
|
||||
?assertEqual(nomatch, binary:match(Raw, integer_to_binary(?USER_C))),
|
||||
assert_no_further_dispatch(),
|
||||
guild_member_list_channel_engine:destroy_all(UpdatedState)
|
||||
after
|
||||
PidA ! stop,
|
||||
PidB ! stop,
|
||||
ets:delete(SubsTab)
|
||||
end.
|
||||
|
||||
base_state(SubsTab, PidA, PidB) ->
|
||||
#{
|
||||
id => ?GUILD_ID,
|
||||
data => #{
|
||||
<<"guild">> => #{<<"owner_id">> => <<"999">>},
|
||||
<<"roles">> => [
|
||||
#{
|
||||
<<"id">> => integer_to_binary(?GUILD_ID),
|
||||
<<"permissions">> => integer_to_binary(viewer_permissions())
|
||||
},
|
||||
#{<<"id">> => integer_to_binary(?ROLE_ID), <<"permissions">> => <<"0">>}
|
||||
],
|
||||
<<"members">> => [
|
||||
member(?USER_A, []),
|
||||
member(?USER_B, []),
|
||||
member(?USER_C, [integer_to_binary(?ROLE_ID)])
|
||||
],
|
||||
<<"channels">> => [visible_channel()]
|
||||
},
|
||||
sessions => #{
|
||||
<<"s_a">> => session(<<"s_a">>, ?USER_A, PidA),
|
||||
<<"s_b">> => session(<<"s_b">>, ?USER_B, PidB)
|
||||
},
|
||||
member_presence => #{},
|
||||
member_list_subscriptions => SubsTab
|
||||
}.
|
||||
|
||||
session(SessionId, UserId, Pid) ->
|
||||
#{
|
||||
session_id => SessionId,
|
||||
user_id => UserId,
|
||||
pid => Pid,
|
||||
viewable_channels => #{?CHANNEL_ID => true}
|
||||
}.
|
||||
|
||||
member(UserId, Roles) ->
|
||||
#{
|
||||
<<"user">> => #{
|
||||
<<"id">> => integer_to_binary(UserId),
|
||||
<<"username">> => <<"u", (integer_to_binary(UserId))/binary>>
|
||||
},
|
||||
<<"roles">> => Roles
|
||||
}.
|
||||
|
||||
visible_channel() ->
|
||||
#{<<"id">> => list_id(), <<"type">> => 0, <<"permission_overwrites">> => []}.
|
||||
|
||||
denied_channel() ->
|
||||
#{
|
||||
<<"id">> => list_id(),
|
||||
<<"type">> => 0,
|
||||
<<"permission_overwrites">> => [
|
||||
#{
|
||||
<<"id">> => integer_to_binary(?ROLE_ID),
|
||||
<<"type">> => 0,
|
||||
<<"allow">> => <<"0">>,
|
||||
<<"deny">> => integer_to_binary(constants:view_channel_permission())
|
||||
}
|
||||
]
|
||||
}.
|
||||
|
||||
viewer_permissions() ->
|
||||
constants:view_channel_permission() bor constants:view_channel_members_permission().
|
||||
|
||||
list_id() ->
|
||||
integer_to_binary(?CHANNEL_ID).
|
||||
|
||||
with_relay_mock(Fun) ->
|
||||
meck:new(gateway_dispatch_relay, [passthrough, no_link]),
|
||||
Parent = self(),
|
||||
meck:expect(
|
||||
gateway_dispatch_relay,
|
||||
dispatch_many,
|
||||
fun
|
||||
(Pids, guild_member_list_update, {pre_encoded, Bin}, GuildId) when is_binary(Bin) ->
|
||||
Parent ! {member_list_dispatch, Pids, guild_member_list_update, GuildId, Bin},
|
||||
ok;
|
||||
(_Pids, _Event, _Payload, _GuildId) ->
|
||||
ok
|
||||
end
|
||||
),
|
||||
try
|
||||
Fun()
|
||||
after
|
||||
meck:unload(gateway_dispatch_relay)
|
||||
end.
|
||||
|
||||
receive_member_list_dispatch() ->
|
||||
receive
|
||||
{member_list_dispatch, Pids, guild_member_list_update, ?GUILD_ID, Bin} ->
|
||||
{Pids, Bin, json:decode(Bin)}
|
||||
after 1000 ->
|
||||
?assert(false, no_member_list_sync_dispatched),
|
||||
{[], <<>>, #{}}
|
||||
end.
|
||||
|
||||
assert_no_further_dispatch() ->
|
||||
receive
|
||||
{member_list_dispatch, _Pids, _Event, _GuildId, _Bin} = Msg ->
|
||||
?assert(false, {unexpected_member_list_dispatch, Msg})
|
||||
after 0 ->
|
||||
ok
|
||||
end.
|
||||
|
||||
idle() ->
|
||||
receive
|
||||
stop -> ok
|
||||
after 30000 -> ok
|
||||
end.
|
||||
@@ -102,6 +102,15 @@ build_context_normalizes_fields_test() ->
|
||||
?assertEqual(false, maps:get(self_stream, Context)),
|
||||
?assertEqual(false, maps:get(is_mobile, Context)).
|
||||
|
||||
build_context_normalizes_null_connection_id_test() ->
|
||||
Context = guild_voice_connection_util:build_context(#{
|
||||
user_id => 42,
|
||||
channel_id => null,
|
||||
connection_id => null
|
||||
}),
|
||||
?assertEqual(undefined, maps:get(connection_id, Context)),
|
||||
?assertNot(maps:is_key(raw_connection_id, Context)).
|
||||
|
||||
build_context_does_not_accept_zero_ids_test() ->
|
||||
Context = guild_voice_connection_util:build_context(#{
|
||||
user_id => 0,
|
||||
|
||||
@@ -55,6 +55,30 @@ voice_state_update_invalid_channel_id_test() ->
|
||||
#{user_id => 10, channel_id => undefined}, State
|
||||
).
|
||||
|
||||
voice_state_update_guild_leave_null_connection_id_test() ->
|
||||
State = connected_state(<<"10">>),
|
||||
{reply, {error, validation_error, voice_missing_connection_id}, NewState} =
|
||||
guild_voice_connection:voice_state_update(
|
||||
#{user_id => 10, channel_id => null, connection_id => null}, State
|
||||
),
|
||||
?assertEqual(connected_voice_states(<<"10">>), maps:get(voice_states, NewState)).
|
||||
|
||||
voice_state_update_guild_leave_omitted_connection_id_test() ->
|
||||
State = connected_state(<<"10">>),
|
||||
{reply, {error, validation_error, voice_missing_connection_id}, NewState} =
|
||||
guild_voice_connection:voice_state_update(
|
||||
#{user_id => 10, channel_id => null}, State
|
||||
),
|
||||
?assertEqual(connected_voice_states(<<"10">>), maps:get(voice_states, NewState)).
|
||||
|
||||
voice_state_update_guild_leave_blank_connection_id_test() ->
|
||||
State = connected_state(<<"10">>),
|
||||
assert_voice_state_update_error(
|
||||
#{user_id => 10, channel_id => null, connection_id => <<>>},
|
||||
State,
|
||||
{error, validation_error, voice_missing_connection_id}
|
||||
).
|
||||
|
||||
voice_state_update_channel_not_found_test() ->
|
||||
State = replace_channels(base_test_state(), []),
|
||||
{reply, {error, not_found, voice_channel_not_found}, _} =
|
||||
@@ -300,6 +324,28 @@ voice_state_update_stress_many_watch_unwatch_updates_test() ->
|
||||
),
|
||||
?assertMatch(#{<<"conn-1">> := #{}}, maps:get(voice_states, FinalState)).
|
||||
|
||||
voice_state_update_clears_self_stream_without_stream_permission_test() ->
|
||||
State = connected_state(<<"10">>),
|
||||
{reply, #{success := true}, NewState} = guild_voice_connection:voice_state_update(
|
||||
update_request(#{self_stream => true, self_video => true}), State
|
||||
),
|
||||
Updated = updated_voice_state(NewState),
|
||||
?assertEqual(false, maps:get(<<"self_stream">>, Updated)),
|
||||
?assertEqual(false, maps:get(<<"self_video">>, Updated)).
|
||||
|
||||
voice_state_update_keeps_self_stream_with_stream_permission_test() ->
|
||||
State = maps:put(
|
||||
voice_states,
|
||||
connected_voice_states(<<"10">>),
|
||||
with_stream_permission(base_test_state())
|
||||
),
|
||||
{reply, #{success := true}, NewState} = guild_voice_connection:voice_state_update(
|
||||
update_request(#{self_stream => true, self_video => true}), State
|
||||
),
|
||||
Updated = updated_voice_state(NewState),
|
||||
?assertEqual(true, maps:get(<<"self_stream">>, Updated)),
|
||||
?assertEqual(true, maps:get(<<"self_video">>, Updated)).
|
||||
|
||||
voice_state_update_allows_twenty_fifth_camera_sharer_test() ->
|
||||
State = camera_state(24),
|
||||
{reply, #{success := true}, NewState} = guild_voice_connection:voice_state_update(
|
||||
@@ -379,10 +425,20 @@ camera_base_state(SharerCount) ->
|
||||
Members = [
|
||||
base_test_member(10) | [base_test_member(200 + N) || N <- lists:seq(1, SharerCount)]
|
||||
],
|
||||
State = base_test_state(),
|
||||
State = with_stream_permission(base_test_state()),
|
||||
Data0 = maps:get(data, State, #{}),
|
||||
maps:put(data, maps:put(<<"members">>, Members, Data0), State).
|
||||
|
||||
with_stream_permission(State) ->
|
||||
Roles = [
|
||||
#{
|
||||
<<"id">> => <<"999">>,
|
||||
<<"permissions">> => integer_to_binary(constants:stream_permission())
|
||||
}
|
||||
],
|
||||
Data0 = maps:get(data, State, #{}),
|
||||
maps:put(data, maps:put(<<"roles">>, Roles, Data0), State).
|
||||
|
||||
camera_state(SharerCount) ->
|
||||
maps:put(
|
||||
voice_states, camera_test_voice_states(SharerCount), camera_base_state(SharerCount)
|
||||
|
||||
@@ -135,6 +135,7 @@ users_in_channel_test() ->
|
||||
?assertNot(sets:is_element(3, Result)).
|
||||
|
||||
permission_sync_disconnects_when_view_channel_lost_test() ->
|
||||
ok = drain_mailbox(),
|
||||
ConnectOnly = constants:connect_permission(),
|
||||
{State, UserId, ChannelId, GuildId} = build_perm_sync_state(ConnectOnly),
|
||||
ok = guild_voice_permission_sync:sync_user_voice_permissions(UserId, State),
|
||||
@@ -146,6 +147,7 @@ permission_sync_disconnects_when_view_channel_lost_test() ->
|
||||
end.
|
||||
|
||||
permission_sync_does_not_disconnect_with_full_perms_test() ->
|
||||
ok = drain_mailbox(),
|
||||
FullPerms =
|
||||
constants:view_channel_permission() bor
|
||||
constants:connect_permission() bor
|
||||
@@ -160,6 +162,123 @@ permission_sync_does_not_disconnect_with_full_perms_test() ->
|
||||
?assert(false)
|
||||
end.
|
||||
|
||||
permission_sync_runs_on_role_permission_update_test() ->
|
||||
ok = drain_mailbox(),
|
||||
{State, UserId, ChannelId, GuildId, RoleId} = build_sync_wiring_state(),
|
||||
_ = guild_state:update_state(
|
||||
guild_role_update,
|
||||
#{
|
||||
<<"role">> => #{
|
||||
<<"id">> => integer_to_binary(RoleId),
|
||||
<<"permissions">> => <<"0">>
|
||||
}
|
||||
},
|
||||
State
|
||||
),
|
||||
receive
|
||||
{synced, GuildId, ChannelId, UserId, <<"test-conn">>, Perms} ->
|
||||
?assertEqual(false, maps:get(can_speak, Perms)),
|
||||
?assertEqual(false, maps:get(can_stream, Perms))
|
||||
after 200 ->
|
||||
?assert(false)
|
||||
end.
|
||||
|
||||
permission_sync_runs_on_channel_overwrite_update_test() ->
|
||||
ok = drain_mailbox(),
|
||||
{State, UserId, ChannelId, GuildId, RoleId} = build_sync_wiring_state(),
|
||||
_ = guild_state:update_state(
|
||||
channel_update,
|
||||
#{
|
||||
<<"id">> => integer_to_binary(ChannelId),
|
||||
<<"type">> => 2,
|
||||
<<"permission_overwrites">> => [
|
||||
#{
|
||||
<<"id">> => integer_to_binary(RoleId),
|
||||
<<"type">> => 0,
|
||||
<<"allow">> => <<"0">>,
|
||||
<<"deny">> => integer_to_binary(constants:stream_permission())
|
||||
}
|
||||
]
|
||||
},
|
||||
State
|
||||
),
|
||||
receive
|
||||
{synced, GuildId, ChannelId, UserId, <<"test-conn">>, Perms} ->
|
||||
?assertEqual(true, maps:get(can_speak, Perms)),
|
||||
?assertEqual(false, maps:get(can_stream, Perms))
|
||||
after 200 ->
|
||||
?assert(false)
|
||||
end.
|
||||
|
||||
permission_sync_runs_on_member_role_removal_test() ->
|
||||
ok = drain_mailbox(),
|
||||
{State, UserId, ChannelId, GuildId, _RoleId} = build_sync_wiring_state(),
|
||||
_ = guild_state:update_state(
|
||||
guild_member_update,
|
||||
#{
|
||||
<<"user">> => #{<<"id">> => integer_to_binary(UserId)},
|
||||
<<"roles">> => []
|
||||
},
|
||||
State
|
||||
),
|
||||
receive
|
||||
{synced, GuildId, ChannelId, UserId, <<"test-conn">>, Perms} ->
|
||||
?assertEqual(false, maps:get(can_speak, Perms)),
|
||||
?assertEqual(false, maps:get(can_stream, Perms))
|
||||
after 200 ->
|
||||
?assert(false)
|
||||
end.
|
||||
|
||||
build_sync_wiring_state() ->
|
||||
Self = self(),
|
||||
TestFun = fun(GId, ChId, UId, ConnId, Perms) ->
|
||||
Self ! {synced, GId, ChId, UId, ConnId, Perms}
|
||||
end,
|
||||
UserId = 10,
|
||||
ChannelId = 500,
|
||||
GuildId = 42,
|
||||
RoleId = 999,
|
||||
RoleIdBin = integer_to_binary(RoleId),
|
||||
EveryonePerms = required_voice_perms(),
|
||||
RolePerms = constants:speak_permission() bor constants:stream_permission(),
|
||||
State = #{
|
||||
id => GuildId,
|
||||
voice_states => #{
|
||||
<<"conn">> => #{
|
||||
<<"user_id">> => integer_to_binary(UserId),
|
||||
<<"channel_id">> => integer_to_binary(ChannelId),
|
||||
<<"connection_id">> => <<"test-conn">>,
|
||||
<<"deaf">> => false
|
||||
}
|
||||
},
|
||||
member_list_subscriptions => guild_member_list_subs:new(),
|
||||
test_permission_sync_fun => TestFun,
|
||||
data => #{
|
||||
<<"guild">> => #{<<"owner_id">> => <<"1">>},
|
||||
<<"roles">> => [
|
||||
#{<<"id">> => RoleIdBin, <<"permissions">> => integer_to_binary(RolePerms)},
|
||||
#{
|
||||
<<"id">> => integer_to_binary(GuildId),
|
||||
<<"permissions">> => integer_to_binary(EveryonePerms)
|
||||
}
|
||||
],
|
||||
<<"members">> => [
|
||||
#{
|
||||
<<"user">> => #{<<"id">> => integer_to_binary(UserId)},
|
||||
<<"roles">> => [RoleIdBin]
|
||||
}
|
||||
],
|
||||
<<"channels">> => [
|
||||
#{
|
||||
<<"id">> => integer_to_binary(ChannelId),
|
||||
<<"type">> => 2,
|
||||
<<"permission_overwrites">> => []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{State, UserId, ChannelId, GuildId, RoleId}.
|
||||
|
||||
build_perm_sync_state(Permissions) ->
|
||||
Self = self(),
|
||||
TestFun = fun(GId, ChId, UId, ConnId, Perms) ->
|
||||
@@ -221,3 +340,10 @@ camera_limited_voice_states(Count, ChannelId) ->
|
||||
#{},
|
||||
lists:seq(1, Count)
|
||||
).
|
||||
|
||||
drain_mailbox() ->
|
||||
receive
|
||||
_ -> drain_mailbox()
|
||||
after 0 ->
|
||||
ok
|
||||
end.
|
||||
|
||||
@@ -103,6 +103,84 @@ apply_voice_permissions_to_flags_suppresses_without_speak_test() ->
|
||||
?assertEqual(false, maps:get(self_mute, Result)),
|
||||
?assertEqual(true, maps:get(suppress, Result)).
|
||||
|
||||
apply_voice_permissions_to_flags_clears_self_stream_without_stream_test() ->
|
||||
Flags = #{self_stream => true, self_video => true, suppress => false},
|
||||
VoicePerms = #{
|
||||
can_speak => true,
|
||||
can_stream => false,
|
||||
can_video => false
|
||||
},
|
||||
Result = voice_utils:apply_voice_permissions_to_flags(Flags, VoicePerms),
|
||||
?assertEqual(false, maps:get(self_stream, Result)),
|
||||
?assertEqual(false, maps:get(self_video, Result)),
|
||||
?assertEqual(false, maps:get(suppress, Result)).
|
||||
|
||||
apply_voice_permissions_to_flags_keeps_self_stream_with_stream_test() ->
|
||||
Flags = #{self_stream => true, self_video => true, suppress => false},
|
||||
VoicePerms = #{
|
||||
can_speak => true,
|
||||
can_stream => true,
|
||||
can_video => true
|
||||
},
|
||||
Result = voice_utils:apply_voice_permissions_to_flags(Flags, VoicePerms),
|
||||
?assertEqual(true, maps:get(self_stream, Result)),
|
||||
?assertEqual(true, maps:get(self_video, Result)).
|
||||
|
||||
apply_voice_permissions_to_flags_clears_self_stream_denied_by_overwrite_test() ->
|
||||
UserId = 10,
|
||||
ChannelId = 500,
|
||||
State = stream_denied_overwrite_state(UserId, ChannelId),
|
||||
VoicePerms = voice_utils:compute_voice_permissions(UserId, ChannelId, State),
|
||||
?assertEqual(true, maps:get(can_speak, VoicePerms)),
|
||||
?assertEqual(false, maps:get(can_stream, VoicePerms)),
|
||||
Result = voice_utils:apply_voice_permissions_to_flags(
|
||||
#{self_stream => true, self_video => true, suppress => false}, VoicePerms
|
||||
),
|
||||
?assertEqual(false, maps:get(self_stream, Result)),
|
||||
?assertEqual(false, maps:get(self_video, Result)),
|
||||
?assertEqual(false, maps:get(suppress, Result)).
|
||||
|
||||
stream_denied_overwrite_state(UserId, ChannelId) ->
|
||||
GuildId = 90,
|
||||
RoleId = 300,
|
||||
RolePerms =
|
||||
constants:view_channel_permission() bor
|
||||
constants:connect_permission() bor
|
||||
constants:speak_permission() bor
|
||||
constants:stream_permission(),
|
||||
#{
|
||||
id => GuildId,
|
||||
data => #{
|
||||
<<"guild">> => #{<<"owner_id">> => <<"999">>},
|
||||
<<"roles">> => [
|
||||
#{<<"id">> => integer_to_binary(GuildId), <<"permissions">> => <<"0">>},
|
||||
#{
|
||||
<<"id">> => integer_to_binary(RoleId),
|
||||
<<"permissions">> => integer_to_binary(RolePerms)
|
||||
}
|
||||
],
|
||||
<<"members">> => [
|
||||
#{
|
||||
<<"user">> => #{<<"id">> => integer_to_binary(UserId)},
|
||||
<<"roles">> => [integer_to_binary(RoleId)]
|
||||
}
|
||||
],
|
||||
<<"channels">> => [
|
||||
#{
|
||||
<<"id">> => integer_to_binary(ChannelId),
|
||||
<<"permission_overwrites">> => [
|
||||
#{
|
||||
<<"id">> => integer_to_binary(RoleId),
|
||||
<<"type">> => 0,
|
||||
<<"allow">> => <<"0">>,
|
||||
<<"deny">> => integer_to_binary(constants:stream_permission())
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}.
|
||||
|
||||
generate_token_nonce_format_test() ->
|
||||
Nonce = voice_utils:generate_token_nonce(),
|
||||
?assert(is_binary(Nonce)),
|
||||
|
||||
Reference in New Issue
Block a user