Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 74 additions & 41 deletions apps/ff_transfer/test/ff_withdrawal_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ end).
all() ->
[
{group, default},
{group, non_parallel},
{group, withdrawal_without_termset}
{group, non_parallel}
].

-spec groups() -> [{group_name(), list(), [test_case_name()]}].
Expand Down Expand Up @@ -119,7 +118,8 @@ groups() ->
preserve_revisions_test,
unknown_test,
provider_callback_test,
provider_terminal_terms_merging_test
provider_terminal_terms_merging_test,
withdrawal_without_termset_test
]},
%% Domain revision changes must run after all default cases finish.
{non_parallel, [], [
Expand All @@ -135,13 +135,20 @@ groups() ->

-spec init_per_suite(config()) -> config().
init_per_suite(C) ->
ct_helper:makeup_cfg(
C1 = ct_helper:makeup_cfg(
[
ct_helper:test_case_name(init),
ct_payment_system:setup()
],
C
).
),
%% Publish the private fixture before any parallel case captures a revision.
ok = ct_helper:set_context(C1),
try
[{without_termset_fixture, without_termset_fixture()} | C1]
after
ok = ct_helper:unset_context()
end.

-spec end_per_suite(config()) -> _.
end_per_suite(C) ->
Expand All @@ -155,45 +162,22 @@ init_per_group(withdrawal_repair, C) ->
TermsetHierarchy = ct_domain:term_set_hierarchy(?trms(1), Termset),
_ = ct_domain_config:update(TermsetHierarchy),
C;
init_per_group(withdrawal_without_termset, C) ->
WasRevision = dmt_client:get_latest_version(),
#domain_conf_v2_VersionedObject{
object = {provider, ProviderObject}
} = dmt_client:checkout_object(WasRevision, {provider, ?prv(1)}),
Provider = ProviderObject#domain_ProviderObject.data,
#domain_Provider{
terms =
Terms = #domain_ProvisionTermSet{
wallet = Wallet
}
} = Provider,
ProviderUpd =
{provider, ProviderObject#domain_ProviderObject{
data = Provider#domain_Provider{
terms = Terms#domain_ProvisionTermSet{
wallet = Wallet#domain_WalletProvisionTerms{
withdrawals = undefined
}
}
}
}},
_ = ct_domain_config:upsert(ProviderUpd),
[{domain_revision, WasRevision} | C];
init_per_group(_, C) ->
C.

-spec end_per_group(group_name(), config()) -> _.
end_per_group(withdrawal_without_termset, C) ->
WasRevision = proplists:get_value(domain_revision, C),
ct_domain_config:reset(WasRevision),
proplists:delete(domain_revision, C);
end_per_group(_, _) ->
ok.

%%

