From b6d1f2fe25332ff8e0d07bdcd9ad59770af0244f Mon Sep 17 00:00:00 2001 From: ndiezel0 Date: Sun, 20 Sep 2026 12:09:53 +0000 Subject: [PATCH] Speedup tests v1 --- apps/ff_transfer/test/ff_withdrawal_SUITE.erl | 115 +++++++++++------- .../test/ff_withdrawal_adjustment_SUITE.erl | 18 ++- apps/hellgate/test/hg_ct_helper.erl | 4 +- .../test/hg_invoice_template_tests_SUITE.erl | 85 +++++++++---- 4 files changed, 150 insertions(+), 72 deletions(-) diff --git a/apps/ff_transfer/test/ff_withdrawal_SUITE.erl b/apps/ff_transfer/test/ff_withdrawal_SUITE.erl index 95ccb2ff..5e955c44 100644 --- a/apps/ff_transfer/test/ff_withdrawal_SUITE.erl +++ b/apps/ff_transfer/test/ff_withdrawal_SUITE.erl @@ -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()]}]. @@ -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, [], [ @@ -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) -> @@ -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), @@ -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 = <>, + 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, @@ -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), diff --git a/apps/ff_transfer/test/ff_withdrawal_adjustment_SUITE.erl b/apps/ff_transfer/test/ff_withdrawal_adjustment_SUITE.erl index e6ff414d..72912f62 100644 --- a/apps/ff_transfer/test/ff_withdrawal_adjustment_SUITE.erl +++ b/apps/ff_transfer/test/ff_withdrawal_adjustment_SUITE.erl @@ -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 ]} ]. @@ -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). diff --git a/apps/hellgate/test/hg_ct_helper.erl b/apps/hellgate/test/hg_ct_helper.erl index d1431139..a3c915be 100644 --- a/apps/hellgate/test/hg_ct_helper.erl +++ b/apps/hellgate/test/hg_ct_helper.erl @@ -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 @@ -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 diff --git a/apps/hellgate/test/hg_invoice_template_tests_SUITE.erl b/apps/hellgate/test/hg_invoice_template_tests_SUITE.erl index 627d51af..6c2273ad 100644 --- a/apps/hellgate/test/hg_invoice_template_tests_SUITE.erl +++ b/apps/hellgate/test/hg_invoice_template_tests_SUITE.erl @@ -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, @@ -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 @@ -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) -> @@ -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{ @@ -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), @@ -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 = @@ -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