VEN-9: Adds support for versioned limit config object (#20)

* Adds explicit damsel dependency

* Bump dominant docker compose image (sha-e0afa44)

* Adds dominant-sourced config unmarshalling

* Adds operation `Limiter:GetVersioned/4`

* Refactor test suite and add testcase group with dominant-sourced config object use
This commit is contained in:
Aleksey Kashapov 2023-03-01 18:33:36 +03:00 committed by GitHub
parent d7febd557c
commit 3eff7ddf1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 491 additions and 166 deletions

View File

@ -2,6 +2,8 @@
-include_lib("limiter_proto/include/limproto_config_thrift.hrl").
-include_lib("limiter_proto/include/limproto_timerange_thrift.hrl").
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
-include_lib("damsel/include/dmsl_limiter_config_thrift.hrl").
-export([marshal/2]).
-export([unmarshal/2]).
@ -138,7 +140,35 @@ marshal_scope_type(payer_contact_email) ->
unmarshal(timestamped_change, TimestampedChange) ->
Timestamp = unmarshal_timestamp(TimestampedChange#config_TimestampedChange.occured_at),
Change = unmarshal_change(TimestampedChange#config_TimestampedChange.change),
{ev, Timestamp, Change}.
{ev, Timestamp, Change};
unmarshal('LimitConfigObject', #domain_LimitConfigObject{
ref = #domain_LimitConfigRef{id = ID},
data = #limiter_config_LimitConfig{
processor_type = ProcessorType,
created_at = CreatedAt,
started_at = StartedAt,
shard_size = ShardSize,
time_range_type = TimeRangeType,
context_type = ContextType,
type = Type,
scopes = Scopes,
description = Description,
op_behaviour = OpBehaviour
}
}) ->
genlib_map:compact(#{
id => ID,
processor_type => ProcessorType,
created_at => lim_time:from_rfc3339(CreatedAt),
started_at => StartedAt,
shard_size => ShardSize,
time_range_type => unmarshal_time_range_type(TimeRangeType),
context_type => unmarshal_context_type(ContextType),
type => maybe_apply(Type, fun unmarshal_type/1),
scope => maybe_apply(Scopes, fun unmarshal_scope/1),
description => Description,
op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1)
}).
unmarshal_timestamp(Timestamp) when is_binary(Timestamp) ->
try
@ -225,16 +255,22 @@ derive_type(undefined, {cash, Currency}) ->
{turnover, {amount, Currency}}.
-spec unmarshal_op_behaviour(encoded_value()) -> decoded_value().
unmarshal_op_behaviour(OpBehaviour) ->
#config_OperationLimitBehaviour{
invoice_payment_refund = Refund
} = OpBehaviour,
unmarshal_op_behaviour(#limiter_config_OperationLimitBehaviour{invoice_payment_refund = Refund}) ->
do_unmarshal_op_behaviour_refund(Refund);
unmarshal_op_behaviour(#config_OperationLimitBehaviour{invoice_payment_refund = Refund}) ->
do_unmarshal_op_behaviour_refund(Refund).
do_unmarshal_op_behaviour_refund(Refund) ->
genlib_map:compact(#{
invoice_payment_refund => maybe_apply(Refund, fun unmarshal_behaviour/1)
}).
unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) ->
subtraction;
unmarshal_behaviour({subtraction, #config_Subtraction{}}) ->
subtraction;
unmarshal_behaviour({addition, #limiter_config_Addition{}}) ->
addition;
unmarshal_behaviour({addition, #config_Addition{}}) ->
addition.
@ -244,6 +280,8 @@ unmarshal_body_type_deprecated({cash, #config_LimitBodyTypeCash{currency = Curre
unmarshal_time_range_type({calendar, CalendarType}) ->
{calendar, unmarshal_calendar_time_range_type(CalendarType)};
unmarshal_time_range_type({interval, #limiter_config_TimeRangeTypeInterval{amount = Amount}}) ->
{interval, Amount};
unmarshal_time_range_type({interval, #timerange_TimeRangeTypeInterval{amount = Amount}}) ->
{interval, Amount}.
@ -256,22 +294,32 @@ unmarshal_calendar_time_range_type({month, _}) ->
unmarshal_calendar_time_range_type({year, _}) ->
year.
unmarshal_context_type({payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}) ->
payment_processing;
unmarshal_context_type({payment_processing, #config_LimitContextTypePaymentProcessing{}}) ->
payment_processing;
unmarshal_context_type({withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}}) ->
withdrawal_processing;
unmarshal_context_type({withdrawal_processing, #config_LimitContextTypeWithdrawalProcessing{}}) ->
withdrawal_processing.
unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{metric = Metric}}) ->
{turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)};
unmarshal_type({turnover, #config_LimitTypeTurnover{metric = Metric}}) ->
{turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)}.
unmarshal_turnover_metric({number, _}) ->
number;
unmarshal_turnover_metric({amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}) ->
{amount, Currency};
unmarshal_turnover_metric({amount, #config_LimitTurnoverAmount{currency = Currency}}) ->
{amount, Currency}.
unmarshal_scope({single, Type}) ->
ordsets:from_list([unmarshal_scope_type(Type)]);
unmarshal_scope({multi, Types}) ->
ordsets:from_list(lists:map(fun unmarshal_scope_type/1, ordsets:to_list(Types)));
unmarshal_scope(Types) when is_list(Types) ->
ordsets:from_list(lists:map(fun unmarshal_scope_type/1, ordsets:to_list(Types))).
unmarshal_scope_type({party, _}) ->
@ -360,4 +408,38 @@ unmarshal_created_w_deprecated_body_type_test_() ->
)
].
-spec unmarshal_config_object_test() -> _.
unmarshal_config_object_test() ->
CreatedAt = lim_time:now(),
Config = #{
id => <<"id">>,
processor_type => <<"type">>,
created_at => CreatedAt,
started_at => <<"2000-01-01T00:00:00Z">>,
shard_size => 7,
time_range_type => {calendar, day},
context_type => payment_processing,
type => {turnover, number},
scope => ordsets:from_list([party, shop]),
description => <<"description">>
},
Object = #domain_LimitConfigObject{
ref = #domain_LimitConfigRef{id = <<"id">>},
data = #limiter_config_LimitConfig{
processor_type = <<"type">>,
created_at = lim_time:to_rfc3339(CreatedAt),
started_at = <<"2000-01-01T00:00:00Z">>,
shard_size = 7,
time_range_type = {calendar, {day, #limiter_config_TimeRangeTypeCalendarDay{}}},
context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}},
type =
{turnover, #limiter_config_LimitTypeTurnover{metric = {number, #limiter_config_LimitTurnoverNumber{}}}},
scopes = ordsets:from_list([
{'party', #limiter_config_LimitScopeEmptyDetails{}}, {'shop', #limiter_config_LimitScopeEmptyDetails{}}
]),
description = <<"description">>
}
},
?assertEqual(Config, unmarshal('LimitConfigObject', Object)).
-endif.

View File

@ -1,6 +1,8 @@
-module(lim_config_machine).
-include_lib("limiter_proto/include/limproto_limiter_thrift.hrl").
-include_lib("damsel/include/dmsl_domain_thrift.hrl").
-include_lib("damsel/include/dmsl_domain_conf_thrift.hrl").
%% Accessors
@ -21,7 +23,7 @@
-export([start/3]).
-export([get/2]).
-export([get_limit/2]).
-export([get_limit/3]).
-export([hold/2]).
-export([commit/2]).
-export([rollback/2]).
@ -80,6 +82,7 @@
-type operation_type() :: invoice_payment_refund.
-type lim_id() :: limproto_limiter_thrift:'LimitID'().
-type lim_version() :: dmsl_domain_thrift:'DataRevision'() | undefined.
-type lim_change() :: limproto_limiter_thrift:'LimitChange'().
-type limit() :: limproto_limiter_thrift:'Limit'().
-type timestamp() :: dmsl_base_thrift:'Timestamp'().
@ -232,41 +235,56 @@ get(ID, LimitContext) ->
collapse(Machine)
end).
-spec get_limit(lim_id(), lim_context()) -> {ok, limit()} | {error, config_error() | {processor(), get_limit_error()}}.
get_limit(ID, LimitContext) ->
-spec get_limit(lim_id(), lim_version(), lim_context()) ->
{ok, limit()} | {error, config_error() | {processor(), get_limit_error()}}.
get_limit(ID, Version, LimitContext) ->
do(fun() ->
{Handler, Config} = unwrap(get_handler(ID, LimitContext)),
{Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)),
unwrap(Handler, Handler:get_limit(ID, Config, LimitContext))
end).
-spec hold(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), hold_error()}}.
hold(LimitChange = #limiter_LimitChange{id = ID}, LimitContext) ->
hold(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitContext) ->
do(fun() ->
{Handler, Config} = unwrap(get_handler(ID, LimitContext)),
{Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)),
unwrap(Handler, Handler:hold(LimitChange, Config, LimitContext))
end).
-spec commit(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), commit_error()}}.
commit(LimitChange = #limiter_LimitChange{id = ID}, LimitContext) ->
commit(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitContext) ->
do(fun() ->
{Handler, Config} = unwrap(get_handler(ID, LimitContext)),
{Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)),
unwrap(Handler, Handler:commit(LimitChange, Config, LimitContext))
end).
-spec rollback(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), rollback_error()}}.
rollback(LimitChange = #limiter_LimitChange{id = ID}, LimitContext) ->
rollback(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitContext) ->
do(fun() ->
{Handler, Config} = unwrap(get_handler(ID, LimitContext)),
{Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)),
unwrap(Handler, Handler:rollback(LimitChange, Config, LimitContext))
end).
get_handler(ID, LimitContext) ->
get_handler(ID, Version, LimitContext) ->
do(fun() ->
Config = #{processor_type := ProcessorType} = unwrap(config, get(ID, LimitContext)),
Config = #{processor_type := ProcessorType} = unwrap(config, get_config(ID, Version, LimitContext)),
{ok, Handler} = lim_router:get_handler(ProcessorType),
{Handler, Config}
end).
-spec get_config(lim_id(), lim_version(), lim_context()) -> {ok, config()} | {error, notfound}.
get_config(ID, undefined, LimitContext) ->
get(ID, LimitContext);
get_config(ID, Version, #{woody_context := WoodyContext}) ->
LimitConfigRef = {limit_config, #domain_LimitConfigRef{id = ID}},
try
Object = dmt_client:checkout_versioned_object(Version, LimitConfigRef, #{woody_context => WoodyContext}),
#domain_conf_VersionedObject{object = {limit_config, ConfigObject}} = Object,
{ok, lim_config_codec:unmarshal('LimitConfigObject', ConfigObject)}
catch
throw:#domain_conf_ObjectNotFound{} ->
{error, notfound}
end.
-spec calculate_time_range(timestamp(), config()) -> time_range().
calculate_time_range(Timestamp, Config) ->
StartedAt = started_at(Config),

View File

@ -27,11 +27,14 @@ handle_function(Fn, Args, WoodyCtx, Opts) ->
).
-spec handle_function_(woody:func(), woody:args(), lim_context(), woody:options()) -> {ok, woody:result()}.
handle_function_('Get', {LimitID, Clock, Context}, LimitContext, _Opts) ->
handle_function_('Get', {LimitID, Clock, Context}, LimitContext, Opts) ->
handle_function_('GetVersioned', {LimitID, undefined, Clock, Context}, LimitContext, Opts);
handle_function_('GetVersioned', {LimitID, Version, Clock, Context}, LimitContext, _Opts) ->
scoper:add_meta(#{limit_id => LimitID}),
case
lim_config_machine:get_limit(
LimitID,
Version,
lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext))
)
of

View File

@ -10,7 +10,8 @@
machinery,
woody,
scoper, % should be before any scoper event handler usage
erl_health
erl_health,
dmt_client
]},
{mod, {limiter, []}},
{env, []}

View File

@ -3,7 +3,7 @@
-include_lib("limiter_proto/include/limproto_limiter_thrift.hrl").
-export([new/0]).
-export([get/3]).
-export([get/4]).
-export([hold/3]).
-export([commit/3]).
-export([rollback/3]).
@ -15,6 +15,7 @@
-type client() :: woody_context:ctx().
-type limit_id() :: limproto_limiter_thrift:'LimitID'().
-type limit_version() :: limproto_limiter_thrift:'Version'() | undefined.
-type limit_change() :: limproto_limiter_thrift:'LimitChange'().
-type limit_context() :: limproto_limiter_thrift:'LimitContext'().
-type clock() :: limproto_limiter_thrift:'Clock'().
@ -27,9 +28,11 @@
new() ->
woody_context:new().
-spec get(limit_id(), limit_context(), client()) -> woody:result() | no_return().
get(LimitID, Context, Client) ->
call('Get', {LimitID, clock(), Context}, Client).
-spec get(limit_id(), limit_version(), limit_context(), client()) -> woody:result() | no_return().
get(LimitID, undefined, Context, Client) ->
call('Get', {LimitID, clock(), Context}, Client);
get(LimitID, Version, Context, Client) ->
call('GetVersioned', {LimitID, Version, clock(), Context}, Client).
-spec hold(limit_change(), limit_context(), client()) -> woody:result() | no_return().
hold(LimitChange, Context, Client) ->

View File

@ -51,16 +51,38 @@ init_per_suite(Config) ->
% dbg:tracer(), dbg:p(all, c),
% dbg:tpl({machinery, '_', '_'}, x),
Apps =
genlib_app:start_application_with(limiter, [
{service_clients, #{
accounter => #{
url => <<"http://shumway:8022/accounter">>
},
automaton => #{
url => <<"http://machinegun:8022/v1/automaton">>
}
genlib_app:start_application_with(dmt_client, [
% milliseconds
{cache_update_interval, 5000},
{max_cache_size, #{
elements => 20,
% 50Mb
memory => 52428800
}},
{woody_event_handlers, [
{scoper_woody_event_handler, #{
event_handler_opts => #{
formatter_opts => #{
max_length => 1000
}
}
}}
]},
{service_urls, #{
'Repository' => <<"http://dominant:8022/v1/domain/repository">>,
'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">>
}}
]),
]) ++
genlib_app:start_application_with(limiter, [
{service_clients, #{
accounter => #{
url => <<"http://shumway:8022/accounter">>
},
automaton => #{
url => <<"http://machinegun:8022/v1/automaton">>
}
}}
]),
[{apps, Apps}] ++ Config.
-spec end_per_suite(config()) -> _.