-spec init_per_testcase(test_case_name(), config()) -> config().
init_per_testcase(Name, C) ->
init_per_testcase(Name, C0) ->
C =
case Name of
withdrawal_without_termset_test -> ct_helper:cfg(without_termset_fixture, C0) ++ C0;
_ -> C0
end,
C1 = ct_helper:makeup_cfg(
[
ct_helper:test_case_name(Name),
Expand Down Expand Up @@ -786,9 +770,14 @@ withdrawal_without_termset_test(C) ->
},
ok = ff_withdrawal_machine:create(WithdrawalParams, ff_entity_context:new()),
Result = await_final_withdrawal_status(WithdrawalID),
Part1 = <<"{rejected_routes,[{{domain_ProviderRef,1},{domain_TerminalRef,1},">>,
Part2 = <<"{'WithdrawalProvisionTerms',not_found}}]}">>,
ExpectedReason = <<Part1/binary, Part2/binary>>,
ID = integer_to_binary(ct_helper:cfg(without_termset_route_id, C)),
ExpectedReason = <<
"{rejected_routes,[{{domain_ProviderRef,",
ID/binary,
"},{domain_TerminalRef,",
ID/binary,
"},{'WithdrawalProvisionTerms',not_found}}]}"
>>,
?assertEqual(
{
failed,
Expand Down Expand Up @@ -927,14 +916,58 @@ provider_terminal_terms_merging_test(C) ->

%% Utils

%% A single-route RUB scenario. Shared currencies and proxy definitions are
%% immutable; provider terms, routing, wallet terms and accounts are private.
without_termset_fixture() ->
ID = 1000000 + erlang:unique_integer([positive, monotonic]),
Provider = ct_domain_config:get({provider, ?prv(1)}),
#domain_Provider{terms = Terms = #domain_ProvisionTermSet{wallet = Wallet}} = Provider,
{ok, ProviderAccount} = ct_helper:create_account(<<"RUB">>),
PaymentInstitution = ct_domain_config:get({payment_institution, ?payinst(1)}),
WalletTerms = ct_domain_config:get({term_set_hierarchy, ?trms(1)}),
Fixture = [
{provider, #domain_ProviderObject{
ref = ?prv(ID),
data = Provider#domain_Provider{
accounts = #{?cur(<<"RUB">>) => #domain_ProviderAccount{settlement = ProviderAccount}},
terms = Terms#domain_ProvisionTermSet{
wallet = Wallet#domain_WalletProvisionTerms{withdrawals = undefined}
}
}
}},
ct_domain:withdrawal_terminal(?trm(ID), ?prv(ID)),
ct_domain:system_account_set(?sas(ID), <<"Missing withdrawal terms">>, ?cur(<<"RUB">>)),
{term_set_hierarchy, #domain_TermSetHierarchyObject{ref = ?trms(ID), data = WalletTerms}},
{routing_rules, #domain_RoutingRulesObject{
ref = ?ruleset(ID),
data = #domain_RoutingRuleset{
name = <<"Missing withdrawal terms">>,
decisions = {candidates, [#domain_RoutingCandidate{allowed = {constant, true}, terminal = ?trm(ID)}]}
}
}},
{payment_institution, #domain_PaymentInstitutionObject{
ref = ?payinst(ID),
data = PaymentInstitution#domain_PaymentInstitution{
system_account_set = {value, ?sas(ID)},
wallet_system_account_set = {value, ?sas(ID)},
withdrawal_routing_rules = #domain_RoutingRules{
policies = ?ruleset(ID),
prohibitions = ?ruleset(0)
}
}
}}
],
_ = ct_domain_config:insert(Fixture),
[{without_termset_route_id, ID}, {terms_ref, ?trms(ID)}, {payment_institution_ref, ?payinst(ID)}].

prepare_standard_environment(WithdrawalCash, C) ->
prepare_standard_environment(WithdrawalCash, undefined, C).

prepare_standard_environment({_Amount, Currency} = WithdrawalCash, Token, _C) ->
prepare_standard_environment({_Amount, Currency} = WithdrawalCash, Token, C) ->
PartyID = ct_objects:create_party(),
WalletID = ct_objects:create_wallet(
PartyID, Currency, #domain_TermSetHierarchyRef{id = 1}, #domain_PaymentInstitutionRef{id = 1}
),
TermsRef = ct_helper:cfg_with_default(terms_ref, C, ?trms(1)),
PaymentInstitutionRef = ct_helper:cfg_with_default(payment_institution_ref, C, ?payinst(1)),
WalletID = ct_objects:create_wallet(PartyID, Currency, TermsRef, PaymentInstitutionRef),
ok = await_wallet_balance({0, Currency}, WalletID),
DestinationID = ct_objects:create_destination(PartyID, Token),
SourceID = ct_objects:create_source(PartyID, Currency),
Expand Down
18 changes: 12 additions & 6 deletions apps/ff_transfer/test/ff_withdrawal_adjustment_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,16 @@ groups() ->
adjustment_can_change_body_on_succeeded_test,
adjustment_can_not_change_body_to_same,
adjustment_can_not_increase_body,
adjustment_can_not_change_body_on_pending
]},
%% These cases assert deltas on the shared provider account.
{non_parallel, [], [
adjustment_can_change_domain_revision_test,
adjustment_can_not_change_body_on_pending,
%% These scenarios keep all steps on their own wallet/destination.
%% Concurrent cases only insert entities; provider terms stay fixed.
adjustment_change_cash_flow_then_change_body_test,
adjustment_change_body_then_change_cash_flow_test
]},
%% Preserve the shared provider's accumulated history from default.
%% Its balance delta must be checked after every concurrent case finishes.
{non_parallel, [], [
adjustment_can_change_domain_revision_test
]}
].

