mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
290 lines
11 KiB
Erlang
290 lines
11 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()
|
|
)
|
|
).
|
|
|
|
validate_config_rejects_relay_max_queue_zero_test() ->
|
|
?assertMatch(
|
|
{error, {invalid_field, <<"gateway_dispatch_relay_max_queue">>, 0}},
|
|
gateway_rollout_config_validate:validate(
|
|
#{<<"gateway_dispatch_relay_max_queue">> => 0},
|
|
default_config()
|
|
)
|
|
).
|
|
|
|
validate_config_rejects_relay_max_queue_above_kill_threshold_test() ->
|
|
Above = process_health_watchdog:kill_threshold() + 1,
|
|
?assertMatch(
|
|
{error, {invalid_field, <<"gateway_dispatch_relay_max_queue">>, Above}},
|
|
gateway_rollout_config_validate:validate(
|
|
#{<<"gateway_dispatch_relay_max_queue">> => Above},
|
|
default_config()
|
|
)
|
|
).
|
|
|
|
validate_config_accepts_relay_max_queue_range_bounds_test() ->
|
|
Ceiling = process_health_watchdog:kill_threshold(),
|
|
?assertMatch(
|
|
{ok, #{<<"gateway_dispatch_relay_max_queue">> := 1}},
|
|
gateway_rollout_config_validate:validate(
|
|
#{<<"gateway_dispatch_relay_max_queue">> => 1},
|
|
default_config()
|
|
)
|
|
),
|
|
?assertMatch(
|
|
{ok, #{<<"gateway_dispatch_relay_max_queue">> := Ceiling}},
|
|
gateway_rollout_config_validate:validate(
|
|
#{<<"gateway_dispatch_relay_max_queue">> => Ceiling},
|
|
default_config()
|
|
)
|
|
).
|