View File

@ -3,6 +3,7 @@
-include_lib("stdlib/include/assert.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("damsel/include/dmsl_base_thrift.hrl").
-include_lib("damsel/include/dmsl_limiter_config_thrift.hrl").
-include("lim_ct_helper.hrl").
-export([all/0]).
@ -10,6 +11,8 @@
-export([groups/0]).
-export([init_per_suite/1]).
-export([end_per_suite/1]).
-export([init_per_group/2]).
-export([end_per_group/2]).
-export([init_per_testcase/2]).
-export([end_per_testcase/2]).
@ -59,6 +62,7 @@
all() ->
[
{group, default},
{group, default_with_dominant},
{group, withdrawals},
{group, cashless},
{group, idempotency}
@ -91,6 +95,29 @@ groups() ->
commit_with_email_scope_ok,
commit_with_multi_scope_ok
]},
%% Repeats `default` group exept for `get_config_ok` and `commit_multirange_limit_ok`
{default_with_dominant, [], [
commit_with_long_change_id,
commit_with_default_exchange,
partial_commit_with_exchange,
commit_with_exchange,
commit_with_disabled_exchange,
get_limit_ok,
get_limit_notfound,
hold_ok,
commit_ok,
rollback_ok,
partial_zero_commit_rollbacks,
refund_ok,
commit_inexistent_hold_fails,
partial_commit_inexistent_hold_fails,
commit_with_payment_tool_scope_ok,
commit_with_party_scope_ok,
commit_with_provider_scope_ok,
commit_with_terminal_scope_ok,
commit_with_email_scope_ok,
commit_with_multi_scope_ok
]},
{withdrawals, [parallel], [
get_limit_ok,
hold_ok,
@ -123,30 +150,65 @@ init_per_suite(Config) ->
% dbg:tracer(), dbg:p(all, c),
% dbg:tpl({lim_handler, '_', '_'}, x),
Apps =
genlib_app:start_application_with(limiter, [
{service_clients, #{
accounter => #{
url => <<"http://shumway:8022/accounter">>
},
automaton => #{
url => <<"http://machinegun:8022/v1/automaton">>
},
xrates => #{
url => <<"http://xrates:8022/xrates">>
}
genlib_app:start_application_with(dmt_client, [
% milliseconds
{cache_update_interval, 5000},
{max_cache_size, #{
elements => 20,
% 50Mb
memory => 52428800
}},
{exchange_factors, #{
<<"DEFAULT">> => {1, 1},
<<"USD">> => {105, 100},
<<"EUR">> => {12, 10}
{woody_event_handlers, [
{scoper_woody_event_handler, #{
event_handler_opts => #{
formatter_opts => #{
max_length => 1000
}
}
}}
]},
{service_urls, #{
'Repository' => <<"http://dominant:8022/v1/domain/repository">>,
'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">>
}}
]),
]) ++
genlib_app:start_application_with(limiter, [
{service_clients, #{
accounter => #{
url => <<"http://shumway:8022/accounter">>
},
automaton => #{
url => <<"http://machinegun:8022/v1/automaton">>
},
xrates => #{
url => <<"http://xrates:8022/xrates">>
}
}},
{exchange_factors, #{
<<"DEFAULT">> => {1, 1},
<<"USD">> => {105, 100},
<<"EUR">> => {12, 10}
}}
]),
[{apps, Apps}] ++ Config.
-spec end_per_suite(config()) -> _.
end_per_suite(Config) ->
genlib_app:test_application_stop(?config(apps, Config)).
-spec init_per_group(test_case_name(), config()) -> config().
init_per_group(default_with_dominant, C) ->
set_limit_config_source(repository, C);
init_per_group(_Name, C) ->
set_limit_config_source(legacy, C).
set_limit_config_source(ConfigSource, C) ->
[{limit_config_source, ConfigSource} | C].
-spec end_per_group(test_case_name(), config()) -> ok.
end_per_group(_Name, _C) ->
ok.
-spec init_per_testcase(test_case_name(), config()) -> config().
init_per_testcase(Name, C) ->
[
@ -164,12 +226,16 @@ end_per_testcase(_Name, C) ->
%%
-define(CHANGE_ID, 42).
-define(LIMIT_CHANGE(ID), ?LIMIT_CHANGE(ID, ?CHANGE_ID)).
-define(LIMIT_CHANGE(ID, ChangeID), #limiter_LimitChange{id = ID, change_id = gen_change_id(ID, ChangeID)}).
-define(LIMIT_CHANGE(ID, ChangeID), ?LIMIT_CHANGE(ID, ChangeID, undefined)).
-define(LIMIT_CHANGE(ID, ChangeID, Version), #limiter_LimitChange{
id = ID,
change_id = gen_change_id(ID, ChangeID),
version = Version
}).
-spec commit_with_long_change_id(config()) -> _.
commit_with_long_change_id(C) ->
ID = configure_limit(?time_range_month(), ?global(), C),
{ID, Version} = configure_limit(?time_range_month(), ?global(), C),
Context = ?payproc_ctx_invoice(?cash(10, <<"RUB">>)),
LongBinary =
<<
@ -177,115 +243,122 @@ commit_with_long_change_id(C) ->
" BinaryLongBinaryLongBinaryLongBinaryLongBinaryLongBinary"
>>,
ChangeID = <<LongBinary/binary, LongBinary/binary, LongBinary/binary, LongBinary/binary, LongBinary/binary>>,
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ChangeID), Context, ?config(client, C)),
{ok, #limiter_Limit{amount = 10}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ChangeID, Version), Context, ?config(client, C)),
{ok, #limiter_Limit{amount = 10}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec commit_with_default_exchange(config()) -> _.
commit_with_default_exchange(C) ->
ok = application:set_env(limiter, currency_conversion, enabled),
Rational = #base_Rational{p = 1000000, q = 100},
_ = mock_exchange(Rational, C),
ID = configure_limit(?time_range_month(), ?global(), C),
{ID, Version} = configure_limit(?time_range_month(), ?global(), C),
Cost = ?cash(10000, <<"SOME_CURRENCY">>),
Context = ?payproc_ctx_invoice(Cost),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
{ok, #limiter_Limit{amount = 10000}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)),
{ok, #limiter_Limit{amount = 10000}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec partial_commit_with_exchange(config()) -> _.
partial_commit_with_exchange(C) ->
ok = application:set_env(limiter, currency_conversion, enabled),
Rational = #base_Rational{p = 800000, q = 100},
_ = mock_exchange(Rational, C),
ID = configure_limit(?time_range_month(), ?global(), C),
{ID, Version} = configure_limit(?time_range_month(), ?global(), C),
Cost = ?cash(1000, <<"USD">>),
CaptureCost = ?cash(800, <<"USD">>),
Context = ?payproc_ctx_payment(Cost, CaptureCost),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
{ok, #limiter_Limit{amount = 8400}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)),
{ok, #limiter_Limit{amount = 8400}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec commit_with_exchange(config()) -> _.
commit_with_exchange(C) ->
ok = application:set_env(limiter, currency_conversion, enabled),
Rational = #base_Rational{p = 1000000, q = 100},
_ = mock_exchange(Rational, C),
ID = configure_limit(?time_range_month(), ?global(), C),
{ID, Version} = configure_limit(?time_range_month(), ?global(), C),
Cost = ?cash(10000, <<"USD">>),
Context = ?payproc_ctx_invoice(Cost),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
{ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)),
{ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec commit_with_disabled_exchange(config()) -> _.
commit_with_disabled_exchange(C) ->
ok = application:set_env(limiter, currency_conversion, disabled),
Rational = #base_Rational{p = 1000000, q = 100},
_ = mock_exchange(Rational, C),
ID = configure_limit(?time_range_month(), ?global(), C),
{ID, Version} = configure_limit(?time_range_month(), ?global(), C),
Cost = ?cash(10000, <<"USD">>),
Context = ?payproc_ctx_invoice(Cost),
{exception, #base_InvalidRequest{}} =
lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)).
lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)).
-spec get_limit_ok(config()) -> _.
get_limit_ok(C) ->
ID = configure_limit(?time_range_month(), ?global(), C),
{ID, Version} = configure_limit(?time_range_month(), ?global(), C),
Context =
case get_group_name(C) of
default -> ?payproc_ctx_invoice(?cash(0));
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(0))
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(0));
_Default -> ?payproc_ctx_invoice(?cash(0))
end,
?assertMatch(
{ok, #limiter_Limit{amount = 0}},
lim_client:get(ID, Context, ?config(client, C))
lim_client:get(ID, Version, Context, ?config(client, C))
).
-spec get_limit_notfound(config()) -> _.
get_limit_notfound(C) ->
Version =
case proplists:get_value(limit_config_source, C, legacy) of
legacy -> undefined;
repository -> 0
end,
Context = ?payproc_ctx_invoice(?cash(0)),
?assertEqual(
{exception, #limiter_LimitNotFound{}},
lim_client:get(<<"NOSUCHLIMITID">>, Context, ?config(client, C))
lim_client:get(<<"NOSUCHLIMITID">>, Version, Context, ?config(client, C))
).
-spec hold_ok(config()) -> _.
hold_ok(C) ->
ID = configure_limit(?time_range_month(), ?global(), C),
{ID, Version} = configure_limit(?time_range_month(), ?global(), C),
Context =
case get_group_name(C) of
default -> ?payproc_ctx_invoice(?cash(10));
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10))
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10));
_Default -> ?payproc_ctx_invoice(?cash(10))
end,
{ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(
?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)
),
{ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec commit_ok(config()) -> _.
commit_ok(C) ->
ID = configure_limit(?time_range_month(), ?global(), C),
{ID, Version} = configure_limit(?time_range_month(), ?global(), C),
Context =
case get_group_name(C) of
default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>));
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>))
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>));
_Default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>))
end,
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec rollback_ok(config()) -> _.
rollback_ok(C) ->
ID = configure_limit(?time_range_week(), ?global(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), C),
Context =
case get_group_name(C) of
default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>));
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>))
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>));
_Default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>))
end,
Change = ?LIMIT_CHANGE(ID),
Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version),
{ok, {vector, _}} = lim_client:hold(Change, Context, ?config(client, C)),
{ok, {vector, _}} = lim_client:rollback(Change, Context, ?config(client, C)).
-spec partial_zero_commit_rollbacks(config()) -> _.
partial_zero_commit_rollbacks(C) ->
ID = configure_limit(?time_range_week(), ?global(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), C),
Context0 = ?payproc_ctx_payment(?cash(10), ?cash(10)),
Context1 = ?payproc_ctx_payment(?cash(10), ?cash(0)),
Change = ?LIMIT_CHANGE(ID),
Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version),
{ok, {vector, _}} = lim_client:hold(Change, Context0, ?config(client, C)),
{ok, {vector, _}} = lim_client:commit(Change, Context1, ?config(client, C)),
% NOTE
@ -299,38 +372,38 @@ refund_ok(C) ->
Client = ?config(client, C),
OwnerID = <<"WWWcool Ltd">>,
ShopID = <<"shop">>,
ID = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C),
{ID, Version} = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C),
Context0 = ?payproc_ctx_payment(OwnerID, ShopID, ?cash(15), ?cash(15)),
RefundContext1 = ?payproc_ctx_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>), Context0, Client),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Refund">>), RefundContext1, Client),
{ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, RefundContext1, Client),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>, Version), Context0, Client),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Refund">>, Version), RefundContext1, Client),
{ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, Version, RefundContext1, Client),
?assertEqual(Limit2#limiter_Limit.amount, 5).
-spec get_config_ok(config()) -> _.
get_config_ok(C) ->
ID = configure_limit(?time_range_week(), ?global(), C),
{ID, _Version} = configure_limit(?time_range_week(), ?global(), C),
{ok, #config_LimitConfig{}} = lim_client:get_config(ID, ?config(client, C)).
-spec commit_inexistent_hold_fails(config()) -> _.
commit_inexistent_hold_fails(C) ->
ID = configure_limit(?time_range_week(), ?global(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), C),
Context = ?payproc_ctx_payment(?cash(42), undefined),
% NOTE
% We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter
% before requesting him to hold / commit.
{exception, #base_InvalidRequest{}} =
lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)).
lim_client:commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)).
-spec partial_commit_inexistent_hold_fails(config()) -> _.
partial_commit_inexistent_hold_fails(C) ->
ID = configure_limit(?time_range_week(), ?global(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), C),
Context = ?payproc_ctx_payment(?cash(42), ?cash(21)),
% NOTE
% We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter
% before requesting him to hold / commit.
{exception, #base_InvalidRequest{}} =
lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)).
lim_client:commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)).
-spec commit_multirange_limit_ok(config()) -> _.
commit_multirange_limit_ok(C) ->
@ -356,14 +429,14 @@ commit_multirange_limit_ok(C) ->
{ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 2), ?payproc_ctx_payment(PaymentFeb), Client),
PaymentApr = ?invoice_payment(?cash(44), ?cash(44), ?bank_card(), <<"2020-04-01T00:00:00Z">>),
{ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 3), ?payproc_ctx_payment(PaymentApr), Client),
{ok, #limiter_Limit{amount = 42}} = lim_client:get(ID, ?payproc_ctx_payment(PaymentJan), Client),
{ok, #limiter_Limit{amount = 43}} = lim_client:get(ID, ?payproc_ctx_payment(PaymentFeb), Client),
{ok, #limiter_Limit{amount = 44}} = lim_client:get(ID, ?payproc_ctx_payment(PaymentApr), Client).
{ok, #limiter_Limit{amount = 42}} = lim_client:get(ID, undefined, ?payproc_ctx_payment(PaymentJan), Client),
{ok, #limiter_Limit{amount = 43}} = lim_client:get(ID, undefined, ?payproc_ctx_payment(PaymentFeb), Client),
{ok, #limiter_Limit{amount = 44}} = lim_client:get(ID, undefined, ?payproc_ctx_payment(PaymentApr), Client).
-spec commit_with_payment_tool_scope_ok(config()) -> _.
commit_with_payment_tool_scope_ok(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C),
{ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C),
Context1 = ?payproc_ctx_payment(
?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"Token">>, 2, 2022))
),
@ -379,13 +452,13 @@ commit_with_payment_tool_scope_ok(C) ->
Context5 = ?payproc_ctx_payment(
?invoice_payment(?cash(10), ?cash(10), ?digital_wallet(<<"ID42">>, <<"Pepal">>))
),
{ok, LimitState0} = lim_client:get(ID, Context1, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 1), Context1, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 2), Context2, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 3), Context3, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 4), Context4, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 5), Context5, Client),
{ok, LimitState1} = lim_client:get(ID, Context1, Client),
{ok, LimitState0} = lim_client:get(ID, Version, Context1, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 1, Version), Context1, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 2, Version), Context2, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 3, Version), Context3, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 4, Version), Context4, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 5, Version), Context5, Client),
{ok, LimitState1} = lim_client:get(ID, Version, Context1, Client),
?assertEqual(
LimitState1#limiter_Limit.amount,
LimitState0#limiter_Limit.amount + 1
@ -396,66 +469,66 @@ commit_with_payment_tool_scope_ok(C) ->
-spec commit_processes_idempotently(config()) -> _.
commit_processes_idempotently(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?global(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), C),
Context = ?payproc_ctx_payment(?cash(42), undefined),
Change = ?LIMIT_CHANGE(ID),
Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version),
{ok, _} = lim_client:hold(Change, Context, Client),
{ok, _} = lim_client:hold(Change, Context, Client),
{ok, _} = lim_client:commit(Change, Context, Client),
{ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Context, Client),
{ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Version, Context, Client),
{ok, _} = lim_client:commit(Change, Context, Client),
{ok, Limit} = lim_client:get(ID, Context, Client).
{ok, Limit} = lim_client:get(ID, Version, Context, Client).
-spec full_commit_processes_idempotently(config()) -> _.
full_commit_processes_idempotently(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?global(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), C),
Cost = ?cash(42),
Context = ?payproc_ctx_payment(Cost, Cost),
Change = ?LIMIT_CHANGE(ID),
Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version),
{ok, _} = lim_client:hold(Change, Context, Client),
{ok, _} = lim_client:hold(Change, Context, Client),
{ok, _} = lim_client:commit(Change, Context, Client),
{ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Context, Client),
{ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Version, Context, Client),
{ok, _} = lim_client:commit(Change, Context, Client),
{ok, Limit} = lim_client:get(ID, Context, Client).
{ok, Limit} = lim_client:get(ID, Version, Context, Client).
-spec partial_commit_processes_idempotently(config()) -> _.
partial_commit_processes_idempotently(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?global(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), C),
Context = ?payproc_ctx_payment(?cash(42), ?cash(40)),
Change = ?LIMIT_CHANGE(ID),
Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version),
{ok, _} = lim_client:hold(Change, Context, Client),
{ok, _} = lim_client:hold(Change, Context, Client),
{ok, _} = lim_client:commit(Change, Context, Client),
{ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Context, Client),
{ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Version, Context, Client),
{ok, _} = lim_client:commit(Change, Context, Client),
{ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Context, Client).
{ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Version, Context, Client).
-spec rollback_processes_idempotently(config()) -> _.
rollback_processes_idempotently(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?global(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), C),
Context = ?payproc_ctx_payment(?cash(42), ?cash(0)),
Change = ?LIMIT_CHANGE(ID),
Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version),
{ok, _} = lim_client:hold(Change, Context, Client),
{ok, _} = lim_client:hold(Change, Context, Client),
{ok, _} = lim_client:commit(Change, Context, Client),
{ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Context, Client),
{ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client),
{ok, _} = lim_client:commit(Change, Context, Client),
{ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Context, Client).
{ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client).
%%
-spec commit_number_ok(config()) -> _.
commit_number_ok(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
Context = ?payproc_ctx_payment(?cash(10), ?cash(10)),
{ok, LimitState0} = lim_client:get(ID, Context, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID), Context, Client),
{ok, LimitState1} = lim_client:get(ID, Context, Client),
{ok, LimitState0} = lim_client:get(ID, Version, Context, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, Client),
{ok, LimitState1} = lim_client:get(ID, Version, Context, Client),
?assertEqual(
LimitState1#limiter_Limit.amount,
LimitState0#limiter_Limit.amount + 1
@ -464,12 +537,12 @@ commit_number_ok(C) ->
-spec rollback_number_ok(config()) -> _.
rollback_number_ok(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
Context = ?payproc_ctx_payment(?cash(10), ?cash(10)),
ContextRollback = ?payproc_ctx_payment(?cash(10), ?cash(0)),
{ok, LimitState0} = lim_client:get(ID, Context, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextRollback, Client),
{ok, LimitState1} = lim_client:get(ID, Context, Client),
{ok, LimitState0} = lim_client:get(ID, Version, Context, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ContextRollback, Client),
{ok, LimitState1} = lim_client:get(ID, Version, Context, Client),
?assertEqual(
LimitState1#limiter_Limit.amount,
LimitState0#limiter_Limit.amount
@ -478,16 +551,16 @@ rollback_number_ok(C) ->
-spec commit_refund_keep_number_unchanged(config()) -> _.
commit_refund_keep_number_unchanged(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
Cost = ?cash(10),
CaptureCost = ?cash(8),
RefundCost = ?cash(5),
PaymentContext = ?payproc_ctx_payment(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost),
RefundContext = ?payproc_ctx_refund(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost, RefundCost),
{ok, LimitState0} = lim_client:get(ID, PaymentContext, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 1), PaymentContext, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 2), RefundContext, Client),
{ok, LimitState1} = lim_client:get(ID, PaymentContext, Client),
{ok, LimitState0} = lim_client:get(ID, Version, PaymentContext, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 1, Version), PaymentContext, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 2, Version), RefundContext, Client),
{ok, LimitState1} = lim_client:get(ID, Version, PaymentContext, Client),
?assertEqual(
% Expected to be the same because refund decreases counter given limit config
LimitState1#limiter_Limit.amount,
@ -497,12 +570,12 @@ commit_refund_keep_number_unchanged(C) ->
-spec partial_commit_number_counts_as_single_op(config()) -> _.
partial_commit_number_counts_as_single_op(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
{ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C),
Context = ?payproc_ctx_payment(?cash(10), ?cash(10)),
ContextPartial = ?payproc_ctx_payment(?cash(10), ?cash(5)),
{ok, LimitState0} = lim_client:get(ID, Context, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextPartial, Client),
{ok, LimitState1} = lim_client:get(ID, Context, Client),
{ok, LimitState0} = lim_client:get(ID, Version, Context, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ContextPartial, Client),
{ok, LimitState1} = lim_client:get(ID, Version, Context, Client),
?assertEqual(
LimitState1#limiter_Limit.amount,
LimitState0#limiter_Limit.amount + 1
@ -523,40 +596,40 @@ commit_with_terminal_scope_ok(C) ->
_ = commit_with_some_scope(?scope([?scope_terminal()]), C).
commit_with_some_scope(Scope, C) ->
ID = configure_limit(?time_range_month(), Scope, C),
{ID, Version} = configure_limit(?time_range_month(), Scope, C),
Context =
case get_group_name(C) of
default -> ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>));
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>))
withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>));
_Default -> ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>))
end,
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec commit_with_email_scope_ok(config()) -> _.
commit_with_email_scope_ok(C) ->
ID = configure_limit(?time_range_month(), ?scope([?scope_payer_contact_email()]), C),
{ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_payer_contact_email()]), C),
Context = ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec commit_with_identity_scope_ok(config()) -> _.
commit_with_identity_scope_ok(C) ->
ID = configure_limit(?time_range_month(), ?scope([?scope_identity()]), C),
{ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_identity()]), C),
Context = ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec commit_with_wallet_scope_ok(config()) -> _.
commit_with_wallet_scope_ok(C) ->
ID = configure_limit(?time_range_month(), ?scope([?scope_wallet()]), C),
{ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_wallet()]), C),
Context = ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)),
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)).
{ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)),
{ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)).
-spec commit_with_multi_scope_ok(config()) -> _.
commit_with_multi_scope_ok(C) ->
Client = ?config(client, C),
ID = configure_limit(?time_range_week(), ?scope([?scope_provider(), ?scope_payment_tool()]), C),
{ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_provider(), ?scope_payment_tool()]), C),
Context1 = ?payproc_ctx_payment(
?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"Token">>, 2, 2022))
),
@ -572,13 +645,13 @@ commit_with_multi_scope_ok(C) ->
Context5 = ?payproc_ctx_payment(
?invoice_payment(?cash(10), ?cash(10), ?digital_wallet(<<"ID42">>, <<"Pepal">>))
),
{ok, LimitState0} = lim_client:get(ID, Context1, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 1), Context1, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 2), Context2, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 3), Context3, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 4), Context4, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 5), Context5, Client),
{ok, LimitState1} = lim_client:get(ID, Context1, Client),
{ok, LimitState0} = lim_client:get(ID, Version, Context1, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 1, Version), Context1, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 2, Version), Context2, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 3, Version), Context3, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 4, Version), Context4, Client),
_ = hold_and_commit(?LIMIT_CHANGE(ID, 5, Version), Context5, Client),
{ok, LimitState1} = lim_client:get(ID, Version, Context1, Client),
?assertEqual(
LimitState1#limiter_Limit.amount,
LimitState0#limiter_Limit.amount + 10
@ -609,7 +682,7 @@ configure_limit(TimeRange, Scope, Metric, C) ->
withdrawals -> ?ctx_type_wthdproc();
_Default -> ?ctx_type_payproc()
end,
Params = #config_LimitConfigParams{
CreateParams = #config_LimitConfigParams{
id = ID,
started_at = <<"2000-01-01T00:00:00Z">>,
time_range_type = TimeRange,
@ -619,8 +692,16 @@ configure_limit(TimeRange, Scope, Metric, C) ->
context_type = ContextType,
op_behaviour = ?op_behaviour(?op_subtraction())
},
{ok, _LimitConfig} = lim_client:create_config(Params, ?config(client, C)),
ID.
ConfigSource = proplists:get_value(limit_config_source, C, legacy),
put_config_into_repository(ConfigSource, CreateParams, ?config(client, C)).
put_config_into_repository(legacy, CreateParams = #config_LimitConfigParams{id = ID}, Client) ->
{ok, _LimitConfig} = lim_client:create_config(CreateParams, Client),
{ID, undefined};
put_config_into_repository(repository, CreateParams = #config_LimitConfigParams{id = ID}, _Client) ->
LimitConfigObject = mk_limit_config_object(CreateParams),
Version = dmt_client:insert({limit_config, LimitConfigObject}),
{ID, Version}.
gen_unique_id(Prefix) ->
genlib:format("~s/~B", [Prefix, lim_time:now()]).
@ -628,3 +709,67 @@ gen_unique_id(Prefix) ->
get_group_name(C) ->
GroupProps = ?config(tc_group_properties, C),
proplists:get_value(name, GroupProps).
mk_limit_config_object(#config_LimitConfigParams{
id = ID,
started_at = StartedAt,
time_range_type = TimeRange,
shard_size = ShardSize,
type = Type,
scope = Scope,
context_type = ContextType,
op_behaviour = OpBehaviour
}) ->
#domain_LimitConfigObject{
ref = #domain_LimitConfigRef{id = ID},
data = #limiter_config_LimitConfig{
processor_type = <<"TurnoverProcessor">>,
created_at = StartedAt,
started_at = StartedAt,
shard_size = ShardSize,
time_range_type = translate_time_range_type(TimeRange),
context_type = translate_tuple_record(ContextType, "config", "limiter_config"),
type = maybe_apply(Type, fun translate_type/1),
scopes = maybe_apply(Scope, fun translate_scope/1),
description = <<"Description">>,
op_behaviour = maybe_apply(OpBehaviour, fun mk_op_behaviour/1)
}
}.
mk_op_behaviour(#config_OperationLimitBehaviour{invoice_payment_refund = PaymentRefund}) ->
#limiter_config_OperationLimitBehaviour{
invoice_payment_refund = maybe_apply(PaymentRefund, fun(Item) ->
translate_tuple_record(Item, "config", "limiter_config")
end)
}.
%% Interval type is never used in this test suite. To appease dialyzer this clause is commented out.
% translate_time_range_type({interval, #timerange_TimeRangeTypeInterval{amount = Amount}}) ->
% {interval, #limiter_config_TimeRangeTypeInterval{amount = Amount}};
translate_time_range_type({calendar, CalendarType}) ->
{calendar, translate_tuple_record(CalendarType, "timerange", "limiter_config")}.
translate_type({turnover, #config_LimitTypeTurnover{metric = Metric}}) ->
{turnover, #limiter_config_LimitTypeTurnover{metric = translate_tuple_record(Metric, "config", "limiter_config")}}.
translate_scope({single, ScopeType}) ->
ordsets:from_list([translate_scope_type(ScopeType)]);
translate_scope({multi, ScopeTypes}) ->
ordsets:from_list(lists:map(fun translate_scope_type/1, ordsets:to_list(ScopeTypes))).
translate_scope_type({Scope, #config_LimitScopeEmptyDetails{}}) ->
{Scope, #limiter_config_LimitScopeEmptyDetails{}}.
maybe_apply(undefined, _) ->
undefined;
maybe_apply(Value, Fun) ->
Fun(Value).
translate_tuple_record({Type, Record}, OldRecordPrefix, NewRecordPrefix) ->
{Type, change_record_name_prefix(Record, OldRecordPrefix, NewRecordPrefix)}.
change_record_name_prefix(Record, OldPrefix, NewPrefix) ->
RecordName = atom_to_list(element(1, Record)),
[OldPrefix, TypeName] = string:split(RecordName, "_", trailing),
NewRecordName = list_to_existing_atom(NewPrefix ++ "_" ++ TypeName),
setelement(1, Record, NewRecordName).

View File

@ -15,11 +15,25 @@ services:
working_dir: $PWD
command: /sbin/init
depends_on:
dominant:
condition: service_healthy
machinegun:
condition: service_healthy
shumway:
condition: service_healthy
dominant:
image: ghcr.io/valitydev/dominant:sha-e0afa44
command: /opt/dominant/bin/dominant foreground
depends_on:
machinegun:
condition: service_healthy
healthcheck:
test: "/opt/dominant/bin/dominant ping"
interval: 10s
timeout: 5s
retries: 10
machinegun:
image: ghcr.io/valitydev/machinegun:sha-7e785cd
volumes:

View File

@ -73,6 +73,29 @@
}}
]},
{dmt_client, [
% milliseconds
{cache_update_interval, 5000},
{max_cache_size, #{
elements => 20,
% 50Mb
memory => 52428800
}},
{woody_event_handlers, [
{scoper_woody_event_handler, #{
event_handler_opts => #{
formatter_opts => #{
max_length => 1000
}
}
}}
]},
{service_urls, #{
'Repository' => <<"http://dominant:8022/v1/domain/repository">>,
'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">>
}}
]},
{kernel, [
{logger_sasl_compatible, false},
{logger_level, debug},

View File

@ -26,13 +26,15 @@
%% Common project dependencies.
{deps, [
{damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "master"}}},
{limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}},
{xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}},
{machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {branch, "master"}}},
{erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}},
{genlib, {git, "https://github.com/valitydev/genlib.git", {branch, "master"}}},
{scoper, {git, "https://github.com/valitydev/scoper.git", {branch, "master"}}},
{woody, {git, "https://github.com/valitydev/woody_erlang.git", {branch, "master"}}}
{woody, {git, "https://github.com/valitydev/woody_erlang.git", {branch, "master"}}},
{dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {branch, "master"}}}
]}.
%% XRef checks