Expand Down Expand Up @@ -355,9 +358,12 @@ adjustment_can_not_change_domain_revision_with_failed_status(C) ->
},
ok = ff_withdrawal_machine:create(Params, ff_entity_context:new()),
?assertMatch({failed, _}, await_final_withdrawal_status(WithdrawalID)),
%% HEAD can advance in another case. Choose a different revision relative
%% to this withdrawal so the status guard, not the same-revision guard, runs.
PreviousRevision = ff_withdrawal:final_domain_revision(get_withdrawal(WithdrawalID)) - 1,
Result = ff_withdrawal_machine:start_adjustment(WithdrawalID, #{
id => genlib:bsuuid(),
change => {change_cash_flow, ct_domain_config:head() - 1}
change => {change_cash_flow, PreviousRevision}
}),
?assertMatch({error, {invalid_cash_flow_change, {unavailable_status, {failed, #{code := _}}}}}, Result).

Expand Down
4 changes: 2 additions & 2 deletions apps/hellgate/test/hg_ct_helper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ unblock_party(PartyConfigRef) ->
change_party(PartyConfigRef, Fun) ->
PartyConfig0 = hg_domain:get({party_config, PartyConfigRef}),
PartyConfig1 = Fun(PartyConfig0),
_ = hg_domain:upsert(
_ = hg_domain:update(
{party_config, #domain_PartyConfigObject{
ref = PartyConfigRef,
data = PartyConfig1
Expand Down Expand Up @@ -652,7 +652,7 @@ unblock_shop(ShopConfigRef) ->
change_shop(ShopConfigRef, Fun) ->
ShopConfig0 = hg_domain:get({shop_config, ShopConfigRef}),
ShopConfig1 = Fun(ShopConfig0),
_ = hg_domain:upsert(
_ = hg_domain:update(
{shop_config, #domain_ShopConfigObject{
ref = ShopConfigRef,
data = ShopConfig1
Expand Down
85 changes: 62 additions & 23 deletions apps/hellgate/test/hg_invoice_template_tests_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,26 @@

-spec all() -> [test_case_name() | {group, atom()}].
all() ->
[
create_invalid_shop,
create_invalid_party_status,
create_invalid_shop_status,
{group, create_templates},
get_invoice_template_anyhow,
update_invalid_party_status,
update_invalid_shop_status,
{group, update_templates},
delete_invalid_party_status,
delete_invalid_shop_status,
delete_invoice_template,
terms_retrieval
].
[{group, templates}].

-spec groups() -> [{atom(), list(), [test_case_name()]}].
-spec groups() -> [{atom(), list(), [test_case_name() | {group, atom()}]}].
groups() ->
%% Status tests mutate the shared party/shop; terms_retrieval changes the domain.
[
%% Each case owns its party, shop accounts and mutable terms.
{templates, [parallel], [
create_invalid_shop,
create_invalid_party_status,
create_invalid_shop_status,
{group, create_templates},
get_invoice_template_anyhow,
update_invalid_party_status,
update_invalid_shop_status,
{group, update_templates},
delete_invalid_party_status,
delete_invalid_shop_status,
delete_invoice_template,
terms_retrieval
]},
{create_templates, [parallel], [
create_invalid_cost_fixed_amount,
create_invalid_cost_fixed_currency,
Expand Down Expand Up @@ -111,11 +112,21 @@ init_per_suite(C) ->
ok = op_context:save(op_context:key(hellgate), op_context:create()),
ShopConfigRef =
hg_ct_helper:create_party_and_shop(PartyConfigRef, ?cat(1), <<"RUB">>, ?trms(1), ?pinst(1), Client),
%% Allocate before the parallel group starts. The original shop is a control
%% for terms_retrieval, not a source of mutable fixtures for the other cases.
Cases = lists:usort([Name || {_, _, Tests} <- groups(), Name <- Tests, is_atom(Name)]),
Party = hg_domain:get({party_config, PartyConfigRef}),
Shop = hg_domain:get({shop_config, ShopConfigRef}),
Terms = hg_domain:get({term_set_hierarchy, ?trms(1)}),
Fixtures = [{Name, create_case_fixture(Party, Shop, Terms)} || Name <- Cases],
_ = hg_domain:insert(lists:append([Objects || {_, {_, Objects}} <- Fixtures])),
CaseFixtures = maps:from_list([{Name, Config} || {Name, {Config, _}} <- Fixtures]),
ok = op_context:cleanup(hellgate),
[
{party_config_ref, PartyConfigRef},
{case_fixtures, CaseFixtures},
{control_party_config_ref, PartyConfigRef},
{control_shop_config_ref, ShopConfigRef},
{party_client, Client},
{shop_config_ref, ShopConfigRef},
{root_url, RootUrl},
{apps, Apps}
| C
Expand All @@ -131,10 +142,10 @@ end_per_suite(C) ->
%% tests

-spec init_per_testcase(test_case_name(), config()) -> config().
init_per_testcase(_Name, C) ->
init_per_testcase(Name, C) ->
RootUrl = cfg(root_url, C),
Client = hg_client_invoice_templating:start_link(hg_ct_helper:create_client(RootUrl)),
[{client, Client} | C].
[{client, Client} | maps:get(Name, cfg(case_fixtures, C))] ++ C.

-spec end_per_testcase(test_case_name(), config()) -> _.
end_per_testcase(_Name, _C) ->
Expand Down Expand Up @@ -505,7 +516,16 @@ terms_retrieval(C) ->
}
} = TermSet1,

_ = hg_domain:update(construct_term_set_for_cost(5000, 11000)),
ControlC = [
{party_config_ref, cfg(control_party_config_ref, C)},
{shop_config_ref, cfg(control_shop_config_ref, C)}
| C
],
?invoice_tpl(ControlTplID) = create_invoice_tpl(ControlC),
TermSet1 = hg_client_invoice_templating:compute_terms(ControlTplID, Client),
_ = hg_domain:update(construct_term_set_for_cost(cfg(terms_ref, C), 5000, 11000)),
%% Updating one scenario must not change terms on the control shop.
TermSet1 = hg_client_invoice_templating:compute_terms(ControlTplID, Client),

TermSet2 = hg_client_invoice_templating:compute_terms(TplID1, Client),
#domain_TermSet{
Expand All @@ -531,6 +551,25 @@ terms_retrieval(C) ->

%%

create_case_fixture(Party, Shop, Terms) ->
PartyRef = #domain_PartyConfigRef{id = hg_utils:unique_id()},
ShopRef = #domain_ShopConfigRef{id = hg_utils:unique_id()},
TermsRef = ?trms(1000000 + erlang:unique_integer([positive, monotonic])),
Account = #domain_ShopAccount{
currency = ?cur(<<"RUB">>),
settlement = hg_accounting:create_account(<<"RUB">>),
guarantee = hg_accounting:create_account(<<"RUB">>)
},
Objects = [
{party_config, #domain_PartyConfigObject{ref = PartyRef, data = Party}},
{shop_config, #domain_ShopConfigObject{
ref = ShopRef,
data = Shop#domain_ShopConfig{party_ref = PartyRef, terms = TermsRef, account = Account}
}},
{term_set_hierarchy, #domain_TermSetHierarchyObject{ref = TermsRef, data = Terms}}
],
{[{party_config_ref, PartyRef}, {shop_config_ref, ShopRef}, {terms_ref, TermsRef}], Objects}.

create_invoice_tpl(Config) ->
Client = cfg(client, Config),
ShopConfigRef = cfg(shop_config_ref, Config),
Expand Down Expand Up @@ -638,7 +677,7 @@ construct_domain_fixture() ->
}}
].

construct_term_set_for_cost(LowerBound, UpperBound) ->
construct_term_set_for_cost(TermsRef, LowerBound, UpperBound) ->
TermSet = #domain_TermSet{
payments = #domain_PaymentsServiceTerms{
payment_methods =
Expand Down Expand Up @@ -669,7 +708,7 @@ construct_term_set_for_cost(LowerBound, UpperBound) ->
}
},
{term_set_hierarchy, #domain_TermSetHierarchyObject{
ref = ?trms(1),
ref = TermsRef,
data = #domain_TermSetHierarchy{
parent_terms = undefined,
term_set = TermSet
Expand Down
Loading