From 084579a740886a0adebcdd0a350dba2dadbfc145 Mon Sep 17 00:00:00 2001 From: Roman Pushkov Date: Wed, 21 Oct 2020 18:34:22 +0300 Subject: [PATCH] HG-560: use compute provider in routing (#491) * use compute provider in routing * fix routing tests * fix formatting * drop revision, switch reduce to get_value * update route rules tests * use compute provider in routing_rule * clean up redundant varsets * remove more redundant varsets * rework acceptable_terminal/5 * refactoring * change redundant pm call --- apps/hellgate/src/hg_routing.erl | 185 +++++++----------- apps/hellgate/src/hg_routing_rule.erl | 14 +- .../test/hg_route_rules_tests_SUITE.erl | 29 ++- apps/hellgate/test/hg_routing_tests_SUITE.erl | 56 ++++-- 4 files changed, 141 insertions(+), 143 deletions(-) diff --git a/apps/hellgate/src/hg_routing.erl b/apps/hellgate/src/hg_routing.erl index 56441b9..ecd9f1a 100644 --- a/apps/hellgate/src/hg_routing.erl +++ b/apps/hellgate/src/hg_routing.erl @@ -479,28 +479,16 @@ get_rec_paytools_terms(?route(ProviderRef, _), Revision) -> pm_selector:varset(), hg_domain:revision() ) -> provider_with_ref() | no_return(). -acceptable_provider(payment, ProviderRef, VS, Revision) -> - Provider = - #domain_Provider{ - terms = Terms - } = hg_domain:get(Revision, {provider, ProviderRef}), - _ = acceptable_provision_payment_terms(Terms, VS, Revision), - {ProviderRef, Provider}; -acceptable_provider(recurrent_paytool, ProviderRef, VS, Revision) -> - Provider = - #domain_Provider{ - terms = Terms - } = hg_domain:get(Revision, {provider, ProviderRef}), - _ = acceptable_provision_recurrent_terms(Terms, VS, Revision), - {ProviderRef, Provider}; -acceptable_provider(recurrent_payment, ProviderRef, VS, Revision) -> - % Use provider check combined from recurrent_paytool and payment check - Provider = - #domain_Provider{ - terms = Terms - } = hg_domain:get(Revision, {provider, ProviderRef}), - _ = acceptable_provision_payment_terms(Terms, VS, Revision), - _ = acceptable_provision_recurrent_terms(Terms, VS, Revision), +acceptable_provider(Predestination, ProviderRef, VS, Revision) -> + {Client, Context} = get_party_client(), + {ok, Provider = #domain_Provider{terms = Terms}} = party_client_thrift:compute_provider( + ProviderRef, + Revision, + hg_varset:prepare_varset(VS), + Client, + Context + ), + _ = check_terms_acceptability(Predestination, Terms, VS), {ProviderRef, Provider}. %% @@ -513,13 +501,13 @@ acceptable_provider(recurrent_payment, ProviderRef, VS, Revision) -> ) -> {[non_fail_rated_route()], [rejected_route()]}. collect_routes_for_provider(Predestination, {ProviderRef, Provider}, VS, Revision) -> TerminalSelector = Provider#domain_Provider.terminal, - ProviderTerminalRefs = reduce(terminal, TerminalSelector, VS, Revision), + ProviderTerminalRefs = get_selector_value(terminal, TerminalSelector), lists:foldl( fun(ProviderTerminalRef, {Accepted, Rejected}) -> TerminalRef = get_terminal_ref(ProviderTerminalRef), Priority = get_terminal_priority(ProviderTerminalRef), try - {TerminalRef, Terminal} = acceptable_terminal(Predestination, TerminalRef, Provider, VS, Revision), + {TerminalRef, Terminal} = acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision), {[{{ProviderRef, Provider}, {TerminalRef, Terminal, Priority}} | Accepted], Rejected} catch ?rejected(Reason) -> @@ -534,44 +522,23 @@ collect_routes_for_provider(Predestination, {ProviderRef, Provider}, VS, Revisio -spec acceptable_terminal( route_predestination(), + provider_ref(), terminal_ref(), - provider(), pm_selector:varset(), hg_domain:revision() ) -> unweighted_terminal() | no_return(). -acceptable_terminal(payment, TerminalRef, Provider, VS, Revision) -> - #domain_Provider{ - terms = ProviderTerms - } = Provider, - Terminal = - #domain_Terminal{ - terms = TerminalTerms - } = hg_domain:get(Revision, {terminal, TerminalRef}), - % TODO the ability to override any terms makes for uncommon sense - % is it better to allow to override only cash flow / refunds terms? - Terms = merge_terms(ProviderTerms, TerminalTerms), - _ = acceptable_provision_payment_terms(Terms, VS, Revision), - {TerminalRef, Terminal}; -acceptable_terminal(recurrent_paytool, TerminalRef, Provider, VS, Revision) -> - #domain_Provider{ - terms = Terms - } = Provider, - Terminal = hg_domain:get(Revision, {terminal, TerminalRef}), - _ = acceptable_provision_recurrent_terms(Terms, VS, Revision), - {TerminalRef, Terminal}; -acceptable_terminal(recurrent_payment, TerminalRef, Provider, VS, Revision) -> - % Use provider check combined from recurrent_paytool and payment check - #domain_Provider{ - terms = ProviderTerms - } = Provider, - Terminal = - #domain_Terminal{ - terms = TerminalTerms - } = hg_domain:get(Revision, {terminal, TerminalRef}), - Terms = merge_terms(ProviderTerms, TerminalTerms), - _ = acceptable_provision_payment_terms(Terms, VS, Revision), - _ = acceptable_provision_recurrent_terms(Terms, VS, Revision), - {TerminalRef, Terminal}. +acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision) -> + {Client, Context} = get_party_client(), + {ok, Terms} = party_client_thrift:compute_provider_terminal_terms( + ProviderRef, + TerminalRef, + Revision, + hg_varset:prepare_varset(VS), + Client, + Context + ), + _ = check_terms_acceptability(Predestination, Terms, VS), + {TerminalRef, hg_domain:get(Revision, {terminal, TerminalRef})}. -spec get_terminal_ref(provider_terminal_ref()) -> terminal_ref(). get_terminal_ref(#domain_ProviderTerminalRef{id = ID}) -> @@ -586,28 +553,41 @@ get_terminal_priority(#domain_ProviderTerminalRef{ %% +get_party_client() -> + HgContext = hg_context:load(), + Client = hg_context:get_party_client(HgContext), + Context = hg_context:get_party_client_context(HgContext), + {Client, Context}. + +check_terms_acceptability(payment, Terms, VS) -> + _ = acceptable_provision_payment_terms(Terms, VS); +check_terms_acceptability(recurrent_paytool, Terms, VS) -> + _ = acceptable_provision_recurrent_terms(Terms, VS); +check_terms_acceptability(recurrent_payment, Terms, VS) -> + % Use provider check combined from recurrent_paytool and payment check + _ = acceptable_provision_payment_terms(Terms, VS), + _ = acceptable_provision_recurrent_terms(Terms, VS). + acceptable_provision_payment_terms( #domain_ProvisionTermSet{ payments = PaymentTerms }, - VS, - Revision + VS ) -> - _ = acceptable_payment_terms(PaymentTerms, VS, Revision), + _ = acceptable_payment_terms(PaymentTerms, VS), true; -acceptable_provision_payment_terms(undefined, _VS, _Revision) -> +acceptable_provision_payment_terms(undefined, _VS) -> throw(?rejected({'ProvisionTermSet', undefined})). acceptable_provision_recurrent_terms( #domain_ProvisionTermSet{ recurrent_paytools = RecurrentTerms }, - VS, - Revision + VS ) -> - _ = acceptable_recurrent_paytool_terms(RecurrentTerms, VS, Revision), + _ = acceptable_recurrent_paytool_terms(RecurrentTerms, VS), true; -acceptable_provision_recurrent_terms(undefined, _VS, _Revision) -> +acceptable_provision_recurrent_terms(undefined, _VS) -> throw(?rejected({'ProvisionTermSet', undefined})). acceptable_payment_terms( @@ -619,69 +599,62 @@ acceptable_payment_terms( holds = HoldsTerms, refunds = RefundsTerms }, - VS, - Revision + VS ) -> % TODO varsets getting mixed up % it seems better to pass down here hierarchy of contexts w/ appropriate module accessors ParentName = 'PaymentsProvisionTerms', - _ = try_accept_term(ParentName, currency, CurrenciesSelector, VS, Revision), - _ = try_accept_term(ParentName, category, CategoriesSelector, VS, Revision), - _ = try_accept_term(ParentName, payment_tool, PMsSelector, VS, Revision), - _ = try_accept_term(ParentName, cost, CashLimitSelector, VS, Revision), - _ = acceptable_holds_terms(HoldsTerms, getv(flow, VS, undefined), VS, Revision), - _ = acceptable_refunds_terms(RefundsTerms, getv(refunds, VS, undefined), VS, Revision), + _ = try_accept_term(ParentName, currency, getv(currency, VS), CurrenciesSelector), + _ = try_accept_term(ParentName, category, getv(category, VS), CategoriesSelector), + _ = try_accept_term(ParentName, payment_tool, getv(payment_tool, VS), PMsSelector), + _ = try_accept_term(ParentName, cost, getv(cost, VS), CashLimitSelector), + _ = acceptable_holds_terms(HoldsTerms, getv(flow, VS, undefined)), + _ = acceptable_refunds_terms(RefundsTerms, getv(refunds, VS, undefined)), %% TODO Check chargeback terms when there will be any %% _ = acceptable_chargeback_terms(...) true; -acceptable_payment_terms(undefined, _VS, _Revision) -> +acceptable_payment_terms(undefined, _VS) -> throw(?rejected({'PaymentsProvisionTerms', undefined})). -acceptable_holds_terms(_Terms, undefined, _VS, _Revision) -> +acceptable_holds_terms(_Terms, undefined) -> true; -acceptable_holds_terms(_Terms, instant, _VS, _Revision) -> +acceptable_holds_terms(_Terms, instant) -> true; -acceptable_holds_terms(Terms, {hold, Lifetime}, VS, Revision) -> +acceptable_holds_terms(Terms, {hold, Lifetime}) -> case Terms of #domain_PaymentHoldsProvisionTerms{lifetime = LifetimeSelector} -> - _ = try_accept_term('PaymentHoldsProvisionTerms', lifetime, Lifetime, LifetimeSelector, VS, Revision), + _ = try_accept_term('PaymentHoldsProvisionTerms', lifetime, Lifetime, LifetimeSelector), true; undefined -> throw(?rejected({'PaymentHoldsProvisionTerms', undefined})) end. -acceptable_refunds_terms(_Terms, undefined, _VS, _Revision) -> +acceptable_refunds_terms(_Terms, undefined) -> true; acceptable_refunds_terms( #domain_PaymentRefundsProvisionTerms{ partial_refunds = PartialRefundsTerms }, - RVS, - VS, - Revision + RVS ) -> _ = acceptable_partial_refunds_terms( PartialRefundsTerms, - getv(partial, RVS, undefined), - VS, - Revision + getv(partial, RVS, undefined) ), true; -acceptable_refunds_terms(undefined, _RVS, _VS, _Revision) -> +acceptable_refunds_terms(undefined, _RVS) -> throw(?rejected({'PaymentRefundsProvisionTerms', undefined})). -acceptable_partial_refunds_terms(_Terms, undefined, _VS, _Revision) -> +acceptable_partial_refunds_terms(_Terms, undefined) -> true; acceptable_partial_refunds_terms( #domain_PartialRefundsProvisionTerms{cash_limit = CashLimitSelector}, - #{cash_limit := MerchantLimit}, - VS, - Revision + #{cash_limit := MerchantLimit} ) -> - ProviderLimit = reduce(cash_limit, CashLimitSelector, VS, Revision), + ProviderLimit = get_selector_value(cash_limit, CashLimitSelector), hg_cash_range:is_subrange(MerchantLimit, ProviderLimit) == true orelse throw(?rejected({'PartialRefundsProvisionTerms', cash_limit})); -acceptable_partial_refunds_terms(undefined, _RVS, _VS, _Revision) -> +acceptable_partial_refunds_terms(undefined, _RVS) -> throw(?rejected({'PartialRefundsProvisionTerms', undefined})). merge_terms( @@ -745,22 +718,18 @@ acceptable_recurrent_paytool_terms( categories = CategoriesSelector, payment_methods = PMsSelector }, - VS, - Revision + VS ) -> - _ = try_accept_term('RecurrentPaytoolsProvisionTerms', category, CategoriesSelector, VS, Revision), - _ = try_accept_term('RecurrentPaytoolsProvisionTerms', payment_tool, PMsSelector, VS, Revision), + _ = try_accept_term('RecurrentPaytoolsProvisionTerms', category, getv(category, VS), CategoriesSelector), + _ = try_accept_term('RecurrentPaytoolsProvisionTerms', payment_tool, getv(payment_tool, VS), PMsSelector), true; -acceptable_recurrent_paytool_terms(undefined, _VS, _Revision) -> +acceptable_recurrent_paytool_terms(undefined, _VS) -> throw(?rejected({'RecurrentPaytoolsProvisionTerms', undefined})). -try_accept_term(ParentName, Name, Selector, VS, Revision) -> - try_accept_term(ParentName, Name, getv(Name, VS), Selector, VS, Revision). - -try_accept_term(ParentName, Name, Value, Selector, VS, Revision) when Selector /= undefined -> - Values = reduce(Name, Selector, VS, Revision), +try_accept_term(ParentName, Name, Value, Selector) when Selector /= undefined -> + Values = get_selector_value(Name, Selector), test_term(Name, Value, Values) orelse throw(?rejected({ParentName, Name})); -try_accept_term(ParentName, Name, _Value, undefined, _VS, _Revision) -> +try_accept_term(ParentName, Name, _Value, undefined) -> throw(?rejected({ParentName, Name})). test_term(currency, V, Vs) -> @@ -776,14 +745,6 @@ test_term(lifetime, ?hold_lifetime(Lifetime), ?hold_lifetime(Allowed)) -> %% -reduce(Name, S, VS, Revision) -> - case pm_selector:reduce(S, VS, Revision) of - {value, V} -> - V; - Ambiguous -> - error({misconfiguration, {'Could not reduce selector to a value', {Name, Ambiguous}}}) - end. - get_selector_value(Name, Selector) -> case Selector of {value, V} -> diff --git a/apps/hellgate/src/hg_routing_rule.erl b/apps/hellgate/src/hg_routing_rule.erl index bd3c82d..0d4f93a 100644 --- a/apps/hellgate/src/hg_routing_rule.erl +++ b/apps/hellgate/src/hg_routing_rule.erl @@ -105,9 +105,12 @@ collect_routes(Predestination, Candidates, VS, Revision) -> priority = Priority, weight = Weight } = Candidate, - {#domain_Terminal{provider_ref = ProviderRef}, Provider} = get_route(TerminalRef, Revision), + #domain_Terminal{ + provider_ref = ProviderRef + } = hg_domain:get(Revision, {terminal, TerminalRef}), + Provider = hg_domain:get(Revision, {provider, ProviderRef}), try - {_, Terminal} = hg_routing:acceptable_terminal(Predestination, TerminalRef, Provider, VS, Revision), + {_, Terminal} = hg_routing:acceptable_terminal(Predestination, ProviderRef, TerminalRef, VS, Revision), {[{{ProviderRef, Provider}, {TerminalRef, Terminal, {Priority, Weight}}} | Accepted], Rejected} catch {rejected, Reason} -> @@ -136,13 +139,6 @@ filter_routes({Routes, Rejected}, Prohibitions) -> Routes ). -get_route(TerminalRef, Revision) -> - Terminal = - #domain_Terminal{ - provider_ref = ProviderRef - } = hg_domain:get(Revision, {terminal, TerminalRef}), - {Terminal, hg_domain:get(Revision, {provider, ProviderRef})}. - get_rule_set(RuleSetRef, Revision) -> hg_domain:get(Revision, {payment_routing_rules, RuleSetRef}). diff --git a/apps/hellgate/test/hg_route_rules_tests_SUITE.erl b/apps/hellgate/test/hg_route_rules_tests_SUITE.erl index a0f6886..bbbab6f 100644 --- a/apps/hellgate/test/hg_route_rules_tests_SUITE.erl +++ b/apps/hellgate/test/hg_route_rules_tests_SUITE.erl @@ -61,15 +61,23 @@ init_per_suite(C) -> woody, scoper, dmt_client, + party_client, hellgate, {cowboy, CowboySpec} ]), ok = hg_domain:insert(construct_domain_fixture()), - + PartyID = hg_utils:unique_id(), + PartyClient = party_client:create_client(), {ok, SupPid} = supervisor:start_link(?MODULE, []), {ok, _} = supervisor:start_child(SupPid, hg_dummy_fault_detector:child_spec()), _ = unlink(SupPid), - [{apps, Apps}, {test_sup, SupPid} | C]. + [ + {apps, Apps}, + {test_sup, SupPid}, + {party_client, PartyClient}, + {party_id, PartyID} + | C + ]. -spec end_per_suite(config()) -> _. end_per_suite(C) -> @@ -91,10 +99,23 @@ end_per_group(_GroupName, C) -> end. -spec init_per_testcase(test_case_name(), config()) -> config(). -init_per_testcase(_, C) -> C. +init_per_testcase(_, C) -> + Ctx0 = hg_context:set_party_client(cfg(party_client, C), hg_context:create()), + Ctx1 = hg_context:set_user_identity( + #{ + id => cfg(party_id, C), + realm => <<"internal">> + }, + Ctx0 + ), + Ctx2 = hg_context:set_party_client_context(#{woody_context => woody_context:new()}, Ctx1), + ok = hg_context:save(Ctx2), + C. -spec end_per_testcase(test_case_name(), config()) -> config(). -end_per_testcase(_Name, _C) -> ok. +end_per_testcase(_Name, _C) -> + ok = hg_context:cleanup(), + ok. cfg(Key, C) -> hg_ct_helper:cfg(Key, C). diff --git a/apps/hellgate/test/hg_routing_tests_SUITE.erl b/apps/hellgate/test/hg_routing_tests_SUITE.erl index f2fd32e..3ce0041 100644 --- a/apps/hellgate/test/hg_routing_tests_SUITE.erl +++ b/apps/hellgate/test/hg_routing_tests_SUITE.erl @@ -80,18 +80,26 @@ init_per_suite(C) -> woody, scoper, dmt_client, + party_client, hellgate, snowflake, {cowboy, CowboySpec} ]), ok = hg_domain:insert(construct_domain_fixture()), - + PartyID = hg_utils:unique_id(), + PartyClient = party_client:create_client(), {ok, SupPid} = supervisor:start_link(?MODULE, []), {ok, _} = supervisor:start_child(SupPid, hg_dummy_fault_detector:child_spec()), FDConfig = genlib_app:env(hellgate, fault_detector), application:set_env(hellgate, fault_detector, FDConfig#{enabled => true}), _ = unlink(SupPid), - [{apps, Apps}, {test_sup, SupPid} | C]. + [ + {apps, Apps}, + {test_sup, SupPid}, + {party_client, PartyClient}, + {party_id, PartyID} + | C + ]. -spec end_per_suite(config()) -> _. end_per_suite(C) -> @@ -122,10 +130,21 @@ end_per_group(_GroupName, C) -> -spec init_per_testcase(test_case_name(), config()) -> config(). init_per_testcase(_, C) -> + Ctx0 = hg_context:set_party_client(cfg(party_client, C), hg_context:create()), + Ctx1 = hg_context:set_user_identity( + #{ + id => cfg(party_id, C), + realm => <<"internal">> + }, + Ctx0 + ), + Ctx2 = hg_context:set_party_client_context(#{woody_context => woody_context:new()}, Ctx1), + ok = hg_context:save(Ctx2), C. -spec end_per_testcase(test_case_name(), config()) -> config(). end_per_testcase(_Name, _C) -> + ok = hg_context:cleanup(), ok. cfg(Key, C) -> @@ -133,7 +152,6 @@ cfg(Key, C) -> -spec handle_uncomputable_provider_terms(config()) -> test_return(). handle_uncomputable_provider_terms(_C) -> - ok = hg_context:save(hg_context:create()), VS0 = #{ category => ?cat(1), currency => ?cur(<<"EUR">>), @@ -164,8 +182,6 @@ handle_uncomputable_provider_terms(_C) -> -spec gathers_fail_rated_routes(config()) -> test_return(). gathers_fail_rated_routes(_C) -> - ok = hg_context:save(hg_context:create()), - VS = #{ category => ?cat(1), currency => ?cur(<<"RUB">>), @@ -184,22 +200,25 @@ gathers_fail_rated_routes(_C) -> {{?prv(201), _}, _, {{alive, 0.1}, {normal, 0.1}}}, {{?prv(202), _}, _, {{alive, 0.0}, {normal, 0.0}}} ] = Result, - - hg_context:cleanup(), ok. -spec fatal_risk_score_for_route_found(config()) -> test_return(). fatal_risk_score_for_route_found(_C) -> - ok = hg_context:save(hg_context:create()), Revision = hg_domain:head(), PaymentInstitution = hg_domain:get(Revision, {payment_institution, ?pinst(1)}), VS0 = #{ category => ?cat(1), currency => ?cur(<<"RUB">>), cost => ?cash(1000, <<"RUB">>), - payment_tool => {bank_card, #domain_BankCard{}}, party_id => <<"12345">>, - flow => instant + flow => instant, + payment_tool => + {bank_card, #domain_BankCard{ + token = <<"token">>, + payment_system = maestro, + bin = <<"424242">>, + last_digits = <<"4242">> + }} }, RiskScore = fatal, {Routes0, RejectContext0} = hg_routing:gather_routes(payment, PaymentInstitution, VS0, Revision), @@ -236,19 +255,23 @@ fatal_risk_score_for_route_found(_C) -> ], rejected_routes := [] }}}} = Result1, - hg_context:cleanup(), ok. -spec no_route_found_for_payment(config()) -> test_return(). no_route_found_for_payment(_C) -> - ok = hg_context:save(hg_context:create()), VS0 = #{ category => ?cat(1), currency => ?cur(<<"RUB">>), cost => ?cash(1000, <<"RUB">>), - payment_tool => {bank_card, #domain_BankCard{}}, party_id => <<"12345">>, - flow => instant + flow => instant, + payment_tool => + {bank_card, #domain_BankCard{ + token = <<"token">>, + payment_system = maestro, + bin = <<"424242">>, + last_digits = <<"4242">> + }} }, RiskScore = low, @@ -297,7 +320,6 @@ no_route_found_for_payment(_C) -> terminal = ?trm(10) }, _Meta} = hg_routing:choose_route(FailRatedRoutes1, RejectContext1, RiskScore), - hg_context:cleanup(), ok. -spec prefer_alive(config()) -> test_return(). @@ -538,7 +560,6 @@ prefer_weight_over_conversion(_C) -> -spec terminal_priority_for_shop(config()) -> test_return(). terminal_priority_for_shop(C) -> - ok = hg_context:save(hg_context:create()), {ok, #domain_PaymentRoute{ provider = ?prv(300), @@ -550,8 +571,7 @@ terminal_priority_for_shop(C) -> provider = ?prv(300), terminal = ?trm(222) }, - _Meta1} = terminal_priority_for_shop(?dummy_party_id, ?dummy_another_shop_id, C), - ok = hg_context:cleanup(). + _Meta1} = terminal_priority_for_shop(?dummy_party_id, ?dummy_another_shop_id, C). terminal_priority_for_shop(PartyID, ShopID, _C) -> VS = #{