View File

@ -9,7 +9,15 @@
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2},
{<<"damsel">>,
{git,"https://github.com/valitydev/damsel.git",
{ref,"33c5665571042440ccec109735481d8c13704ec2"}},
{ref,"9859ca2843ad1617a0bf8549c125a7e94d1d54b7"}},
0},
{<<"dmt_client">>,
{git,"https://github.com/valitydev/dmt_client.git",
{ref,"19a8ded17c05140f663c7b8b30450d9a1da4f53e"}},
0},
{<<"dmt_core">>,
{git,"https://github.com/valitydev/dmt-core.git",
{ref,"75841332fe0b40a77da0c12ea8d5dbb994da8e82"}},
1},
{<<"erl_health">>,
{git,"https://github.com/valitydev/erlang-health.git",
@ -25,7 +33,7 @@
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1},
{<<"limiter_proto">>,
{git,"https://github.com/valitydev/limiter-proto.git",
{ref,"31de59b17ad20e426b158ace6097e35330926bea"}},
{ref,"9b76200a957c0e91bcdf6f16dfbab90d38a3f173"}},
0},
{<<"machinery">>,
{git,"https://github.com/valitydev/machinery-erlang.git",

View File

@ -15,6 +15,10 @@ namespaces:
processor:
url: http://limiter:8022/v1/stateproc/lim/range_v1
pool_size: 500
domain-config:
processor:
url: http://dominant:8022/v1/stateproc
pool_size: 300
storage:
type: memory