Files
fluxer/fluxer_gateway/test/gateway_rollout_config_tests.erl
T

254 lines
9.7 KiB
Erlang

%% SPDX-License-Identifier: AGPL-3.0-or-later
-module(gateway_rollout_config_tests).
-typing([eqwalizer]).
-include_lib("eunit/include/eunit.hrl").
-define(PERSISTENT_TERM_KEY, gateway_rollout_config).
default_config() ->
#{
<<"session_rollout_percentage">> => 100,
<<"session_rollout_mode">> => <<"modulo">>,
<<"guild_rollout_percentage">> => 100,
<<"rpc_request_timeout_ms">> => 10000,
<<"max_concurrent_session_starts">> => 512,
<<"max_concurrent_guild_starts">> => 256,
<<"gateway_dispatch_relay_shards">> => 32,
<<"gateway_dispatch_relay_max_queue">> => 50000,
<<"voice_e2ee_scope">> => <<"guild_feature_only">>,
<<"voice_reconciliation_v3_percentage">> => 100,
<<"voice_reconciliation_v3_interval_ms">> => 2000
}.
default_config_has_expected_keys_test() ->
persistent_term:erase(?PERSISTENT_TERM_KEY),
Config = gateway_rollout_config:get(),
?assertEqual(100, maps:get(<<"session_rollout_percentage">>, Config)),
?assertEqual(<<"modulo">>, maps:get(<<"session_rollout_mode">>, Config)),
?assertEqual(100, maps:get(<<"guild_rollout_percentage">>, Config)),
?assertEqual(10000, maps:get(<<"rpc_request_timeout_ms">>, Config)),
?assertEqual(512, maps:get(<<"max_concurrent_session_starts">>, Config)),
?assertEqual(256, maps:get(<<"max_concurrent_guild_starts">>, Config)),
?assertEqual(100, maps:get(<<"voice_reconciliation_v3_percentage">>, Config)),
?assertEqual(2000, maps:get(<<"voice_reconciliation_v3_interval_ms">>, Config)).
is_session_eligible_full_rollout_test() ->
persistent_term:put(?PERSISTENT_TERM_KEY, default_config()),
?assert(gateway_rollout_config:is_session_eligible(<<"user123">>)),
?assert(gateway_rollout_config:is_session_eligible(<<"user456">>)).
is_guild_eligible_full_rollout_test() ->
persistent_term:put(?PERSISTENT_TERM_KEY, default_config()),
?assert(gateway_rollout_config:is_guild_eligible(<<"guild123">>)),
?assert(gateway_rollout_config:is_guild_eligible(<<"guild456">>)).
is_session_eligible_zero_rollout_test() ->
Config = (default_config())#{<<"session_rollout_percentage">> => 0},
persistent_term:put(?PERSISTENT_TERM_KEY, Config),
?assertNot(gateway_rollout_config:is_session_eligible(<<"user123">>)),
?assertNot(gateway_rollout_config:is_session_eligible(<<"user456">>)).
is_guild_eligible_zero_rollout_test() ->
Config = (default_config())#{<<"guild_rollout_percentage">> => 0},
persistent_term:put(?PERSISTENT_TERM_KEY, Config),
?assertNot(gateway_rollout_config:is_guild_eligible(<<"guild123">>)),
?assertNot(gateway_rollout_config:is_guild_eligible(<<"guild456">>)).
validate_config_rejects_bad_rollout_values_test() ->
?assertMatch(
{error, {invalid_field, <<"session_rollout_percentage">>, 101}},
gateway_rollout_config_validate:validate(
#{
<<"session_rollout_percentage">> => 101
},
default_config()
)
),
?assertMatch(
{error, {invalid_field, <<"session_rollout_mode">>, <<"open">>}},
gateway_rollout_config_validate:validate(
#{
<<"session_rollout_mode">> => <<"open">>
},
default_config()
)
),
?assertMatch(
{error, {invalid_field, <<"max_concurrent_session_starts">>, 0}},
gateway_rollout_config_validate:validate(
#{
<<"max_concurrent_session_starts">> => 0
},
default_config()
)
).
validate_config_ignores_unknown_keys_and_merges_defaults_test() ->
{ok, Config} = gateway_rollout_config_validate:validate(
#{
<<"session_rollout_percentage">> => 25,
<<"unknown">> => unsafe
},
default_config()
),
?assertEqual(25, maps:get(<<"session_rollout_percentage">>, Config)),
?assertEqual(false, maps:is_key(<<"unknown">>, Config)),
?assertEqual(256, maps:get(<<"max_concurrent_guild_starts">>, Config)).
session_start_rollout_decision_eligible_test() ->
persistent_term:put(?PERSISTENT_TERM_KEY, default_config()),
?assertEqual(
eligible,
gateway_rollout_config:session_start_rollout_decision(#{<<"user_id">> => <<"123">>})
).
session_start_rollout_decision_missing_user_id_test() ->
Config = (default_config())#{<<"session_rollout_percentage">> => 50},
persistent_term:put(?PERSISTENT_TERM_KEY, Config),
?assertEqual(missing_user_id, gateway_rollout_config:session_start_rollout_decision(#{})).
is_clustered_false_when_flag_unset_test() ->
persistent_term:put({fluxer_gateway, runtime_config}, #{}),
?assertEqual(false, gateway_rollout_config:is_clustered()).
is_clustered_false_when_flag_not_true_test() ->
persistent_term:put({fluxer_gateway, runtime_config}, #{cluster_enabled => false}),
?assertEqual(false, gateway_rollout_config:is_clustered()),
persistent_term:put({fluxer_gateway, runtime_config}, #{cluster_enabled => <<"true">>}),
?assertEqual(false, gateway_rollout_config:is_clustered()),
persistent_term:put({fluxer_gateway, runtime_config}, #{cluster_enabled => 1}),
?assertEqual(false, gateway_rollout_config:is_clustered()).
is_clustered_false_when_flag_true_but_only_self_test() ->
persistent_term:put({fluxer_gateway, runtime_config}, #{cluster_enabled => true}),
persistent_term:erase({gateway_cluster_membership, members}),
?assertEqual(false, gateway_rollout_config:is_clustered()),
persistent_term:put({gateway_cluster_membership, members}, [node()]),
?assertEqual(false, gateway_rollout_config:is_clustered()),
persistent_term:erase({gateway_cluster_membership, members}),
persistent_term:erase({fluxer_gateway, runtime_config}).
is_clustered_true_when_flag_and_multiple_members_test() ->
persistent_term:put({fluxer_gateway, runtime_config}, #{cluster_enabled => true}),
persistent_term:put(
{gateway_cluster_membership, members},
[node(), 'peer@host']
),
?assertEqual(true, gateway_rollout_config:is_clustered()),
persistent_term:erase({gateway_cluster_membership, members}),
persistent_term:erase({fluxer_gateway, runtime_config}).
rollout_percentage_update_applies_atomically_across_concurrent_reads_test() ->
Config50 = (default_config())#{<<"session_rollout_percentage">> => 50},
persistent_term:put(?PERSISTENT_TERM_KEY, Config50),
Self = self(),
NumReaders = 20,
Pids = [
spawn(fun() ->
V = gateway_rollout_config:session_rollout_percentage(),
Self ! {rollout_read, self(), V}
end)
|| _ <- lists:seq(1, NumReaders)
],
Results = [
receive
{rollout_read, Pid, V} -> V
after 1000 -> timeout
end
|| Pid <- Pids
],
?assert(lists:all(fun(V) -> V =:= 50 end, Results)),
persistent_term:put(?PERSISTENT_TERM_KEY, default_config()).
nats_payload_updates_config_and_notifies_subscribers_test() ->
persistent_term:put(?PERSISTENT_TERM_KEY, default_config()),
Config = (default_config())#{<<"session_rollout_percentage">> => 42},
Payload = iolist_to_binary(
json:encode(#{
<<"type">> => <<"gateway_rollout_config">>,
<<"config">> => Config
})
),
State = #{
subscribers => [{self(), make_ref()}],
nats_subscription => undefined,
nats_monitor => undefined
},
?assertMatch(
{noreply, State},
gateway_rollout_config:handle_info(
{nats_msg, <<"config.gateway.rollout">>, Payload, undefined},
State
)
),
?assertEqual(42, gateway_rollout_config:session_rollout_percentage()),
receive
rollout_config_changed -> ok
after 100 ->
?assert(false)
end,
persistent_term:put(?PERSISTENT_TERM_KEY, default_config()).
nats_duplicate_payload_does_not_notify_subscribers_test() ->
persistent_term:put(?PERSISTENT_TERM_KEY, default_config()),
Payload = iolist_to_binary(
json:encode(#{
<<"type">> => <<"gateway_rollout_config">>,
<<"config">> => default_config()
})
),
State = #{
subscribers => [{self(), make_ref()}],
nats_subscription => undefined,
nats_monitor => undefined
},
?assertMatch(
{noreply, State},
gateway_rollout_config:handle_info(
{nats_msg, <<"config.gateway.rollout">>, Payload, undefined},
State
)
),
receive
rollout_config_changed -> ?assert(false)
after 100 ->
ok
end.
validate_config_rejects_rpc_timeout_below_minimum_test() ->
?assertMatch(
{error, {invalid_field, <<"rpc_request_timeout_ms">>, 500}},
gateway_rollout_config_validate:validate(
#{<<"rpc_request_timeout_ms">> => 500},
default_config()
)
).
validate_config_rejects_rpc_timeout_above_maximum_test() ->
?assertMatch(
{error, {invalid_field, <<"rpc_request_timeout_ms">>, 61000}},
gateway_rollout_config_validate:validate(
#{<<"rpc_request_timeout_ms">> => 61000},
default_config()
)
).
validate_config_rejects_voice_reconciliation_v3_interval_below_minimum_test() ->
?assertMatch(
{error, {invalid_field, <<"voice_reconciliation_v3_interval_ms">>, 499}},
gateway_rollout_config_validate:validate(
#{<<"voice_reconciliation_v3_interval_ms">> => 499},
default_config()
)
).
validate_config_rejects_voice_reconciliation_v3_percentage_above_maximum_test() ->
?assertMatch(
{error, {invalid_field, <<"voice_reconciliation_v3_percentage">>, 101}},
gateway_rollout_config_validate:validate(
#{<<"voice_reconciliation_v3_percentage">> => 101},
default_config()
)
).