Files
fluxer/fluxer_gateway/test/guild_voice_broadcast_tests.erl
T

211 lines
7.1 KiB
Erlang

%% SPDX-License-Identifier: AGPL-3.0-or-later
-module(guild_voice_broadcast_tests).
-typing([eqwalizer]).
-include_lib("eunit/include/eunit.hrl").
base_guild_state() ->
#{
id => 999,
data => #{<<"channels">> => [#{<<"id">> => <<"100">>, <<"type">> => 2}]},
sessions => #{
<<"s1">> => #{
pid => self(),
user_id => 11,
viewable_channels => #{100 => true}
}
},
voice_states => #{},
pending_voice_connections => #{}
}.
voice_server_state(GuildState) ->
GuildState#{guild_pid => self()}.
join_voice_state(ConnId) ->
#{
<<"user_id">> => <<"10">>,
<<"guild_id">> => <<"999">>,
<<"channel_id">> => <<"100">>,
<<"connection_id">> => ConnId,
<<"self_mute">> => false,
<<"self_deaf">> => false
}.
broadcast_payload(VoiceState) ->
maps:merge(
#{
<<"session_id">> => null,
<<"member">> => null,
<<"mute">> => false,
<<"deaf">> => false,
<<"self_video">> => false,
<<"self_stream">> => false,
<<"is_mobile">> => false,
<<"suppress">> => false,
<<"viewer_stream_keys">> => [],
<<"e2ee_capable">> => false,
<<"version">> => 0
},
VoiceState
).
pending_join(ConnId) ->
#{
user_id => 10,
guild_id => 999,
channel_id => 100,
voice_state => join_voice_state(ConnId)
}.
receive_relay() ->
receive
{'$gen_cast', {relay_voice_state_update, _, _} = RelayMsg} -> RelayMsg
after 1000 ->
error(relay_voice_state_update_not_received)
end.
pump_relay_to_guild(GuildState) ->
RelayMsg = receive_relay(),
{noreply, NewGuildState} = guild_voice_handler:handle_cast(RelayMsg, GuildState),
NewGuildState.
collect_voice_state_dispatches() ->
[decoded_voice_state(Payload) || Payload <- collect_raw_voice_state_dispatches()].
collect_raw_voice_state_dispatches() ->
receive
{'$gen_cast', {dispatch, voice_state_update, Payload}} ->
[Payload | collect_raw_voice_state_dispatches()]
after 0 ->
[]
end.
decoded_voice_state({pre_encoded, Bin}) -> json:decode(Bin);
decoded_voice_state(Payload) -> Payload.
map_path_wire_bytes(VoiceState) ->
iolist_to_binary(
json:encode(guild_data_wire:payload(VoiceState), fun json:encode_value/2)
).
confirm_join(ConnId, GuildState) ->
VoiceServerState = (voice_server_state(GuildState))#{
pending_voice_connections => #{ConnId => pending_join(ConnId)}
},
{reply, #{success := true}, _} =
guild_voice_connection:confirm_voice_connection_from_livekit(
#{connection_id => ConnId}, VoiceServerState
),
pump_relay_to_guild(GuildState).
single_join_dispatches_once_per_recipient_test() ->
GuildState = confirm_join(<<"conn1">>, base_guild_state()),
?assertEqual(
[broadcast_payload(join_voice_state(<<"conn1">>))], collect_voice_state_dispatches()
),
?assertEqual(
join_voice_state(<<"conn1">>),
maps:get(<<"conn1">>, maps:get(voice_states, GuildState))
).
relay_syncs_guild_cache_without_rebroadcast_test() ->
VS = join_voice_state(<<"conn1">>),
{noreply, NewGuildState} = guild_voice_handler:handle_cast(
{relay_voice_state_update, VS, null}, base_guild_state()
),
?assertEqual(VS, maps:get(<<"conn1">>, maps:get(voice_states, NewGuildState))),
?assertEqual([], collect_voice_state_dispatches()).
mute_change_still_dispatches_test() ->
GuildState0 = confirm_join(<<"conn1">>, base_guild_state()),
?assertEqual(
[broadcast_payload(join_voice_state(<<"conn1">>))], collect_voice_state_dispatches()
),
MutedVS = (join_voice_state(<<"conn1">>))#{
<<"self_mute">> => true, <<"self_deaf">> => true
},
ok = guild_voice_broadcast:broadcast_voice_state_update(
MutedVS, voice_server_state(GuildState0), <<"100">>
),
GuildState1 = pump_relay_to_guild(GuildState0),
?assertEqual([broadcast_payload(MutedVS)], collect_voice_state_dispatches()),
?assertEqual(MutedVS, maps:get(<<"conn1">>, maps:get(voice_states, GuildState1))).
rejoin_after_leave_still_dispatches_test() ->
GuildState0 = confirm_join(<<"conn1">>, base_guild_state()),
?assertEqual(
[broadcast_payload(join_voice_state(<<"conn1">>))], collect_voice_state_dispatches()
),
LeaveVS = (join_voice_state(<<"conn1">>))#{<<"channel_id">> => null},
ok = guild_voice_broadcast:broadcast_voice_state_update(
LeaveVS, voice_server_state(GuildState0), <<"100">>
),
GuildState1 = pump_relay_to_guild(GuildState0),
?assertEqual([broadcast_payload(LeaveVS)], collect_voice_state_dispatches()),
?assertNot(maps:is_key(<<"conn1">>, maps:get(voice_states, GuildState1))),
GuildState2 = confirm_join(<<"conn2">>, GuildState1),
?assertEqual(
[broadcast_payload(join_voice_state(<<"conn2">>))], collect_voice_state_dispatches()
),
?assertEqual(
join_voice_state(<<"conn2">>),
maps:get(<<"conn2">>, maps:get(voice_states, GuildState2))
).
broadcast_sanitizes_routing_metadata_test() ->
VS = (join_voice_state(<<"conn1">>))#{
<<"latitude">> => <<"1.0">>,
<<"longitude">> => <<"2.0">>,
<<"region_id">> => <<"us-east">>,
<<"server_id">> => <<"voice-1">>
},
GuildState = base_guild_state(),
ok = guild_voice_broadcast:broadcast_voice_state_update(
VS, voice_server_state(GuildState), null
),
_ = pump_relay_to_guild(GuildState),
?assertEqual(
[broadcast_payload(join_voice_state(<<"conn1">>))], collect_voice_state_dispatches()
).
guild_fanout_pre_encodes_identical_wire_bytes_test() ->
VS = join_voice_state(<<"conn1">>),
?assertEqual(
{pre_encoded, map_path_wire_bytes(broadcast_payload(VS))}, dispatch_payload_for(VS)
).
guild_fanout_payload_never_touches_dm_voice_states_test() ->
{pre_encoded, Bin} = dispatch_payload_for(join_voice_state(<<"conn1">>)),
State = #{dm_voice_states => #{}},
EventData = #{} = json:decode(Bin),
?assertEqual(
State,
session_dispatch_guild:update_dm_voice_states_map(
voice_state_update, EventData, State
)
).
dm_scoped_voice_state_stays_a_map_and_updates_dm_voice_states_test() ->
DmVS = (join_voice_state(<<"conn1">>))#{<<"guild_id">> => null},
Payload = dispatch_payload_for(DmVS),
?assertEqual(broadcast_payload(DmVS), Payload),
State = session_dispatch_guild:update_dm_voice_states_map(
voice_state_update, Payload, #{dm_voice_states => #{}}
),
#{dm_voice_states := #{<<"conn1">> := StoredVoiceState}} = State,
?assertEqual(
broadcast_payload(DmVS),
StoredVoiceState
).
dispatch_payload_for(VoiceState) ->
GuildState = base_guild_state(),
ok = guild_voice_broadcast:broadcast_voice_state_update(
VoiceState, voice_server_state(GuildState), null
),
_ = pump_relay_to_guild(GuildState),
[Payload] = collect_raw_voice_state_dispatches(),
Payload.