mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
274 lines
11 KiB
Erlang
274 lines
11 KiB
Erlang
%% SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
-module(channel_threads_config_tests).
|
|
-typing([eqwalizer]).
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
-define(KEY, channel_threads_config).
|
|
-define(PULLED_KEY, {channel_threads_config, pulled}).
|
|
|
|
config_fails_closed_when_nothing_is_stored_test() ->
|
|
with_stored(undefined, fun() ->
|
|
?assertEqual(channel_threads_config:default_config(), channel_threads_config:config()),
|
|
?assertEqual(0, channel_threads_config:version()),
|
|
?assertNot(channel_threads_config:ever_enabled()),
|
|
?assertNot(channel_threads_config:guild_active(1)),
|
|
?assertNot(channel_threads_config:user_active(1)),
|
|
?assertNot(channel_threads_config:user_excluded(1))
|
|
end).
|
|
|
|
validate_accepts_a_full_wire_config_test() ->
|
|
{ok, Config} = channel_threads_config:validate_config(full_wire()),
|
|
?assertMatch(
|
|
#{
|
|
enabled := true,
|
|
config_version := 7,
|
|
ever_enabled := true,
|
|
guild_basis_points := 250,
|
|
guild_salt := <<"guild salt">>,
|
|
user_basis_points := 10000,
|
|
user_salt := <<"user-salt">>
|
|
},
|
|
Config
|
|
),
|
|
?assertEqual(#{<<"10">> => true, <<"11">> => true}, maps:get(enabled_guilds, Config)),
|
|
?assertEqual(#{<<"12">> => true}, maps:get(disabled_guilds, Config)),
|
|
?assertEqual(#{<<"20">> => true}, maps:get(included_users, Config)),
|
|
?assertEqual(#{<<"21">> => true}, maps:get(excluded_users, Config)).
|
|
|
|
validate_fills_defaults_and_ignores_unknown_keys_test() ->
|
|
{ok, Config} = channel_threads_config:validate_config(#{
|
|
<<"enabled">> => true, <<"future_field">> => 1
|
|
}),
|
|
Default = channel_threads_config:default_config(),
|
|
?assertEqual(Default#{enabled := true}, Config),
|
|
?assertEqual(<<"channel-threads-guild-v1">>, maps:get(guild_salt, Config)),
|
|
?assertEqual(<<"channel-threads-user-v1">>, maps:get(user_salt, Config)).
|
|
|
|
validate_rejects_every_malformed_field_test() ->
|
|
Invalid = [
|
|
{<<"enabled">>, <<"true">>},
|
|
{<<"ever_enabled">>, 1},
|
|
{<<"config_version">>, -1},
|
|
{<<"config_version">>, 1.5},
|
|
{<<"guild_basis_points">>, 10001},
|
|
{<<"user_basis_points">>, -1},
|
|
{<<"guild_salt">>, <<>>},
|
|
{<<"user_salt">>, binary:copy(<<"a">>, 65)},
|
|
{<<"guild_salt">>, <<"caf", 16#c3, 16#a9>>},
|
|
{<<"user_salt">>, <<"tab\there">>},
|
|
{<<"enabled_guild_ids">>, <<"1">>},
|
|
{<<"disabled_guild_ids">>, [1]},
|
|
{<<"included_user_ids">>, [<<"12a">>]},
|
|
{<<"excluded_user_ids">>, [<<>>]},
|
|
{<<"included_user_ids">>, [binary:copy(<<"1">>, 21)]},
|
|
{<<"enabled_guild_ids">>, [integer_to_binary(N) || N <- lists:seq(1, 1001)]}
|
|
],
|
|
lists:foreach(
|
|
fun({Key, Value} = Case) ->
|
|
?assertMatch(
|
|
{Case, {error, {invalid_field, Key, _}}},
|
|
{Case, channel_threads_config:validate_config(#{Key => Value})}
|
|
)
|
|
end,
|
|
Invalid
|
|
).
|
|
|
|
validate_accepts_the_boundaries_test() ->
|
|
{ok, _} = channel_threads_config:validate_config(#{
|
|
<<"guild_basis_points">> => 10000,
|
|
<<"user_basis_points">> => 0,
|
|
<<"guild_salt">> => binary:copy(<<"~">>, 64),
|
|
<<"included_user_ids">> => [<<"18446744073709551615">>],
|
|
<<"disabled_guild_ids">> => [integer_to_binary(N) || N <- lists:seq(1, 1000)]
|
|
}).
|
|
|
|
guild_precedence_is_excluded_then_included_then_bucket_test() ->
|
|
Config = config(#{
|
|
<<"guild_basis_points">> => 10000,
|
|
<<"enabled_guild_ids">> => [<<"5">>],
|
|
<<"disabled_guild_ids">> => [<<"5">>, <<"6">>]
|
|
}),
|
|
?assertNot(channel_threads_config:guild_active(Config, 5)),
|
|
?assertNot(channel_threads_config:guild_active(Config, 6)),
|
|
?assert(channel_threads_config:guild_active(Config, 7)),
|
|
Included = config(#{<<"enabled_guild_ids">> => [<<"8">>]}),
|
|
?assert(channel_threads_config:guild_active(Included, 8)),
|
|
?assertNot(channel_threads_config:guild_active(Included, 9)).
|
|
|
|
guild_bucket_threshold_matches_the_shared_vectors_test() ->
|
|
?assertNot(
|
|
channel_threads_config:guild_active(config(#{<<"guild_basis_points">> => 6741}), 1)
|
|
),
|
|
?assert(
|
|
channel_threads_config:guild_active(config(#{<<"guild_basis_points">> => 6742}), 1)
|
|
),
|
|
?assertNot(
|
|
channel_threads_config:guild_active(config(#{<<"guild_basis_points">> => 9122}), 0)
|
|
),
|
|
?assert(
|
|
channel_threads_config:guild_active(config(#{<<"guild_basis_points">> => 9123}), 0)
|
|
).
|
|
|
|
user_bucket_threshold_matches_the_shared_vectors_test() ->
|
|
?assertNot(
|
|
channel_threads_config:user_active(config(#{<<"user_basis_points">> => 3403}), 1)
|
|
),
|
|
?assert(channel_threads_config:user_active(config(#{<<"user_basis_points">> => 3404}), 1)).
|
|
|
|
user_precedence_and_exclusion_test() ->
|
|
Config = config(#{
|
|
<<"user_basis_points">> => 10000,
|
|
<<"included_user_ids">> => [<<"1">>],
|
|
<<"excluded_user_ids">> => [<<"1">>, <<"2">>]
|
|
}),
|
|
?assertNot(channel_threads_config:user_active(Config, 1)),
|
|
?assertNot(channel_threads_config:user_active(Config, 2)),
|
|
?assert(channel_threads_config:user_active(Config, 3)),
|
|
?assert(channel_threads_config:user_excluded(Config, 2)),
|
|
?assertNot(channel_threads_config:user_excluded(Config, 3)).
|
|
|
|
disabled_config_is_inactive_for_everyone_test() ->
|
|
{ok, Config} = channel_threads_config:validate_config(
|
|
(full_wire())#{<<"enabled">> => false}
|
|
),
|
|
?assertNot(channel_threads_config:guild_active(Config, 10)),
|
|
?assertNot(channel_threads_config:user_active(Config, 20)),
|
|
?assert(channel_threads_config:user_excluded(Config, 21)).
|
|
|
|
zero_basis_points_never_buckets_in_test() ->
|
|
Config = config(#{}),
|
|
?assertEqual([], [
|
|
Id
|
|
|| Id <- lists:seq(1, 2000), channel_threads_config:guild_active(Config, Id)
|
|
]),
|
|
?assertEqual([], [
|
|
Id
|
|
|| Id <- lists:seq(1, 2000), channel_threads_config:user_active(Config, Id)
|
|
]).
|
|
|
|
store_keeps_the_version_monotonic_test() ->
|
|
with_stored(undefined, fun() ->
|
|
V5 = config(#{<<"config_version">> => 5}),
|
|
V4 = config(#{<<"config_version">> => 4, <<"enabled">> => false}),
|
|
Default = channel_threads_config:default_config(),
|
|
?assertEqual(updated, channel_threads_config:store_validated_config(Default, V5, nats)),
|
|
?assertEqual(5, channel_threads_config:version()),
|
|
?assertEqual(stale, channel_threads_config:store_validated_config(V5, V4, api)),
|
|
?assertEqual(5, channel_threads_config:version()),
|
|
?assert(maps:get(enabled, channel_threads_config:config())),
|
|
?assertEqual(unchanged, channel_threads_config:store_validated_config(V5, V5, api))
|
|
end).
|
|
|
|
store_keeps_ever_enabled_sticky_test() ->
|
|
with_stored(undefined, fun() ->
|
|
Enabled = config(#{<<"config_version">> => 1, <<"ever_enabled">> => true}),
|
|
Killed = config(#{
|
|
<<"config_version">> => 2, <<"enabled">> => false, <<"ever_enabled">> => false
|
|
}),
|
|
Default = channel_threads_config:default_config(),
|
|
updated = channel_threads_config:store_validated_config(Default, Enabled, api),
|
|
updated = channel_threads_config:store_validated_config(Enabled, Killed, api),
|
|
?assert(channel_threads_config:ever_enabled()),
|
|
?assertNot(maps:get(enabled, channel_threads_config:config())),
|
|
?assertEqual(
|
|
unchanged,
|
|
channel_threads_config:store_validated_config(
|
|
channel_threads_config:config(), Killed, api
|
|
)
|
|
)
|
|
end).
|
|
|
|
nats_payload_is_validated_before_it_is_stored_test() ->
|
|
with_stored(undefined, fun() ->
|
|
Payload = iolist_to_binary(
|
|
json:encode(#{
|
|
<<"type">> => <<"channel_threads_config">>,
|
|
<<"config">> => #{<<"enabled">> => true, <<"config_version">> => 3}
|
|
})
|
|
),
|
|
?assertEqual(updated, channel_threads_config:apply_nats_payload(Payload)),
|
|
?assertEqual(3, channel_threads_config:version()),
|
|
Bad = iolist_to_binary(
|
|
json:encode(#{
|
|
<<"config">> => #{<<"enabled">> => <<"yes">>, <<"config_version">> => 9}
|
|
})
|
|
),
|
|
?assertEqual(rejected, channel_threads_config:apply_nats_payload(Bad)),
|
|
?assertEqual(rejected, channel_threads_config:apply_nats_payload(<<"not json">>)),
|
|
?assertEqual(rejected, channel_threads_config:apply_nats_payload(<<"[1]">>)),
|
|
?assertEqual(3, channel_threads_config:version())
|
|
end).
|
|
|
|
a_default_pull_counts_as_loaded_test() ->
|
|
with_stored(undefined, fun() ->
|
|
?assertNot(channel_threads_config:loaded()),
|
|
Default = iolist_to_binary(
|
|
json:encode(#{<<"config">> => #{<<"enabled">> => false, <<"config_version">> => 0}})
|
|
),
|
|
?assertEqual(unchanged, channel_threads_config:apply_nats_payload(Default)),
|
|
?assertEqual(channel_threads_config:default_config(), channel_threads_config:config()),
|
|
?assert(channel_threads_config:loaded()),
|
|
?assertNot(channel_threads_config:enabled())
|
|
end).
|
|
|
|
a_rejected_pull_is_not_loaded_test() ->
|
|
with_stored(undefined, fun() ->
|
|
?assertEqual(rejected, channel_threads_config:apply_nats_payload(<<"not json">>)),
|
|
?assertNot(channel_threads_config:loaded())
|
|
end).
|
|
|
|
fields_changed_are_split_by_dimension_test() ->
|
|
Base = config(#{}),
|
|
GuildOnly = config(#{<<"enabled_guild_ids">> => [<<"1">>]}),
|
|
UserOnly = config(#{<<"user_basis_points">> => 5}),
|
|
VersionOnly = config(#{<<"config_version">> => 9}),
|
|
Disabled = config(#{<<"enabled">> => false}),
|
|
?assert(channel_threads_config:guild_fields_changed(Base, GuildOnly)),
|
|
?assertNot(channel_threads_config:user_fields_changed(Base, GuildOnly)),
|
|
?assert(channel_threads_config:user_fields_changed(Base, UserOnly)),
|
|
?assertNot(channel_threads_config:guild_fields_changed(Base, UserOnly)),
|
|
?assertNot(channel_threads_config:guild_fields_changed(Base, VersionOnly)),
|
|
?assertNot(channel_threads_config:user_fields_changed(Base, VersionOnly)),
|
|
?assert(channel_threads_config:guild_fields_changed(Base, Disabled)),
|
|
?assert(channel_threads_config:user_fields_changed(Base, Disabled)).
|
|
|
|
config(Overrides) ->
|
|
{ok, Config} = channel_threads_config:validate_config(
|
|
maps:merge(#{<<"enabled">> => true}, Overrides)
|
|
),
|
|
Config.
|
|
|
|
full_wire() ->
|
|
#{
|
|
<<"enabled">> => true,
|
|
<<"config_version">> => 7,
|
|
<<"ever_enabled">> => true,
|
|
<<"guild_basis_points">> => 250,
|
|
<<"guild_salt">> => <<"guild salt">>,
|
|
<<"enabled_guild_ids">> => [<<"10">>, <<"11">>],
|
|
<<"disabled_guild_ids">> => [<<"12">>],
|
|
<<"user_basis_points">> => 10000,
|
|
<<"user_salt">> => <<"user-salt">>,
|
|
<<"included_user_ids">> => [<<"20">>],
|
|
<<"excluded_user_ids">> => [<<"21">>]
|
|
}.
|
|
|
|
with_stored(Value, Fun) ->
|
|
Previous = persistent_term:get(?KEY, undefined),
|
|
PreviousPulled = persistent_term:get(?PULLED_KEY, undefined),
|
|
restore(?KEY, Value),
|
|
restore(?PULLED_KEY, undefined),
|
|
try
|
|
Fun()
|
|
after
|
|
restore(?KEY, Previous),
|
|
restore(?PULLED_KEY, PreviousPulled)
|
|
end.
|
|
|
|
restore(Key, undefined) ->
|
|
_ = persistent_term:erase(Key),
|
|
ok;
|
|
restore(Key, Value) ->
|
|
persistent_term:put(Key, Value).
|