mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 04:55:42 +09:00
243 lines
9.7 KiB
Erlang
243 lines
9.7 KiB
Erlang
%% SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
-module(voice_p2p_tests).
|
|
-typing([eqwalizer]).
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
peer(ConnectionId, SessionId, P2p) ->
|
|
#{
|
|
<<"connection_id">> => ConnectionId,
|
|
<<"session_id">> => SessionId,
|
|
<<"user_id">> => <<"10">>,
|
|
<<"p2p">> => P2p
|
|
}.
|
|
|
|
mesh(Count) ->
|
|
[
|
|
peer(integer_to_binary(Index), <<"sess-", (integer_to_binary(Index))/binary>>, true)
|
|
|| Index <- lists:seq(1, Count)
|
|
].
|
|
|
|
agreed_requires_an_explicit_true_from_a_user_test() ->
|
|
?assert(voice_p2p:agreed(true, false)),
|
|
?assertNot(voice_p2p:agreed(true, true)),
|
|
?assertNot(voice_p2p:agreed(false, false)),
|
|
?assertNot(voice_p2p:agreed(undefined, false)),
|
|
?assertNot(voice_p2p:agreed(<<"true">>, false)).
|
|
|
|
declined_requires_an_explicit_false_test() ->
|
|
?assert(voice_p2p:declined(false)),
|
|
?assertNot(voice_p2p:declined(true)),
|
|
?assertNot(voice_p2p:declined(undefined)),
|
|
?assertNot(voice_p2p:declined(null)).
|
|
|
|
channel_mode_test() ->
|
|
?assertEqual(empty, voice_p2p:channel_mode([])),
|
|
?assertEqual(p2p, voice_p2p:channel_mode(mesh(2))),
|
|
?assertEqual(sfu, voice_p2p:channel_mode([peer(<<"a">>, <<"s">>, false), #{}])),
|
|
?assertEqual(mixed, voice_p2p:channel_mode([peer(<<"a">>, <<"s">>, false) | mesh(1)])).
|
|
|
|
pinned_reads_the_channel_flag_test() ->
|
|
?assert(voice_p2p:pinned(#{<<"rtc_p2p">> => true})),
|
|
?assertNot(voice_p2p:pinned(#{<<"rtc_p2p">> => false})),
|
|
?assertNot(voice_p2p:pinned(#{})),
|
|
?assertNot(voice_p2p:pinned(undefined)).
|
|
|
|
join_decision_for_a_live_mesh_test() ->
|
|
?assertEqual({p2p, joiner, 2}, voice_p2p:join_decision(true, mesh(1), false, false)),
|
|
?assertEqual({p2p, joiner, 4}, voice_p2p:join_decision(true, mesh(3), false, false)),
|
|
?assertEqual(
|
|
{reject, voice_p2p_consent_required},
|
|
voice_p2p:join_decision(false, mesh(3), false, false)
|
|
),
|
|
?assertEqual(
|
|
{reject, voice_p2p_consent_required},
|
|
voice_p2p:join_decision(false, mesh(4), false, false)
|
|
),
|
|
?assertEqual(
|
|
{reject, voice_channel_full}, voice_p2p:join_decision(true, mesh(4), false, false)
|
|
).
|
|
|
|
join_decision_for_an_empty_channel_test() ->
|
|
?assertEqual({p2p, initiator, 1}, voice_p2p:join_decision(true, [], false, false)),
|
|
?assertEqual({p2p, initiator, 1}, voice_p2p:join_decision(true, [], false, true)),
|
|
?assertEqual(sfu, voice_p2p:join_decision(false, [], false, false)),
|
|
?assertEqual(sfu, voice_p2p:join_decision(false, [], false, true)),
|
|
?assertEqual(sfu, voice_p2p:join_decision(true, [], true, false)),
|
|
?assertEqual(sfu, voice_p2p:join_decision(false, [], true, true)).
|
|
|
|
join_decision_for_an_sfu_call_ignores_the_flag_test() ->
|
|
Sfu = [peer(<<"a">>, <<"s">>, false)],
|
|
?assertEqual(sfu, voice_p2p:join_decision(true, Sfu, false, false)),
|
|
?assertEqual(sfu, voice_p2p:join_decision(false, Sfu, false, true)).
|
|
|
|
admit_test() ->
|
|
Grant = {p2p, <<"conn">>, [], 2},
|
|
Token = #{token => <<"tok">>},
|
|
?assertEqual(Grant, voice_p2p:admit({p2p, initiator, 1}, Grant)),
|
|
?assertEqual(Grant, voice_p2p:admit({p2p, joiner, 3}, Grant)),
|
|
?assertEqual(sfu, voice_p2p:admit({p2p, initiator, 1}, Token)),
|
|
?assertEqual({reject, voice_p2p_unavailable}, voice_p2p:admit({p2p, joiner, 3}, Token)),
|
|
?assertEqual(sfu, voice_p2p:admit(sfu, Token)),
|
|
?assertEqual({reject, voice_token_failed}, voice_p2p:admit(sfu, Grant)).
|
|
|
|
admit_maps_a_size_decline_to_channel_full_test() ->
|
|
Declined = {declined, voice_channel_full},
|
|
?assertEqual({reject, voice_channel_full}, voice_p2p:admit({p2p, joiner, 3}, Declined)),
|
|
?assertEqual({reject, voice_channel_full}, voice_p2p:admit({p2p, initiator, 1}, Declined)),
|
|
?assertEqual({reject, voice_token_failed}, voice_p2p:admit(sfu, Declined)).
|
|
|
|
invariant_violation_test() ->
|
|
?assertEqual(ok, voice_p2p:invariant_violation([])),
|
|
?assertEqual(ok, voice_p2p:invariant_violation(mesh(4))),
|
|
?assertEqual({error, voice_channel_full}, voice_p2p:invariant_violation(mesh(5))),
|
|
?assertEqual(
|
|
{error, voice_p2p_consent_required},
|
|
voice_p2p:invariant_violation([peer(<<"a">>, <<"s">>, false) | mesh(1)])
|
|
),
|
|
?assertEqual(ok, voice_p2p:invariant_violation([peer(<<"a">>, <<"s">>, false)])).
|
|
|
|
invariant_violation_holds_a_mesh_to_the_configured_cap_test() ->
|
|
?assertEqual(ok, voice_p2p:invariant_violation(mesh(2), 2)),
|
|
?assertEqual({error, voice_channel_full}, voice_p2p:invariant_violation(mesh(3), 2)),
|
|
?assertEqual(ok, voice_p2p:invariant_violation(mesh(4), undefined)),
|
|
?assertEqual({error, voice_channel_full}, voice_p2p:invariant_violation(mesh(5), 9)),
|
|
?assertEqual(ok, voice_p2p:invariant_violation([peer(<<"a">>, <<"s">>, false)], 1)).
|
|
|
|
reports_rejection_test() ->
|
|
?assert(voice_p2p:reports_rejection(voice_p2p_consent_required)),
|
|
?assert(voice_p2p:reports_rejection(voice_p2p_unavailable)),
|
|
?assert(voice_p2p:reports_rejection(voice_channel_full)),
|
|
?assertNot(voice_p2p:reports_rejection(voice_token_failed)).
|
|
|
|
to_sfu_clears_the_flag_and_bumps_the_version_test() ->
|
|
VoiceState = (peer(<<"a">>, <<"s">>, true))#{<<"version">> => 3},
|
|
Converted = voice_p2p:to_sfu(VoiceState),
|
|
?assertEqual(false, maps:get(<<"p2p">>, Converted)),
|
|
?assertEqual(4, maps:get(<<"version">>, Converted)).
|
|
|
|
add_to_token_request_test() ->
|
|
Request = #{<<"type">> => <<"voice_get_token">>},
|
|
?assertEqual(Request, voice_p2p:add_to_token_request(Request, sfu, <<"SE">>)),
|
|
?assertEqual(
|
|
Request#{
|
|
<<"p2p">> => true,
|
|
<<"p2p_initiator">> => true,
|
|
<<"p2p_participant_count">> => 1,
|
|
<<"country_code">> => <<"SE">>
|
|
},
|
|
voice_p2p:add_to_token_request(Request, {p2p, initiator, 1}, <<"SE">>)
|
|
),
|
|
?assertEqual(
|
|
Request#{
|
|
<<"p2p">> => true, <<"p2p_initiator">> => false, <<"p2p_participant_count">> => 3
|
|
},
|
|
voice_p2p:add_to_token_request(Request, {p2p, joiner, 3}, undefined)
|
|
).
|
|
|
|
grant_reads_a_p2p_grant_test() ->
|
|
Stun = #{<<"urls">> => [<<"stun:stun.example:3478">>], <<"ignored">> => true},
|
|
Data = #{<<"p2p">> => true, <<"connectionId">> => <<"conn-1">>, <<"iceServers">> => [Stun]},
|
|
?assertEqual(
|
|
{p2p, <<"conn-1">>, [#{<<"urls">> => [<<"stun:stun.example:3478">>]}], 4},
|
|
voice_p2p:grant(Data)
|
|
).
|
|
|
|
grant_reads_the_configured_cap_test() ->
|
|
Data = #{<<"p2p">> => true, <<"connectionId">> => <<"conn-1">>, <<"iceServers">> => []},
|
|
?assertEqual(
|
|
{p2p, <<"conn-1">>, [], 3}, voice_p2p:grant(Data#{<<"maxParticipants">> => 3})
|
|
),
|
|
?assertEqual(
|
|
{p2p, <<"conn-1">>, [], 4}, voice_p2p:grant(Data#{<<"maxParticipants">> => 9})
|
|
),
|
|
?assertEqual(
|
|
{p2p, <<"conn-1">>, [], 4}, voice_p2p:grant(Data#{<<"maxParticipants">> => 0})
|
|
).
|
|
|
|
grant_keeps_only_urls_of_each_ice_server_test() ->
|
|
Server = #{
|
|
<<"urls">> => [<<"stun:stun.example:3478">>],
|
|
<<"username">> => <<"user">>,
|
|
<<"credential">> => <<"secret">>
|
|
},
|
|
Data = #{
|
|
<<"p2p">> => true, <<"connectionId">> => <<"conn-1">>, <<"iceServers">> => [Server]
|
|
},
|
|
?assertEqual(
|
|
{p2p, <<"conn-1">>, [#{<<"urls">> => [<<"stun:stun.example:3478">>]}], 4},
|
|
voice_p2p:grant(Data)
|
|
).
|
|
|
|
grant_reads_a_size_decline_test() ->
|
|
?assertEqual(
|
|
{declined, voice_channel_full},
|
|
voice_p2p:grant(#{<<"p2p">> => false, <<"declineReason">> => <<"channel_full">>})
|
|
),
|
|
?assertEqual(
|
|
sfu, voice_p2p:grant(#{<<"p2p">> => false, <<"declineReason">> => <<"unknown">>})
|
|
).
|
|
|
|
grant_reads_a_token_response_as_sfu_test() ->
|
|
?assertEqual(
|
|
sfu,
|
|
voice_p2p:grant(#{
|
|
<<"token">> => <<"tok">>,
|
|
<<"endpoint">> => <<"wss://voice.example">>,
|
|
<<"connectionId">> => <<"conn-1">>
|
|
})
|
|
).
|
|
|
|
voice_server_update_shape_test() ->
|
|
IceServers = [#{<<"urls">> => [<<"stun:stun.example:3478">>]}],
|
|
?assertEqual(
|
|
#{
|
|
<<"p2p">> => true,
|
|
<<"ice_servers">> => IceServers,
|
|
<<"connection_id">> => <<"conn-1">>,
|
|
<<"channel_id">> => <<"100">>,
|
|
<<"guild_id">> => <<"999">>
|
|
},
|
|
voice_p2p:voice_server_update(<<"conn-1">>, 100, 999, IceServers)
|
|
),
|
|
?assertNot(
|
|
maps:is_key(
|
|
<<"guild_id">>, voice_p2p:voice_server_update(<<"conn-1">>, 100, null, IceServers)
|
|
)
|
|
).
|
|
|
|
signal_route_allows_a_mesh_peer_test() ->
|
|
[Sender, Target] = mesh(2),
|
|
?assertEqual({ok, Sender, Target}, voice_p2p:signal_route(mesh(2), <<"sess-1">>, <<"2">>)).
|
|
|
|
signal_route_denies_a_sender_outside_the_mesh_test() ->
|
|
?assertEqual(error, voice_p2p:signal_route(mesh(2), <<"sess-9">>, <<"2">>)).
|
|
|
|
signal_route_denies_an_unknown_target_test() ->
|
|
?assertEqual(error, voice_p2p:signal_route(mesh(2), <<"sess-1">>, <<"9">>)).
|
|
|
|
signal_route_denies_the_senders_own_connection_test() ->
|
|
?assertEqual(error, voice_p2p:signal_route(mesh(2), <<"sess-1">>, <<"1">>)).
|
|
|
|
signal_route_denies_sfu_voice_states_test() ->
|
|
Sfu = [peer(<<"1">>, <<"sess-1">>, false), peer(<<"2">>, <<"sess-2">>, false)],
|
|
?assertEqual(error, voice_p2p:signal_route(Sfu, <<"sess-1">>, <<"2">>)),
|
|
Mixed = [peer(<<"1">>, <<"sess-1">>, true), peer(<<"2">>, <<"sess-2">>, false)],
|
|
?assertEqual(error, voice_p2p:signal_route(Mixed, <<"sess-1">>, <<"2">>)).
|
|
|
|
signal_payload_shape_test() ->
|
|
[Sender | _] = mesh(1),
|
|
Data = #{<<"type">> => <<"offer">>, <<"sdp">> => <<"v=0">>},
|
|
?assertEqual(
|
|
#{
|
|
<<"guild_id">> => <<"999">>,
|
|
<<"channel_id">> => <<"100">>,
|
|
<<"from">> => <<"1">>,
|
|
<<"user_id">> => <<"10">>,
|
|
<<"data">> => Data
|
|
},
|
|
voice_p2p:signal_payload(999, 100, Sender, Data)
|
|
),
|
|
?assertNot(maps:is_key(<<"guild_id">>, voice_p2p:signal_payload(null, 100, Sender, Data))).
|