mirror of
https://github.com/valitydev/fistful-server.git
synced 2024-11-06 02:35:18 +00:00
FF-73: call wallets from wapi using thrift API (#75)
* fix test * FF-73: init * FF-73 * FF-73: wapi cut off utils * FF-73: cut off wallet from wapi_ff_backend * FF-73: encode to marshal * impl thrift changes * FF-73: add test * idempotense wallet * uncomment options * fix bugs * del junk * del maybe_marshal * little change * Remove races in tests * refactored, added wallet mock tests * Fix missed variable * fixed, added thrift handler * merge master * daylizer fix * FF-94: Wapi identity thrift (#88) * added identity create and get methods * added identity challenges back, get events back * added tests * nano * added some fixed * fixed tests * fixed tests * refactored * refactored * added new suite, some fixes * added more tests * fixed types * minor * fixed tests * fixed linter * updated proto * fixed * updated proto * fixed * fixed * nano Co-authored-by: Артем <WWW_cool@inbox.ru> Co-authored-by: Andrey Fadeev <me@ciiol.net>
This commit is contained in:
parent
208e9c538d
commit
2b335fa3d4
@ -120,7 +120,9 @@ start_app(wapi_woody_client = AppName) ->
|
||||
{service_urls, #{
|
||||
cds_storage => "http://cds:8022/v1/storage",
|
||||
identdoc_storage => "http://cds:8022/v1/identity_document_storage",
|
||||
fistful_stat => "http://fistful-magista:8022/stat"
|
||||
fistful_stat => "http://fistful-magista:8022/stat",
|
||||
fistful_wallet => "http://localhost:8022/v1/wallet",
|
||||
fistful_identity => "http://localhost:8022/v1/identity"
|
||||
}},
|
||||
{service_retries, #{
|
||||
fistful_stat => #{
|
||||
|
@ -35,6 +35,8 @@ handle_function_('Create', [IdentityParams], Opts) ->
|
||||
woody_error:raise(business, #fistful_IdentityClassNotFound{});
|
||||
{error, {inaccessible, _}} ->
|
||||
woody_error:raise(business, #fistful_PartyInaccessible{});
|
||||
{error, exists} ->
|
||||
woody_error:raise(business, #fistful_IDExists{});
|
||||
{error, Error} ->
|
||||
woody_error:raise(system, {internal, result_unexpected, woody_error:format_details(Error)})
|
||||
end;
|
||||
@ -84,6 +86,7 @@ handle_function_('GetChallenges', [ID], _Opts) ->
|
||||
{error, notfound} ->
|
||||
woody_error:raise(business, #fistful_IdentityNotFound{})
|
||||
end;
|
||||
|
||||
handle_function_('GetEvents', [IdentityID, RangeParams], _Opts) ->
|
||||
Range = ff_identity_codec:unmarshal(range, RangeParams),
|
||||
case ff_identity_machine:events(IdentityID, Range) of
|
||||
|
@ -4,26 +4,65 @@
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_wallet_thrift.hrl").
|
||||
|
||||
-export([marshal_wallet/1]).
|
||||
-export([unmarshal_wallet/1]).
|
||||
-export([unmarshal_wallet_params/1]).
|
||||
|
||||
-export([marshal/2]).
|
||||
-export([unmarshal/2]).
|
||||
|
||||
%% API
|
||||
-spec marshal_wallet(ff_wallet:wallet()) ->
|
||||
ff_proto_wallet_thrift:'Wallet'().
|
||||
|
||||
marshal_wallet(Wallet) ->
|
||||
#wlt_Wallet{
|
||||
name = marshal(string, ff_wallet:name(Wallet)),
|
||||
blocking = marshal(blocking, ff_wallet:blocking(Wallet)),
|
||||
account = marshal(account, ff_wallet:account(Wallet)),
|
||||
external_id = marshal(id, ff_wallet:external_id(Wallet))
|
||||
}.
|
||||
|
||||
-spec unmarshal_wallet(ff_proto_wallet_thrift:'Wallet'()) ->
|
||||
ff_wallet:wallet().
|
||||
|
||||
unmarshal_wallet(#wlt_Wallet{
|
||||
name = Name,
|
||||
external_id = ExternalID
|
||||
}) ->
|
||||
genlib_map:compact(#{
|
||||
name => unmarshal(string, Name),
|
||||
external_id => unmarshal(id, ExternalID)
|
||||
}).
|
||||
|
||||
-spec unmarshal_wallet_params(ff_proto_wallet_thrift:'WalletParams'()) ->
|
||||
ff_wallet_machine:params().
|
||||
|
||||
unmarshal_wallet_params(#wlt_WalletParams{
|
||||
id = ID,
|
||||
account_params = AccountParams,
|
||||
name = Name,
|
||||
external_id = ExternalID
|
||||
}) ->
|
||||
{IdentityID, Currency} = unmarshal(account_params, AccountParams),
|
||||
genlib_map:compact(#{
|
||||
id => unmarshal(id, ID),
|
||||
name => unmarshal(string, Name),
|
||||
identity => IdentityID,
|
||||
currency => Currency,
|
||||
external_id => maybe_unmarshal(id, ExternalID)
|
||||
}).
|
||||
|
||||
-spec marshal(ff_codec:type_name(), ff_codec:decoded_value()) ->
|
||||
ff_codec:encoded_value().
|
||||
|
||||
marshal(change, {created, Wallet}) ->
|
||||
{created, marshal(wallet, Wallet)};
|
||||
{created, marshal_wallet(Wallet)};
|
||||
marshal(change, {account, AccountChange}) ->
|
||||
{account, marshal(account_change, AccountChange)};
|
||||
|
||||
marshal(wallet, Wallet) ->
|
||||
Name = maps:get(name, Wallet, undefined),
|
||||
ExternalID = maps:get(external_id, Wallet, undefined),
|
||||
#wlt_Wallet{
|
||||
name = marshal(string, Name),
|
||||
external_id = marshal(id, ExternalID)
|
||||
};
|
||||
marshal(ctx, Ctx) ->
|
||||
marshal(context, Ctx);
|
||||
|
||||
marshal(T, V) ->
|
||||
ff_codec:marshal(T, V).
|
||||
@ -46,14 +85,14 @@ unmarshal(change, {created, Wallet}) ->
|
||||
unmarshal(change, {account, AccountChange}) ->
|
||||
{account, unmarshal(account_change, AccountChange)};
|
||||
|
||||
unmarshal(wallet, #wlt_Wallet{
|
||||
name = Name,
|
||||
external_id = ExternalID
|
||||
unmarshal(account_params, #account_AccountParams{
|
||||
identity_id = IdentityID,
|
||||
symbolic_code = SymbolicCode
|
||||
}) ->
|
||||
genlib_map:compact(#{
|
||||
name => unmarshal(string, Name),
|
||||
external_id => unmarshal(id, ExternalID)
|
||||
});
|
||||
{unmarshal(id, IdentityID), unmarshal(string, SymbolicCode) };
|
||||
|
||||
unmarshal(ctx, Ctx) ->
|
||||
maybe_unmarshal(context, Ctx);
|
||||
|
||||
unmarshal(T, V) ->
|
||||
ff_codec:unmarshal(T, V).
|
||||
|
@ -11,6 +11,7 @@
|
||||
%%
|
||||
-spec handle_function(woody:func(), woody:args(), woody:options()) ->
|
||||
{ok, woody:result()} | no_return().
|
||||
|
||||
handle_function(Func, Args, Opts) ->
|
||||
scoper:scope(wallet, #{},
|
||||
fun() ->
|
||||
@ -24,8 +25,8 @@ handle_function(Func, Args, Opts) ->
|
||||
handle_function_('Create', [Params], Opts) ->
|
||||
WalletID = Params#wlt_WalletParams.id,
|
||||
case ff_wallet_machine:create(
|
||||
decode(wallet_params, Params),
|
||||
decode(context, Params#wlt_WalletParams.context))
|
||||
ff_wallet_codec:unmarshal_wallet_params(Params),
|
||||
ff_wallet_codec:unmarshal(ctx, Params#wlt_WalletParams.context))
|
||||
of
|
||||
ok ->
|
||||
handle_function_('Get', [WalletID], Opts);
|
||||
@ -42,45 +43,15 @@ handle_function_('Create', [Params], Opts) ->
|
||||
handle_function_('Get', [ID], _Opts) ->
|
||||
case ff_wallet_machine:get(ID) of
|
||||
{ok, Machine} ->
|
||||
{ok, encode(wallet, {ID, Machine})};
|
||||
Wallet = ff_wallet_machine:wallet(Machine),
|
||||
Ctx = ff_machine:ctx(Machine),
|
||||
CreatedAt = ff_machine:created(Machine),
|
||||
Response = ff_wallet_codec:marshal_wallet(Wallet),
|
||||
{ok, Response#wlt_Wallet{
|
||||
id = ff_wallet_codec:marshal(id, ID),
|
||||
created_at = ff_wallet_codec:marshal(timestamp, CreatedAt),
|
||||
context = ff_wallet_codec:marshal(ctx, Ctx)
|
||||
}};
|
||||
{error, notfound} ->
|
||||
woody_error:raise(business, #fistful_WalletNotFound{})
|
||||
end.
|
||||
|
||||
encode(wallet, {ID, Machine}) ->
|
||||
Wallet = ff_wallet_machine:wallet(Machine),
|
||||
Ctx = ff_wallet_machine:ctx(Machine),
|
||||
#wlt_Wallet{
|
||||
id = ID,
|
||||
name = ff_wallet:name(Wallet),
|
||||
blocking = ff_wallet:blocking(Wallet),
|
||||
account = encode(account, ff_wallet:account(Wallet)),
|
||||
external_id = ff_wallet:external_id(Wallet),
|
||||
context = encode(context, Ctx)
|
||||
};
|
||||
encode(context, Ctx) ->
|
||||
ff_entity_context_codec:marshal(Ctx);
|
||||
encode(account, Account) ->
|
||||
#account_Account{
|
||||
id = ff_account:id(Account),
|
||||
identity = ff_account:identity(Account),
|
||||
currency = encode(currency, ff_account:currency(Account)),
|
||||
accounter_account_id = ff_account:accounter_account_id(Account)
|
||||
};
|
||||
encode(currency, CurrencyId) ->
|
||||
#'CurrencyRef'{symbolic_code = CurrencyId}.
|
||||
|
||||
decode(wallet_params, Params) ->
|
||||
AccountParams = Params#wlt_WalletParams.account_params,
|
||||
#{
|
||||
id => Params#wlt_WalletParams.id,
|
||||
name => Params#wlt_WalletParams.name,
|
||||
identity => AccountParams#account_AccountParams.identity_id,
|
||||
currency => AccountParams#account_AccountParams.symbolic_code,
|
||||
external_id => Params#wlt_WalletParams.external_id
|
||||
};
|
||||
decode(context, undefined) ->
|
||||
undefined;
|
||||
decode(context, Ctx) ->
|
||||
ff_entity_context_codec:unmarshal(Ctx).
|
||||
|
||||
|
@ -403,6 +403,7 @@ create_destination(IID, C) ->
|
||||
|
||||
process_withdrawal(WalID, DestID) ->
|
||||
WdrID = generate_id(),
|
||||
|
||||
ok = ff_withdrawal_machine:create(
|
||||
#{id => WdrID, wallet_id => WalID, destination_id => DestID, body => {4240, <<"RUB">>}},
|
||||
ff_entity_context:new()
|
||||
|
@ -196,7 +196,7 @@ block_party(Party, C) ->
|
||||
|
||||
construct_wallet_params(ID, IdentityID, Currency) ->
|
||||
#wlt_WalletParams{
|
||||
id = ID,
|
||||
id = ID,
|
||||
name = <<"Valet">>,
|
||||
account_params = #account_AccountParams{
|
||||
identity_id = IdentityID,
|
||||
@ -205,7 +205,7 @@ construct_wallet_params(ID, IdentityID, Currency) ->
|
||||
}.
|
||||
construct_wallet_params(ID, IdentityID, Currency, ExternalID) ->
|
||||
#wlt_WalletParams{
|
||||
id = ID,
|
||||
id = ID,
|
||||
name = <<"Valet">>,
|
||||
external_id = ExternalID,
|
||||
account_params = #account_AccountParams{
|
||||
@ -215,7 +215,7 @@ construct_wallet_params(ID, IdentityID, Currency, ExternalID) ->
|
||||
}.
|
||||
construct_wallet_params(ID, IdentityID, Currency, ExternalID, Ctx) ->
|
||||
#wlt_WalletParams{
|
||||
id = ID,
|
||||
id = ID,
|
||||
name = <<"Valet">>,
|
||||
external_id = ExternalID,
|
||||
context = Ctx,
|
||||
|
@ -117,6 +117,7 @@ deposit_via_admin_ok(C) ->
|
||||
currency = #'CurrencyRef'{symbolic_code = <<"RUB">>},
|
||||
resource = {internal, #src_Internal{details = <<"Infinite source of cash">>}}
|
||||
}]),
|
||||
|
||||
SrcID = Src1#src_Source.id,
|
||||
{authorized, #src_Authorized{}} = ct_helper:await(
|
||||
{authorized, #src_Authorized{}},
|
||||
@ -164,6 +165,7 @@ deposit_via_admin_fails(C) ->
|
||||
currency = #'CurrencyRef'{symbolic_code = <<"RUB">>},
|
||||
resource = {internal, #src_Internal{details = <<"Infinite source of cash">>}}
|
||||
}]),
|
||||
|
||||
SrcID = Src1#src_Source.id,
|
||||
{authorized, #src_Authorized{}} = ct_helper:await(
|
||||
{authorized, #src_Authorized{}},
|
||||
@ -206,6 +208,7 @@ deposit_via_admin_amount_fails(C) ->
|
||||
SrcID = genlib:unique(),
|
||||
DepID = genlib:unique(),
|
||||
% Create source
|
||||
|
||||
{ok, _Src1} = call_admin('CreateSource', [#ff_admin_SourceParams{
|
||||
id = SrcID,
|
||||
name = <<"HAHA NO">>,
|
||||
@ -213,6 +216,7 @@ deposit_via_admin_amount_fails(C) ->
|
||||
currency = #'CurrencyRef'{symbolic_code = <<"RUB">>},
|
||||
resource = {internal, #src_Internal{details = <<"Infinite source of cash">>}}
|
||||
}]),
|
||||
|
||||
{authorized, #src_Authorized{}} = ct_helper:await(
|
||||
{authorized, #src_Authorized{}},
|
||||
fun () ->
|
||||
@ -249,6 +253,7 @@ deposit_via_admin_currency_fails(C) ->
|
||||
currency = #'CurrencyRef'{symbolic_code = <<"RUB">>},
|
||||
resource = {internal, #src_Internal{details = <<"Infinite source of cash">>}}
|
||||
}]),
|
||||
|
||||
SrcID = Src1#src_Source.id,
|
||||
{authorized, #src_Authorized{}} = ct_helper:await(
|
||||
{authorized, #src_Authorized{}},
|
||||
|
@ -73,8 +73,8 @@
|
||||
-spec blocking(wallet()) ->
|
||||
blocking().
|
||||
|
||||
account(#{account := V}) ->
|
||||
V.
|
||||
account(Wallet) ->
|
||||
maps:get(account, Wallet, undefined).
|
||||
|
||||
id(Wallet) ->
|
||||
ff_account:id(account(Wallet)).
|
||||
|
@ -15,7 +15,16 @@
|
||||
|
||||
-type st() :: ff_machine:st(wallet()).
|
||||
|
||||
-type params() :: #{
|
||||
id := id(),
|
||||
identity := ff_identity_machine:id(),
|
||||
name := binary(),
|
||||
currency := ff_currency:id(),
|
||||
external_id => id()
|
||||
}.
|
||||
|
||||
-export_type([id/0]).
|
||||
-export_type([params/0]).
|
||||
|
||||
-export([create/2]).
|
||||
-export([get/1]).
|
||||
@ -55,14 +64,6 @@ ctx(St) ->
|
||||
|
||||
%%
|
||||
|
||||
-type params() :: #{
|
||||
id := id(),
|
||||
identity := ff_identity_machine:id(),
|
||||
name := binary(),
|
||||
currency := ff_currency:id(),
|
||||
external_id => id()
|
||||
}.
|
||||
|
||||
-spec create(params(), ctx()) ->
|
||||
ok | {error, exists | ff_wallet:create_error() }.
|
||||
|
||||
|
60
apps/wapi/src/wapi_access_backend.erl
Normal file
60
apps/wapi/src/wapi_access_backend.erl
Normal file
@ -0,0 +1,60 @@
|
||||
-module(wapi_access_backend).
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_wallet_thrift.hrl").
|
||||
|
||||
-export([check_resource/3]).
|
||||
-export([check_resource_by_id/3]).
|
||||
|
||||
-type id() :: binary().
|
||||
-type resource_type() :: identity | wallet.
|
||||
-type handler_context() :: wapi_handler:context().
|
||||
-type data() ::
|
||||
ff_proto_identity_thrift:'Identity'() |
|
||||
ff_proto_wallet_thrift:'Wallet'().
|
||||
|
||||
-define(CTX_NS, <<"com.rbkmoney.wapi">>).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-spec check_resource(resource_type(), data(), handler_context()) ->
|
||||
ok | {error, unauthorized}.
|
||||
|
||||
check_resource(Resource, Data, Context) ->
|
||||
Owner = get_context(Resource, Data),
|
||||
check_resource_access(is_resource_owner(Owner, Context)).
|
||||
|
||||
-spec check_resource_by_id(resource_type(), id(), handler_context()) ->
|
||||
ok | {error, unauthorized}.
|
||||
|
||||
check_resource_by_id(Resource, ID, Context) ->
|
||||
Owner = get_context_by_id(Resource, ID, Context),
|
||||
check_resource_access(is_resource_owner(Owner, Context)).
|
||||
|
||||
%%
|
||||
%% Internal
|
||||
%%
|
||||
|
||||
get_context_by_id(Resource = identity, IdentityID, WoodyCtx) ->
|
||||
Request = {fistful_identity, 'Get', [IdentityID]},
|
||||
{ok, Identity} = wapi_handler_utils:service_call(Request, WoodyCtx),
|
||||
get_context(Resource, Identity);
|
||||
get_context_by_id(Resource = wallet, WalletID, WoodyCtx) ->
|
||||
Request = {fistful_wallet, 'Get', [WalletID]},
|
||||
{ok, Wallet} = wapi_handler_utils:service_call(Request, WoodyCtx),
|
||||
get_context(Resource, Wallet).
|
||||
|
||||
get_context(identity, Identity) ->
|
||||
#idnt_Identity{context = Ctx} = Identity,
|
||||
Context = ff_codec:unmarshal(context, Ctx),
|
||||
wapi_backend_utils:get_from_ctx(<<"owner">>, Context);
|
||||
get_context(wallet, Wallet) ->
|
||||
#wlt_Wallet{context = Ctx} = Wallet,
|
||||
Context = ff_codec:unmarshal(context, Ctx),
|
||||
wapi_backend_utils:get_from_ctx(<<"owner">>, Context).
|
||||
|
||||
is_resource_owner(Owner, HandlerCtx) ->
|
||||
Owner =:= wapi_handler_utils:get_owner(HandlerCtx).
|
||||
|
||||
check_resource_access(true) -> ok;
|
||||
check_resource_access(false) -> {error, unauthorized}.
|
97
apps/wapi/src/wapi_backend_utils.erl
Normal file
97
apps/wapi/src/wapi_backend_utils.erl
Normal file
@ -0,0 +1,97 @@
|
||||
-module(wapi_backend_utils).
|
||||
|
||||
-define(EXTERNAL_ID, <<"externalID">>).
|
||||
-define(CTX_NS, <<"com.rbkmoney.wapi">>).
|
||||
-define(PARAMS_HASH, <<"params_hash">>).
|
||||
-define(BENDER_DOMAIN, <<"wapi">>).
|
||||
|
||||
%% Context
|
||||
-type md() :: ff_entity_context:md().
|
||||
-type context() :: ff_entity_context:context().
|
||||
-type handler_context() :: wapi_handler:context().
|
||||
-type id() :: binary().
|
||||
-type hash() :: integer().
|
||||
-type params() :: map().
|
||||
-type gen_type() :: identity | identity_challenge | wallet.
|
||||
|
||||
-export([gen_id/3]).
|
||||
-export([gen_id/4]).
|
||||
-export([make_ctx/2]).
|
||||
-export([add_to_ctx/2]).
|
||||
-export([add_to_ctx/3]).
|
||||
-export([get_from_ctx/2]).
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-spec gen_id(gen_type(), params(), handler_context()) ->
|
||||
{ok, id()} | {error, {external_id_conflict, id()}}.
|
||||
|
||||
gen_id(Type, Params, Context) ->
|
||||
ExternalID = maps:get(?EXTERNAL_ID, Params, undefined),
|
||||
Hash = create_params_hash(Params),
|
||||
gen_id(Type, ExternalID, Hash, Context).
|
||||
|
||||
-spec gen_id(gen_type(), id() | undefined, hash(), handler_context()) ->
|
||||
{ok, id()} | {error, {external_id_conflict, id()}}.
|
||||
|
||||
gen_id(Type, ExternalID, Hash, Context) ->
|
||||
PartyID = wapi_handler_utils:get_owner(Context),
|
||||
IdempotentKey = bender_client:get_idempotent_key(?BENDER_DOMAIN, Type, PartyID, ExternalID),
|
||||
gen_id_by_type(Type, IdempotentKey, Hash, Context).
|
||||
|
||||
%@TODO: Bring back later
|
||||
%gen_id_by_type(withdrawal = Type, IdempotentKey, Hash, Context) ->
|
||||
% gen_snowflake_id(Type, IdempotentKey, Hash, Context);
|
||||
gen_id_by_type(Type, IdempotentKey, Hash, Context) ->
|
||||
gen_sequence_id(Type, IdempotentKey, Hash, Context).
|
||||
|
||||
%@TODO: Bring back later
|
||||
%gen_snowflake_id(_Type, IdempotentKey, Hash, #{woody_context := WoodyCtx}) ->
|
||||
% bender_client:gen_by_snowflake(IdempotentKey, Hash, WoodyCtx).
|
||||
gen_sequence_id(Type, IdempotentKey, Hash, #{woody_context := WoodyCtx}) ->
|
||||
BinType = atom_to_binary(Type, utf8),
|
||||
bender_client:gen_by_sequence(IdempotentKey, BinType, Hash, WoodyCtx).
|
||||
|
||||
-spec make_ctx(params(), handler_context()) ->
|
||||
context().
|
||||
|
||||
make_ctx(Params, Context) ->
|
||||
#{?CTX_NS => genlib_map:compact(#{
|
||||
<<"owner">> => wapi_handler_utils:get_owner(Context),
|
||||
<<"metadata">> => maps:get(<<"metadata">>, Params, undefined),
|
||||
?PARAMS_HASH => create_params_hash(Params)
|
||||
})}.
|
||||
|
||||
-spec add_to_ctx({md(), md() | undefined} | list() | map(), context()) ->
|
||||
context().
|
||||
|
||||
add_to_ctx({Key, Value}, Context) ->
|
||||
add_to_ctx(Key, Value, Context);
|
||||
add_to_ctx(Map, Context = #{?CTX_NS := Ctx}) when is_map(Map) ->
|
||||
Context#{?CTX_NS => maps:merge(Ctx, Map)};
|
||||
add_to_ctx(KVList, Context) when is_list(KVList) ->
|
||||
lists:foldl(
|
||||
fun({K, V}, Ctx) -> add_to_ctx(K, V, Ctx) end,
|
||||
Context,
|
||||
KVList
|
||||
).
|
||||
|
||||
-spec add_to_ctx(md(), md() | undefined, context()) ->
|
||||
context().
|
||||
|
||||
add_to_ctx(_Key, undefined, Context) ->
|
||||
Context;
|
||||
add_to_ctx(Key, Value, Context = #{?CTX_NS := Ctx}) ->
|
||||
Context#{?CTX_NS => Ctx#{Key => Value}}.
|
||||
|
||||
-spec get_from_ctx(md(), context()) ->
|
||||
md().
|
||||
|
||||
get_from_ctx(Key, #{?CTX_NS := Ctx}) ->
|
||||
maps:get(Key, Ctx, undefined).
|
||||
|
||||
-spec create_params_hash(term()) ->
|
||||
integer().
|
||||
|
||||
create_params_hash(Value) ->
|
||||
erlang:phash2(Value).
|
@ -46,6 +46,7 @@
|
||||
%% API
|
||||
|
||||
-define(request_result, wapi_req_result).
|
||||
-define(APP, wapi).
|
||||
|
||||
-spec handle_request(tag(), operation_id(), req_data(), swagger_context(), opts()) ->
|
||||
request_result().
|
||||
@ -71,7 +72,7 @@ process_request(Tag, OperationID, Req, SwagContext, Opts, WoodyContext) ->
|
||||
ok = ff_context:save(create_ff_context(WoodyContext, Opts)),
|
||||
|
||||
Context = create_handler_context(SwagContext, WoodyContext),
|
||||
Handler = get_handler(Tag),
|
||||
Handler = get_handler(Tag, genlib_app:env(?APP, transport)),
|
||||
case wapi_auth:authorize_operation(OperationID, Req, Context) of
|
||||
{ok, AuthDetails} ->
|
||||
ok = logger:info("Operation ~p authorized via ~p", [OperationID, AuthDetails]),
|
||||
@ -94,8 +95,9 @@ process_request(Tag, OperationID, Req, SwagContext, Opts, WoodyContext) ->
|
||||
throw_result(Res) ->
|
||||
erlang:throw({?request_result, Res}).
|
||||
|
||||
get_handler(wallet) -> wapi_wallet_handler;
|
||||
get_handler(payres) -> wapi_payres_handler.
|
||||
get_handler(wallet, thrift) -> wapi_wallet_thrift_handler;
|
||||
get_handler(wallet, _) -> wapi_wallet_handler;
|
||||
get_handler(payres, _) -> wapi_payres_handler.
|
||||
|
||||
-spec create_woody_context(tag(), req_data(), wapi_auth:context(), opts()) ->
|
||||
woody_context:ctx().
|
||||
@ -113,8 +115,6 @@ attach_deadline(undefined, Context) ->
|
||||
attach_deadline(Deadline, Context) ->
|
||||
woody_context:set_deadline(Deadline, Context).
|
||||
|
||||
-define(APP, wapi).
|
||||
|
||||
collect_user_identity(AuthContext, _Opts) ->
|
||||
genlib_map:compact(#{
|
||||
id => wapi_auth:get_subject_id(AuthContext),
|
||||
|
475
apps/wapi/src/wapi_identity_backend.erl
Normal file
475
apps/wapi/src/wapi_identity_backend.erl
Normal file
@ -0,0 +1,475 @@
|
||||
-module(wapi_identity_backend).
|
||||
|
||||
-type handler_context() :: wapi_handler:context().
|
||||
-type response_data() :: wapi_handler:response_data().
|
||||
-type params() :: map().
|
||||
-type id() :: binary().
|
||||
-type status() :: binary().
|
||||
-type result(T, E) :: {ok, T} | {error, E}.
|
||||
|
||||
-export([create_identity/2]).
|
||||
-export([get_identity/2]).
|
||||
-export([get_identities/2]).
|
||||
-export([create_identity_challenge/3]).
|
||||
-export([get_identity_challenge/3]).
|
||||
-export([get_identity_challenges/3]).
|
||||
-export([get_identity_challenge_events/2]).
|
||||
-export([get_identity_challenge_event/2]).
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-spec get_identity(id(), handler_context()) ->
|
||||
{ok, response_data()} |
|
||||
{error, {identity, notfound}} |
|
||||
{error, {identity, unauthorized}} .
|
||||
|
||||
get_identity(IdentityID, HandlerContext) ->
|
||||
Request = {fistful_identity, 'Get', [IdentityID]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, IdentityThrift} ->
|
||||
case wapi_access_backend:check_resource(identity, IdentityThrift, HandlerContext) of
|
||||
ok ->
|
||||
{ok, unmarshal(identity, IdentityThrift)};
|
||||
{error, unauthorized} ->
|
||||
{error, {identity, unauthorized}}
|
||||
end;
|
||||
{exception, #fistful_IdentityNotFound{}} ->
|
||||
{error, {identity, notfound}}
|
||||
end.
|
||||
|
||||
-spec create_identity(params(), handler_context()) -> result(map(),
|
||||
{provider, notfound} |
|
||||
{identity_class, notfound} |
|
||||
{external_id_conflict, id()} |
|
||||
inaccessible |
|
||||
_Unexpected
|
||||
).
|
||||
create_identity(Params, HandlerContext) ->
|
||||
case create_id(identity, Params, HandlerContext) of
|
||||
{ok, ID} ->
|
||||
create_identity(ID, Params, HandlerContext);
|
||||
{error, {external_id_conflict, _}} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
create_identity(ID, Params, HandlerContext) ->
|
||||
IdentityParams = marshal(identity_params, {
|
||||
Params#{<<"id">> => ID},
|
||||
wapi_handler_utils:get_owner(HandlerContext),
|
||||
create_context(Params, HandlerContext)
|
||||
}),
|
||||
Request = {fistful_identity, 'Create', [IdentityParams]},
|
||||
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, Identity} ->
|
||||
{ok, unmarshal(identity, Identity)};
|
||||
{exception, #fistful_ProviderNotFound{}} ->
|
||||
{error, {provider, notfound}};
|
||||
{exception, #fistful_IdentityClassNotFound{}} ->
|
||||
{error, {identity_class, notfound}};
|
||||
{exception, #fistful_PartyInaccessible{}} ->
|
||||
{error, inaccessible};
|
||||
{exception, #fistful_IDExists{}} ->
|
||||
get_identity(ID, HandlerContext);
|
||||
{exception, Details} ->
|
||||
{error, Details}
|
||||
end.
|
||||
|
||||
-spec get_identities(params(), handler_context()) -> no_return().
|
||||
get_identities(_Params, _Context) ->
|
||||
wapi_handler_utils:throw_not_implemented().
|
||||
|
||||
-spec create_identity_challenge(id(), params(), handler_context()) -> result(map(),
|
||||
{identity, notfound} |
|
||||
{identity, unauthorized} |
|
||||
{challenge, pending} |
|
||||
{challenge, {class, notfound}} |
|
||||
{challenge, {proof, notfound}} |
|
||||
{challenge, {proof, insufficient}} |
|
||||
{challenge, level} |
|
||||
{challenge, conflict} |
|
||||
{external_id_conflict, id()}
|
||||
).
|
||||
create_identity_challenge(IdentityID, Params, HandlerContext) ->
|
||||
case create_id(identity_challenge, Params, HandlerContext) of
|
||||
{ok, ID} ->
|
||||
create_identity_challenge(ID, IdentityID, Params, HandlerContext);
|
||||
{error, {external_id_conflict, _}} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
create_identity_challenge(ChallengeID, IdentityID, Params, HandlerContext) ->
|
||||
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
|
||||
ok ->
|
||||
ChallengeParams = marshal(challenge_params, {ChallengeID, Params}),
|
||||
Request = {fistful_identity, 'StartChallenge', [IdentityID, ChallengeParams]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, Challenge} ->
|
||||
{ok, unmarshal(challenge, {Challenge, HandlerContext})};
|
||||
{exception, #fistful_IdentityNotFound{}} ->
|
||||
{error, {identity, notfound}};
|
||||
{exception, #fistful_ChallengePending{}} ->
|
||||
{error, {challenge, pending}};
|
||||
{exception, #fistful_ChallengeClassNotFound{}} ->
|
||||
{error, {challenge, {class, notfound}}};
|
||||
{exception, #fistful_ProofNotFound{}} ->
|
||||
{error, {challenge, {proof, notfound}}};
|
||||
{exception, #fistful_ProofInsufficient{}} ->
|
||||
{error, {challenge, {proof, insufficient}}};
|
||||
{exception, #fistful_ChallengeLevelIncorrect{}} ->
|
||||
{error, {challenge, level}};
|
||||
{exception, #fistful_ChallengeConflict{}} ->
|
||||
{error, {challenge, conflict}};
|
||||
{exception, Details} ->
|
||||
{error, Details}
|
||||
end;
|
||||
{error, unauthorized} ->
|
||||
{error, {identity, unauthorized}}
|
||||
end.
|
||||
|
||||
-spec get_identity_challenge(id(), id(), handler_context()) -> result(map(),
|
||||
{identity, notfound} |
|
||||
{identity, unauthorized} |
|
||||
{challenge, notfound}
|
||||
).
|
||||
get_identity_challenge(IdentityID, ChallengeID, HandlerContext) ->
|
||||
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
|
||||
ok ->
|
||||
Request = {fistful_identity, 'GetChallenges', [IdentityID]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, Challenges} ->
|
||||
get_challenge_by_id(ChallengeID, Challenges, HandlerContext);
|
||||
{exception, #fistful_IdentityNotFound{}} ->
|
||||
{error, {identity, notfound}};
|
||||
{exception, Details} ->
|
||||
{error, Details}
|
||||
end;
|
||||
{error, unauthorized} ->
|
||||
{error, {identity, unauthorized}}
|
||||
end.
|
||||
|
||||
-spec get_identity_challenges(id(), status(), handler_context()) -> result(map(),
|
||||
{identity, notfound} |
|
||||
{identity, unauthorized} |
|
||||
{challenge, notfound}
|
||||
).
|
||||
get_identity_challenges(IdentityID, Status, HandlerContext) ->
|
||||
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
|
||||
ok ->
|
||||
Request = {fistful_identity, 'GetChallenges', [IdentityID]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, Challenges} ->
|
||||
Filtered = filter_challenges_by_status(Status, Challenges, HandlerContext, []),
|
||||
{ok, unmarshal({list, challenge}, Filtered)};
|
||||
{exception, #fistful_IdentityNotFound{}} ->
|
||||
{error, {identity, notfound}};
|
||||
{exception, Details} ->
|
||||
{error, Details}
|
||||
end;
|
||||
{error, unauthorized} ->
|
||||
{error, {identity, unauthorized}}
|
||||
end.
|
||||
|
||||
-spec get_identity_challenge_events(params(), handler_context()) -> result(map(),
|
||||
{identity, notfound} |
|
||||
{identity, unauthorized}
|
||||
).
|
||||
get_identity_challenge_events(Params = #{
|
||||
'identityID' := IdentityID,
|
||||
'challengeID' := ChallengeID,
|
||||
'limit' := Limit
|
||||
}, HandlerContext) ->
|
||||
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
|
||||
ok ->
|
||||
Cursor = maps:get('eventCursor', Params, undefined),
|
||||
EventRange = marshal(event_range, {Cursor, Limit}),
|
||||
Request = {fistful_identity, 'GetEvents', [IdentityID, EventRange]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, Events} ->
|
||||
Filtered = filter_events_by_challenge_id(ChallengeID, Events, []),
|
||||
{ok, unmarshal({list, identity_challenge_event}, Filtered)};
|
||||
{exception, #fistful_IdentityNotFound{}} ->
|
||||
{error, {identity, notfound}};
|
||||
{exception, Details} ->
|
||||
{error, Details}
|
||||
end;
|
||||
{error, unauthorized} ->
|
||||
{error, {identity, unauthorized}}
|
||||
end.
|
||||
|
||||
-spec get_identity_challenge_event(params(), handler_context()) -> result(map(),
|
||||
{identity, notfound} |
|
||||
{identity, unauthorized} |
|
||||
{event, notfound}
|
||||
).
|
||||
get_identity_challenge_event(Params = #{
|
||||
'identityID' := IdentityID
|
||||
}, HandlerContext) ->
|
||||
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
|
||||
ok ->
|
||||
get_identity_challenge_event_(Params, HandlerContext);
|
||||
{error, unauthorized} ->
|
||||
{error, {identity, unauthorized}}
|
||||
end.
|
||||
|
||||
get_identity_challenge_event_(#{
|
||||
'identityID' := IdentityID,
|
||||
'challengeID' := ChallengeID,
|
||||
'eventID' := EventId
|
||||
}, HandlerContext) ->
|
||||
EventRange = marshal(event_range, {EventId - 1, 1}),
|
||||
Request = {fistful_identity, 'GetEvents', [IdentityID, EventRange]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, []} ->
|
||||
{error, {event, notfound}};
|
||||
{ok, Events} ->
|
||||
case filter_events_by_challenge_id(ChallengeID, Events, []) of
|
||||
[Event] ->
|
||||
{ok, unmarshal(identity_challenge_event, Event)};
|
||||
_ ->
|
||||
{error, {event, notfound}}
|
||||
end;
|
||||
{exception, #fistful_IdentityNotFound{}} ->
|
||||
{error, {identity, notfound}};
|
||||
{exception, Details} ->
|
||||
{error, Details}
|
||||
end.
|
||||
|
||||
%%
|
||||
%% Internal
|
||||
%%
|
||||
|
||||
filter_events_by_challenge_id(_ID, [], Result) ->
|
||||
Result;
|
||||
filter_events_by_challenge_id(
|
||||
ID, [
|
||||
#idnt_IdentityEvent{
|
||||
change = {identity_challenge, #idnt_ChallengeChange{
|
||||
id = ID,
|
||||
payload = {status_changed, _Status} = Payload
|
||||
}},
|
||||
occured_at = OccuredAt,
|
||||
sequence = EventID
|
||||
} |
|
||||
Rest
|
||||
],
|
||||
Acc
|
||||
) ->
|
||||
filter_events_by_challenge_id(ID, Rest, [{EventID, OccuredAt, Payload} | Acc]);
|
||||
filter_events_by_challenge_id(ID, [_H | Rest], Acc) ->
|
||||
filter_events_by_challenge_id(ID, Rest, Acc).
|
||||
|
||||
get_challenge_by_id(_ID, [], _) ->
|
||||
{error, {challenge, notfound}};
|
||||
get_challenge_by_id(ID, [Challenge = #idnt_Challenge{id = ID} | _Rest], HandlerContext) ->
|
||||
{ok, unmarshal(challenge, {Challenge, HandlerContext})};
|
||||
get_challenge_by_id(ID, [_Challenge | Rest], HandlerContext) ->
|
||||
get_challenge_by_id(ID, Rest, HandlerContext).
|
||||
|
||||
filter_challenges_by_status(undefined, Challenges, HandlerContext, _) ->
|
||||
[{Challenge, HandlerContext} || Challenge <- Challenges];
|
||||
filter_challenges_by_status(_Status, [], _, Result) ->
|
||||
Result;
|
||||
filter_challenges_by_status(
|
||||
FilteringStatus,
|
||||
[Challenge = #idnt_Challenge{status = Status} | Rest],
|
||||
HandlerContext,
|
||||
Acc
|
||||
) ->
|
||||
ChallengeStatus = maps:get(<<"status">>, unmarshal(challenge_status, Status), undefined),
|
||||
case ChallengeStatus =:= FilteringStatus of
|
||||
false ->
|
||||
filter_challenges_by_status(FilteringStatus, Rest, HandlerContext, Acc);
|
||||
true ->
|
||||
filter_challenges_by_status(FilteringStatus, Rest, HandlerContext, [{Challenge, HandlerContext} | Acc])
|
||||
end.
|
||||
|
||||
enrich_proofs(Proofs, HandlerContext) ->
|
||||
[enrich_proof(unmarshal(proof, P), HandlerContext) || P <- Proofs].
|
||||
|
||||
enrich_proof(#{<<"token">> := Token}, HandlerContext) ->
|
||||
wapi_privdoc_backend:get_proof(Token, HandlerContext).
|
||||
|
||||
create_id(Type, Params, HandlerContext) ->
|
||||
wapi_backend_utils:gen_id(
|
||||
Type,
|
||||
Params,
|
||||
HandlerContext
|
||||
).
|
||||
|
||||
create_context(Params, HandlerContext) ->
|
||||
KV = {<<"name">>, maps:get(<<"name">>, Params, undefined)},
|
||||
wapi_backend_utils:add_to_ctx(KV, wapi_backend_utils:make_ctx(Params, HandlerContext)).
|
||||
|
||||
service_call(Params, Ctx) ->
|
||||
wapi_handler_utils:service_call(Params, Ctx).
|
||||
|
||||
%% Marshaling
|
||||
|
||||
marshal({list, Type}, List) ->
|
||||
lists:map(fun(V) -> marshal(Type, V) end, List);
|
||||
|
||||
marshal(identity_params, {Params = #{
|
||||
<<"id">> := ID,
|
||||
<<"provider">> := Provider,
|
||||
<<"class">> := Class
|
||||
}, Owner, Context}) ->
|
||||
ExternalID = maps:get(<<"externalID">>, Params, undefined),
|
||||
#idnt_IdentityParams{
|
||||
id = marshal(id, ID),
|
||||
party = marshal(id, Owner),
|
||||
provider = marshal(string, Provider),
|
||||
cls = marshal(string, Class),
|
||||
external_id = marshal(id, ExternalID),
|
||||
context = marshal(context, Context)
|
||||
};
|
||||
|
||||
marshal(challenge_params, {ID, #{
|
||||
<<"type">> := Class,
|
||||
<<"proofs">> := Proofs
|
||||
}}) ->
|
||||
#idnt_ChallengeParams{
|
||||
id = marshal(id, ID),
|
||||
cls = marshal(id, Class),
|
||||
proofs = marshal({list, proof}, Proofs)
|
||||
};
|
||||
|
||||
marshal(proof, #{<<"token">> := WapiToken}) ->
|
||||
try
|
||||
#{<<"type">> := Type, <<"token">> := Token} = wapi_utils:base64url_to_map(WapiToken),
|
||||
#idnt_ChallengeProof{
|
||||
type = marshal(proof_type, Type),
|
||||
token = marshal(string, Token)
|
||||
}
|
||||
catch
|
||||
error:badarg ->
|
||||
wapi_handler:throw_result(wapi_handler_utils:reply_error(
|
||||
422,
|
||||
wapi_handler_utils:get_error_msg(io_lib:format("Invalid proof token: ~p", [WapiToken]))
|
||||
))
|
||||
end;
|
||||
|
||||
marshal(event_range, {Cursor, Limit}) ->
|
||||
#'EventRange'{
|
||||
'after' = marshal(integer, Cursor),
|
||||
'limit' = marshal(integer, Limit)
|
||||
};
|
||||
|
||||
marshal(context, Ctx) ->
|
||||
ff_codec:marshal(context, Ctx);
|
||||
|
||||
marshal(proof_type, <<"RUSDomesticPassport">>) ->
|
||||
rus_domestic_passport;
|
||||
marshal(proof_type, <<"RUSRetireeInsuranceCertificate">>) ->
|
||||
rus_retiree_insurance_cert;
|
||||
|
||||
marshal(T, V) ->
|
||||
ff_codec:marshal(T, V).
|
||||
|
||||
%%
|
||||
|
||||
unmarshal({list, Type}, List) ->
|
||||
lists:map(fun(V) -> unmarshal(Type, V) end, List);
|
||||
|
||||
unmarshal(identity, #idnt_Identity{
|
||||
id = IdentityID,
|
||||
blocking = Blocking,
|
||||
cls = Class,
|
||||
provider = Provider,
|
||||
level = Level,
|
||||
effective_challenge = EffectiveChallenge,
|
||||
external_id = ExternalID,
|
||||
created_at = CreatedAt,
|
||||
context = Ctx
|
||||
}) ->
|
||||
Context = unmarshal(context, Ctx),
|
||||
genlib_map:compact(#{
|
||||
<<"id">> => unmarshal(id, IdentityID),
|
||||
<<"name">> => wapi_backend_utils:get_from_ctx(<<"name">>, Context),
|
||||
<<"createdAt">> => maybe_unmarshal(string, CreatedAt),
|
||||
<<"isBlocked">> => maybe_unmarshal(blocking, Blocking),
|
||||
<<"class">> => unmarshal(string, Class),
|
||||
<<"provider">> => unmarshal(id, Provider),
|
||||
<<"level">> => maybe_unmarshal(id, Level),
|
||||
<<"effectiveChallenge">> => maybe_unmarshal(id, EffectiveChallenge),
|
||||
<<"externalID">> => maybe_unmarshal(id, ExternalID),
|
||||
<<"metadata">> => wapi_backend_utils:get_from_ctx(<<"metadata">>, Context)
|
||||
});
|
||||
|
||||
unmarshal(challenge, {#idnt_Challenge{
|
||||
id = ID,
|
||||
cls = Class,
|
||||
proofs = Proofs,
|
||||
status = Status
|
||||
}, HandlerContext}) ->
|
||||
genlib_map:compact(maps:merge(#{
|
||||
<<"id">> => unmarshal(id, ID),
|
||||
<<"type">> => unmarshal(id, Class),
|
||||
<<"proofs">> => enrich_proofs(Proofs, HandlerContext)
|
||||
}, unmarshal(challenge_status, Status)));
|
||||
|
||||
unmarshal(challenge_status, {pending, #idnt_ChallengePending{}}) ->
|
||||
#{<<"status">> => <<"Pending">>};
|
||||
unmarshal(challenge_status, {cancelled, #idnt_ChallengeCancelled{}}) ->
|
||||
#{<<"status">> => <<"Cancelled">>};
|
||||
unmarshal(challenge_status, {completed, #idnt_ChallengeCompleted{
|
||||
valid_until = Time,
|
||||
resolution = approved
|
||||
}}) ->
|
||||
#{
|
||||
<<"status">> => <<"Completed">>,
|
||||
<<"validUntil">> => unmarshal(string, Time)
|
||||
};
|
||||
unmarshal(challenge_status, {completed, #idnt_ChallengeCompleted{
|
||||
resolution = denied
|
||||
}}) ->
|
||||
%% TODO Add denied reason to proto
|
||||
unmarshal(challenge_status, {failed, #idnt_ChallengeFailed{}});
|
||||
unmarshal(challenge_status, {failed, #idnt_ChallengeFailed{}}) ->
|
||||
#{
|
||||
<<"status">> => <<"Failed">>,
|
||||
<<"failureReason">> => <<"Denied">>
|
||||
};
|
||||
|
||||
unmarshal(proof, #idnt_ChallengeProof{
|
||||
type = Type,
|
||||
token = Token
|
||||
}) ->
|
||||
genlib_map:compact(#{
|
||||
<<"type">> => unmarshal(proof_type, Type),
|
||||
<<"token">> => unmarshal(string, Token)
|
||||
});
|
||||
|
||||
unmarshal(proof_type, rus_domestic_passport) ->
|
||||
<<"RUSDomesticPassport">>;
|
||||
unmarshal(proof_type, rus_retiree_insurance_cert) ->
|
||||
<<"RUSRetireeInsuranceCertificate">>;
|
||||
|
||||
unmarshal(identity_challenge_event, {ID, Ts, V}) ->
|
||||
#{
|
||||
<<"eventID">> => unmarshal(integer, ID),
|
||||
<<"occuredAt">> => unmarshal(string, Ts),
|
||||
<<"changes">> => [unmarshal(identity_challenge_event_change, V)]
|
||||
};
|
||||
|
||||
unmarshal(identity_challenge_event_change, {status_changed, S}) ->
|
||||
maps:merge(
|
||||
#{<<"type">> => <<"IdentityChallengeStatusChanged">>},
|
||||
unmarshal(challenge_status, S)
|
||||
);
|
||||
|
||||
unmarshal(blocking, unblocked) ->
|
||||
false;
|
||||
unmarshal(blocking, blocked) ->
|
||||
true;
|
||||
|
||||
unmarshal(T, V) ->
|
||||
ff_codec:unmarshal(T, V).
|
||||
|
||||
maybe_unmarshal(_, undefined) ->
|
||||
undefined;
|
||||
maybe_unmarshal(T, V) ->
|
||||
unmarshal(T, V).
|
153
apps/wapi/src/wapi_wallet_backend.erl
Normal file
153
apps/wapi/src/wapi_wallet_backend.erl
Normal file
@ -0,0 +1,153 @@
|
||||
-module(wapi_wallet_backend).
|
||||
|
||||
-type req_data() :: wapi_handler:req_data().
|
||||
-type handler_context() :: wapi_handler:context().
|
||||
-type response_data() :: wapi_handler:response_data().
|
||||
-type id() :: binary().
|
||||
|
||||
-export([create/2]).
|
||||
-export([get/2]).
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_wallet_thrift.hrl").
|
||||
|
||||
%% Pipeline
|
||||
|
||||
-spec create(req_data(), handler_context()) ->
|
||||
{ok, response_data()} | {error, WalletError}
|
||||
when WalletError ::
|
||||
{identity, unauthorized} |
|
||||
{identity, notfound} |
|
||||
{currency, notfound} |
|
||||
inaccessible |
|
||||
{external_id_conflict, id()}.
|
||||
|
||||
create(Params = #{<<"identity">> := IdentityID}, HandlerContext) ->
|
||||
case wapi_access_backend:check_resource_by_id(identity, IdentityID, HandlerContext) of
|
||||
ok ->
|
||||
case wapi_backend_utils:gen_id(wallet, Params, HandlerContext) of
|
||||
{ok, ID} ->
|
||||
Context = wapi_backend_utils:make_ctx(Params, HandlerContext),
|
||||
PreparedParams = genlib_map:compact(Params#{
|
||||
<<"id">> => ID,
|
||||
<<"context">> => Context
|
||||
}),
|
||||
create(ID, PreparedParams, HandlerContext);
|
||||
{error, {external_id_conflict, _}} = Error ->
|
||||
Error
|
||||
end;
|
||||
{error, unauthorized} ->
|
||||
{error, {identity, unauthorized}}
|
||||
end.
|
||||
|
||||
create(WalletID, Params, HandlerContext) ->
|
||||
WalletParams = marshal(wallet_params, Params#{<<"id">> => WalletID}),
|
||||
Request = {fistful_wallet, 'Create', [WalletParams]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, Wallet} ->
|
||||
{ok, unmarshal(wallet, Wallet)};
|
||||
{exception, #fistful_CurrencyNotFound{}} ->
|
||||
{error, {currency, notfound}};
|
||||
{exception, #fistful_PartyInaccessible{}} ->
|
||||
{error, inaccessible};
|
||||
{exception, #fistful_IDExists{}} ->
|
||||
get(WalletID, HandlerContext);
|
||||
{exception, Details} ->
|
||||
{error, Details}
|
||||
end.
|
||||
|
||||
-spec get(id(), handler_context()) ->
|
||||
{ok, response_data()} |
|
||||
{error, {wallet, notfound}} |
|
||||
{error, {wallet, unauthorized}}.
|
||||
|
||||
get(WalletID, HandlerContext) ->
|
||||
Request = {fistful_wallet, 'Get', [WalletID]},
|
||||
case service_call(Request, HandlerContext) of
|
||||
{ok, WalletThrift} ->
|
||||
case wapi_access_backend:check_resource(wallet, WalletThrift, HandlerContext) of
|
||||
ok ->
|
||||
{ok, unmarshal(wallet, WalletThrift)};
|
||||
{error, unauthorized} ->
|
||||
{error, {wallet, unauthorized}}
|
||||
end;
|
||||
{exception, #fistful_WalletNotFound{}} ->
|
||||
{error, {wallet, notfound}}
|
||||
end.
|
||||
|
||||
%%
|
||||
%% Internal
|
||||
%%
|
||||
|
||||
service_call(Params, Ctx) ->
|
||||
wapi_handler_utils:service_call(Params, Ctx).
|
||||
|
||||
%% Marshaling
|
||||
|
||||
marshal(wallet_params, Params = #{
|
||||
<<"id">> := ID,
|
||||
<<"name">> := Name,
|
||||
<<"identity">> := IdentityID,
|
||||
<<"currency">> := CurrencyID,
|
||||
<<"context">> := Ctx
|
||||
}) ->
|
||||
ExternalID = maps:get(<<"externalID">>, Params, undefined),
|
||||
#wlt_WalletParams{
|
||||
id = marshal(id, ID),
|
||||
name = marshal(string, Name),
|
||||
account_params = marshal(account_params, {IdentityID, CurrencyID}),
|
||||
external_id = marshal(id, ExternalID),
|
||||
context = marshal(context, Ctx)
|
||||
};
|
||||
|
||||
marshal(account_params, {IdentityID, CurrencyID}) ->
|
||||
#account_AccountParams{
|
||||
identity_id = marshal(id, IdentityID),
|
||||
symbolic_code = marshal(string, CurrencyID)
|
||||
};
|
||||
|
||||
marshal(context, Ctx) ->
|
||||
ff_codec:marshal(context, Ctx);
|
||||
|
||||
marshal(T, V) ->
|
||||
ff_codec:marshal(T, V).
|
||||
|
||||
unmarshal(wallet, #wlt_Wallet{
|
||||
id = WalletID,
|
||||
name = Name,
|
||||
blocking = Blocking,
|
||||
account = Account,
|
||||
external_id = ExternalID,
|
||||
created_at = CreatedAt,
|
||||
context = Ctx
|
||||
}) ->
|
||||
#{
|
||||
identity := Identity,
|
||||
currency := Currency
|
||||
} = unmarshal(account, Account),
|
||||
Context = unmarshal(context, Ctx),
|
||||
genlib_map:compact(#{
|
||||
<<"id">> => unmarshal(id, WalletID),
|
||||
<<"name">> => unmarshal(string, Name),
|
||||
<<"isBlocked">> => unmarshal(blocking, Blocking),
|
||||
<<"identity">> => Identity,
|
||||
<<"currency">> => Currency,
|
||||
<<"createdAt">> => CreatedAt,
|
||||
<<"externalID">> => maybe_unmarshal(id, ExternalID),
|
||||
<<"metadata">> => wapi_backend_utils:get_from_ctx(<<"metadata">>, Context)
|
||||
});
|
||||
|
||||
unmarshal(blocking, unblocked) ->
|
||||
false;
|
||||
unmarshal(blocking, blocked) ->
|
||||
true;
|
||||
|
||||
unmarshal(context, Ctx) ->
|
||||
ff_codec:unmarshal(context, Ctx);
|
||||
|
||||
unmarshal(T, V) ->
|
||||
ff_codec:unmarshal(T, V).
|
||||
|
||||
maybe_unmarshal(_, undefined) ->
|
||||
undefined;
|
||||
maybe_unmarshal(T, V) ->
|
||||
unmarshal(T, V).
|
148
apps/wapi/src/wapi_wallet_thrift_handler.erl
Normal file
148
apps/wapi/src/wapi_wallet_thrift_handler.erl
Normal file
@ -0,0 +1,148 @@
|
||||
-module(wapi_wallet_thrift_handler).
|
||||
|
||||
-behaviour(swag_server_wallet_logic_handler).
|
||||
-behaviour(wapi_handler).
|
||||
|
||||
%% swag_server_wallet_logic_handler callbacks
|
||||
-export([authorize_api_key/3]).
|
||||
-export([handle_request/4]).
|
||||
|
||||
%% wapi_handler callbacks
|
||||
-export([process_request/4]).
|
||||
|
||||
%% Types
|
||||
|
||||
-type req_data() :: wapi_handler:req_data().
|
||||
-type handler_context() :: wapi_handler:context().
|
||||
-type request_result() :: wapi_handler:request_result().
|
||||
-type operation_id() :: swag_server_wallet:operation_id().
|
||||
-type api_key() :: swag_server_wallet:api_key().
|
||||
-type request_context() :: swag_server_wallet:request_context().
|
||||
-type handler_opts() :: swag_server_wallet:handler_opts(_).
|
||||
|
||||
%% API
|
||||
|
||||
-spec authorize_api_key(operation_id(), api_key(), handler_opts()) ->
|
||||
false | {true, wapi_auth:context()}.
|
||||
authorize_api_key(OperationID, ApiKey, Opts) ->
|
||||
ok = scoper:add_meta(#{api => wallet, operation_id => OperationID}),
|
||||
wapi_auth:authorize_api_key(OperationID, ApiKey, Opts).
|
||||
|
||||
-spec handle_request(swag_server_wallet:operation_id(), req_data(), request_context(), handler_opts()) ->
|
||||
request_result().
|
||||
handle_request(OperationID, Req, SwagContext, Opts) ->
|
||||
wapi_handler:handle_request(wallet, OperationID, Req, SwagContext, Opts).
|
||||
|
||||
-spec process_request(operation_id(), req_data(), handler_context(), handler_opts()) ->
|
||||
request_result().
|
||||
|
||||
%% Identities
|
||||
process_request('GetIdentity', #{'identityID' := IdentityId}, Context, _Opts) ->
|
||||
case wapi_identity_backend:get_identity(IdentityId, Context) of
|
||||
{ok, Identity} -> wapi_handler_utils:reply_ok(200, Identity);
|
||||
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
|
||||
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
process_request('CreateIdentity', #{'Identity' := Params}, Context, Opts) ->
|
||||
case wapi_identity_backend:create_identity(Params, Context) of
|
||||
{ok, Identity = #{<<"id">> := IdentityId}} ->
|
||||
wapi_handler_utils:reply_ok(201, Identity, get_location('GetIdentity', [IdentityId], Opts));
|
||||
{error, {provider, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such provider">>));
|
||||
{error, {identity_class, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such identity class">>));
|
||||
{error, inaccessible} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Identity inaccessible">>));
|
||||
{error, {external_id_conflict, ID}} ->
|
||||
wapi_handler_utils:reply_error(409, #{<<"id">> => ID})
|
||||
end;
|
||||
process_request('ListIdentityChallenges', #{'identityID' := Id, 'status' := Status}, Context, _Opts) ->
|
||||
case wapi_identity_backend:get_identity_challenges(Id, Status, Context) of
|
||||
{ok, Challenges} -> wapi_handler_utils:reply_ok(200, Challenges);
|
||||
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
|
||||
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
process_request('StartIdentityChallenge', #{
|
||||
'identityID' := IdentityId,
|
||||
'IdentityChallenge' := Params
|
||||
}, Context, Opts) ->
|
||||
case wapi_identity_backend:create_identity_challenge(IdentityId, Params, Context) of
|
||||
{ok, Challenge = #{<<"id">> := ChallengeId}} ->
|
||||
wapi_handler_utils:reply_ok(202, Challenge, get_location('GetIdentityChallenge', [ChallengeId], Opts));
|
||||
{error, {identity, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(404);
|
||||
{error, {identity, unauthorized}} ->
|
||||
wapi_handler_utils:reply_ok(404);
|
||||
{error, {challenge, conflict}} ->
|
||||
wapi_handler_utils:reply_ok(409);
|
||||
{error, {external_id_conflict, ID}} ->
|
||||
wapi_handler_utils:reply_ok(409, #{<<"id">> => ID});
|
||||
{error, {challenge, pending}} ->
|
||||
wapi_handler_utils:reply_ok(409);
|
||||
{error, {challenge, {class, notfound}}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such challenge type">>));
|
||||
{error, {challenge, {proof, notfound}}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Proof not found">>));
|
||||
{error, {challenge, {proof, insufficient}}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Insufficient proof">>));
|
||||
{error, {challenge, level}} ->
|
||||
wapi_handler_utils:reply_ok(422,
|
||||
wapi_handler_utils:get_error_msg(<<"Illegal identification type for current identity level">>)
|
||||
)
|
||||
%% TODO any other possible errors here?
|
||||
end;
|
||||
process_request('GetIdentityChallenge', #{
|
||||
'identityID' := IdentityId,
|
||||
'challengeID' := ChallengeId
|
||||
}, Context, _Opts) ->
|
||||
case wapi_identity_backend:get_identity_challenge(IdentityId, ChallengeId, Context) of
|
||||
{ok, Challenge} -> wapi_handler_utils:reply_ok(200, Challenge);
|
||||
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
|
||||
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404);
|
||||
{error, {challenge, notfound}} -> wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
process_request('PollIdentityChallengeEvents', Params, Context, _Opts) ->
|
||||
case wapi_identity_backend:get_identity_challenge_events(Params, Context) of
|
||||
{ok, Events} -> wapi_handler_utils:reply_ok(200, Events);
|
||||
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
|
||||
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
process_request('GetIdentityChallengeEvent', Params, Context, _Opts) ->
|
||||
case wapi_identity_backend:get_identity_challenge_event(Params, Context) of
|
||||
{ok, Event} -> wapi_handler_utils:reply_ok(200, Event);
|
||||
{error, {identity, notfound}} -> wapi_handler_utils:reply_ok(404);
|
||||
{error, {identity, unauthorized}} -> wapi_handler_utils:reply_ok(404);
|
||||
{error, {event, notfound}} -> wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
|
||||
%% Wallets
|
||||
|
||||
process_request('GetWallet', #{'walletID' := WalletId}, Context, _Opts) ->
|
||||
case wapi_wallet_backend:get(WalletId, Context) of
|
||||
{ok, Wallet} -> wapi_handler_utils:reply_ok(200, Wallet);
|
||||
{error, {wallet, notfound}} -> wapi_handler_utils:reply_ok(404);
|
||||
{error, {wallet, unauthorized}} -> wapi_handler_utils:reply_ok(404)
|
||||
end;
|
||||
process_request('CreateWallet', #{'Wallet' := Params}, Context, Opts) ->
|
||||
case wapi_wallet_backend:create(Params, Context) of
|
||||
{ok, Wallet = #{<<"id">> := WalletId}} ->
|
||||
wapi_handler_utils:reply_ok(201, Wallet, get_location('GetWallet', [WalletId], Opts));
|
||||
{error, {identity, unauthorized}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such identity">>));
|
||||
{error, {identity, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"No such identity">>));
|
||||
{error, {currency, notfound}} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Currency not supported">>));
|
||||
{error, inaccessible} ->
|
||||
wapi_handler_utils:reply_ok(422, wapi_handler_utils:get_error_msg(<<"Identity inaccessible">>));
|
||||
{error, {external_id_conflict, ID}} ->
|
||||
wapi_handler_utils:reply_error(409, #{<<"id">> => ID})
|
||||
end;
|
||||
process_request(OperationID, Params, Context, Opts) ->
|
||||
wapi_wallet_handler:process_request(OperationID, Params, Context, Opts).
|
||||
|
||||
%% Internal functions
|
||||
|
||||
get_location(OperationId, Params, Opts) ->
|
||||
#{path := PathSpec} = swag_server_wallet_router:get_operation(OperationId),
|
||||
wapi_handler_utils:get_location(PathSpec, Params, Opts).
|
317
apps/wapi/test/wapi_identity_tests_SUITE.erl
Normal file
317
apps/wapi/test/wapi_identity_tests_SUITE.erl
Normal file
@ -0,0 +1,317 @@
|
||||
-module(wapi_identity_tests_SUITE).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-include_lib("damsel/include/dmsl_webhooker_thrift.hrl").
|
||||
|
||||
-include_lib("jose/include/jose_jwk.hrl").
|
||||
-include_lib("wapi_wallet_dummy_data.hrl").
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_identity_thrift.hrl").
|
||||
-include_lib("identdocstore_proto/include/identdocstore_identity_document_storage_thrift.hrl").
|
||||
|
||||
-export([all/0]).
|
||||
-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]).
|
||||
|
||||
-export([init/1]).
|
||||
|
||||
-export([
|
||||
create_identity/1,
|
||||
get_identity/1,
|
||||
create_identity_challenge/1,
|
||||
get_identity_challenge/1,
|
||||
list_identity_challenges/1,
|
||||
get_identity_challenge_event/1,
|
||||
poll_identity_challenge_events/1
|
||||
]).
|
||||
|
||||
-define(badresp(Code), {error, {invalid_response_code, Code}}).
|
||||
-define(emptyresp(Code), {error, {Code, #{}}}).
|
||||
|
||||
-type test_case_name() :: atom().
|
||||
-type config() :: [{atom(), any()}].
|
||||
-type group_name() :: atom().
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
-spec init([]) ->
|
||||
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
init([]) ->
|
||||
{ok, {#{strategy => one_for_all, intensity => 1, period => 1}, []}}.
|
||||
|
||||
-spec all() ->
|
||||
[test_case_name()].
|
||||
all() ->
|
||||
[
|
||||
{group, base}
|
||||
].
|
||||
|
||||
-spec groups() ->
|
||||
[{group_name(), list(), [test_case_name()]}].
|
||||
groups() ->
|
||||
[
|
||||
{base, [],
|
||||
[
|
||||
create_identity,
|
||||
get_identity,
|
||||
create_identity_challenge,
|
||||
get_identity_challenge,
|
||||
list_identity_challenges,
|
||||
get_identity_challenge_event,
|
||||
poll_identity_challenge_events
|
||||
]
|
||||
}
|
||||
].
|
||||
|
||||
%%
|
||||
%% starting/stopping
|
||||
%%
|
||||
-spec init_per_suite(config()) ->
|
||||
config().
|
||||
init_per_suite(Config) ->
|
||||
%% TODO remove this after cut off wapi
|
||||
ok = application:set_env(wapi, transport, thrift),
|
||||
ct_helper:makeup_cfg([
|
||||
ct_helper:test_case_name(init),
|
||||
ct_payment_system:setup(#{
|
||||
optional_apps => [
|
||||
bender_client,
|
||||
wapi,
|
||||
wapi_woody_client
|
||||
]
|
||||
})
|
||||
], Config).
|
||||
|
||||
-spec end_per_suite(config()) ->
|
||||
_.
|
||||
end_per_suite(C) ->
|
||||
%% TODO remove this after cut off wapi
|
||||
ok = application:unset_env(wapi, transport),
|
||||
ok = ct_payment_system:shutdown(C).
|
||||
|
||||
-spec init_per_group(group_name(), config()) ->
|
||||
config().
|
||||
init_per_group(Group, Config) when Group =:= base ->
|
||||
ok = ff_context:save(ff_context:create(#{
|
||||
party_client => party_client:create_client(),
|
||||
woody_context => woody_context:new(<<"init_per_group/", (atom_to_binary(Group, utf8))/binary>>)
|
||||
})),
|
||||
Party = create_party(Config),
|
||||
Token = issue_token(Party, [{[party], write}], unlimited),
|
||||
Config1 = [{party, Party} | Config],
|
||||
[{context, wapi_ct_helper:get_context(Token)} | Config1];
|
||||
init_per_group(_, Config) ->
|
||||
Config.
|
||||
|
||||
-spec end_per_group(group_name(), config()) ->
|
||||
_.
|
||||
end_per_group(_Group, _C) ->
|
||||
ok.
|
||||
|
||||
-spec init_per_testcase(test_case_name(), config()) ->
|
||||
config().
|
||||
init_per_testcase(Name, C) ->
|
||||
C1 = ct_helper:makeup_cfg([ct_helper:test_case_name(Name), ct_helper:woody_ctx()], C),
|
||||
ok = ct_helper:set_context(C1),
|
||||
[{test_sup, wapi_ct_helper:start_mocked_service_sup(?MODULE)} | C1].
|
||||
|
||||
-spec end_per_testcase(test_case_name(), config()) ->
|
||||
config().
|
||||
end_per_testcase(_Name, C) ->
|
||||
ok = ct_helper:unset_context(),
|
||||
wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)),
|
||||
ok.
|
||||
|
||||
%%% Tests
|
||||
-spec create_identity(config()) ->
|
||||
_.
|
||||
create_identity(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_identity, fun('Create', _) -> {ok, ?IDENTITY(PartyID)} end}
|
||||
], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_identities_api:create_identity/3,
|
||||
#{
|
||||
body => #{
|
||||
<<"name">> => ?STRING,
|
||||
<<"class">> => ?STRING,
|
||||
<<"provider">> => ?STRING
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_identity(config()) ->
|
||||
_.
|
||||
get_identity(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)} end}
|
||||
], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_identities_api:get_identity/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => ?STRING
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec create_identity_challenge(config()) ->
|
||||
_.
|
||||
create_identity_challenge(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_identity, fun
|
||||
('Get', _) -> {ok, ?IDENTITY(PartyID)};
|
||||
('StartChallenge', _) -> {ok, ?IDENTITY_CHALLENGE(?IDENTITY_CHALLENGE_STATUS_COMPLETED)}
|
||||
end},
|
||||
{identdoc_storage, fun('Get', _) -> {ok, ?IDENT_DOC} end}
|
||||
], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_identities_api:start_identity_challenge/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => ?STRING
|
||||
},
|
||||
body => #{
|
||||
<<"type">> => <<"sword-initiation">>,
|
||||
<<"proofs">> => [
|
||||
#{
|
||||
<<"token">> => wapi_utils:map_to_base64url(#{
|
||||
<<"type">> => <<"RUSRetireeInsuranceCertificate">>,
|
||||
<<"token">> => ?STRING
|
||||
})
|
||||
},
|
||||
#{
|
||||
<<"token">> => wapi_utils:map_to_base64url(#{
|
||||
<<"type">> => <<"RUSDomesticPassport">>,
|
||||
<<"token">> => ?STRING
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_identity_challenge(config()) ->
|
||||
_.
|
||||
get_identity_challenge(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_identity, fun
|
||||
('Get', _) -> {ok, ?IDENTITY(PartyID)};
|
||||
('GetChallenges', _) -> {ok, [?IDENTITY_CHALLENGE(?IDENTITY_CHALLENGE_STATUS_COMPLETED)]}
|
||||
end},
|
||||
{identdoc_storage, fun('Get', _) -> {ok, ?IDENT_DOC} end}
|
||||
], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_identities_api:get_identity_challenge/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => ?STRING,
|
||||
<<"challengeID">> => ?STRING
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec list_identity_challenges(config()) ->
|
||||
_.
|
||||
list_identity_challenges(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_identity, fun
|
||||
('Get', _) -> {ok, ?IDENTITY(PartyID)};
|
||||
('GetChallenges', _) -> {ok, [?IDENTITY_CHALLENGE(?IDENTITY_CHALLENGE_STATUS_COMPLETED)]}
|
||||
end},
|
||||
{identdoc_storage, fun('Get', _) -> {ok, ?IDENT_DOC} end}
|
||||
], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_identities_api:list_identity_challenges/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => ?STRING
|
||||
},
|
||||
qs_val => #{
|
||||
<<"status">> => <<"Completed">>
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_identity_challenge_event(config()) ->
|
||||
_.
|
||||
get_identity_challenge_event(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_identity, fun
|
||||
('Get', _) -> {ok, ?IDENTITY(PartyID)};
|
||||
('GetEvents', _) -> {ok, [?IDENTITY_CHALLENGE_EVENT(?CHALLENGE_STATUS_CHANGE)]}
|
||||
end}
|
||||
], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_identities_api:get_identity_challenge_event/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => ?STRING,
|
||||
<<"challengeID">> => ?STRING,
|
||||
<<"eventID">> => ?INTEGER
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec poll_identity_challenge_events(config()) ->
|
||||
_.
|
||||
poll_identity_challenge_events(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_identity, fun
|
||||
('Get', _) -> {ok, ?IDENTITY(PartyID)};
|
||||
('GetEvents', _) -> {ok, [?IDENTITY_CHALLENGE_EVENT(?CHALLENGE_STATUS_CHANGE)]}
|
||||
end}
|
||||
], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_identities_api:poll_identity_challenge_events/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => ?STRING,
|
||||
<<"challengeID">> => ?STRING
|
||||
},
|
||||
qs_val => #{
|
||||
<<"limit">> => 551,
|
||||
<<"eventCursor">> => ?INTEGER
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
%%
|
||||
|
||||
-spec call_api(function(), map(), wapi_client_lib:context()) ->
|
||||
{ok, term()} | {error, term()}.
|
||||
call_api(F, Params, Context) ->
|
||||
{Url, PreparedParams, Opts} = wapi_client_lib:make_request(Context, Params),
|
||||
Response = F(Url, PreparedParams, Opts),
|
||||
wapi_client_lib:handle_response(Response).
|
||||
|
||||
create_party(_C) ->
|
||||
ID = genlib:bsuuid(),
|
||||
_ = ff_party:create(ID),
|
||||
ID.
|
||||
|
||||
issue_token(PartyID, ACL, LifeTime) ->
|
||||
Claims = #{?STRING => ?STRING},
|
||||
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),
|
||||
Token.
|
||||
|
303
apps/wapi/test/wapi_report_tests_SUITE.erl
Normal file
303
apps/wapi/test/wapi_report_tests_SUITE.erl
Normal file
@ -0,0 +1,303 @@
|
||||
-module(wapi_report_tests_SUITE).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-include_lib("damsel/include/dmsl_domain_config_thrift.hrl").
|
||||
-include_lib("fistful_reporter_proto/include/ff_reporter_reports_thrift.hrl").
|
||||
-include_lib("file_storage_proto/include/fs_file_storage_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
|
||||
-include_lib("jose/include/jose_jwk.hrl").
|
||||
-include_lib("wapi_wallet_dummy_data.hrl").
|
||||
|
||||
-export([all/0]).
|
||||
-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]).
|
||||
|
||||
-export([init/1]).
|
||||
|
||||
-export([
|
||||
create_report_ok_test/1,
|
||||
get_report_ok_test/1,
|
||||
get_reports_ok_test/1,
|
||||
reports_with_wrong_identity_ok_test/1,
|
||||
download_file_ok_test/1
|
||||
]).
|
||||
|
||||
-define(badresp(Code), {error, {invalid_response_code, Code}}).
|
||||
-define(emptyresp(Code), {error, {Code, #{}}}).
|
||||
|
||||
-type test_case_name() :: atom().
|
||||
-type config() :: [{atom(), any()}].
|
||||
-type group_name() :: atom().
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
-spec init([]) ->
|
||||
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
init([]) ->
|
||||
{ok, {#{strategy => one_for_all, intensity => 1, period => 1}, []}}.
|
||||
|
||||
-spec all() ->
|
||||
[test_case_name()].
|
||||
all() ->
|
||||
[
|
||||
{group, base}
|
||||
].
|
||||
|
||||
-spec groups() ->
|
||||
[{group_name(), list(), [test_case_name()]}].
|
||||
groups() ->
|
||||
[
|
||||
{base, [],
|
||||
[
|
||||
create_report_ok_test,
|
||||
get_report_ok_test,
|
||||
get_reports_ok_test,
|
||||
reports_with_wrong_identity_ok_test,
|
||||
download_file_ok_test
|
||||
]
|
||||
}
|
||||
].
|
||||
|
||||
%%
|
||||
%% starting/stopping
|
||||
%%
|
||||
-spec init_per_suite(config()) ->
|
||||
config().
|
||||
init_per_suite(Config) ->
|
||||
%% TODO remove this after cut off wapi
|
||||
ok = application:set_env(wapi, transport, thrift),
|
||||
ct_helper:makeup_cfg([
|
||||
ct_helper:test_case_name(init),
|
||||
ct_payment_system:setup(#{
|
||||
optional_apps => [
|
||||
bender_client,
|
||||
wapi,
|
||||
wapi_woody_client
|
||||
]
|
||||
})
|
||||
], Config).
|
||||
|
||||
-spec end_per_suite(config()) ->
|
||||
_.
|
||||
end_per_suite(C) ->
|
||||
%% TODO remove this after cut off wapi
|
||||
ok = application:unset_env(wapi, transport),
|
||||
ok = ct_payment_system:shutdown(C).
|
||||
|
||||
-spec init_per_group(group_name(), config()) ->
|
||||
config().
|
||||
init_per_group(Group, Config) when Group =:= base ->
|
||||
ok = ff_context:save(ff_context:create(#{
|
||||
party_client => party_client:create_client(),
|
||||
woody_context => woody_context:new(<<"init_per_group/", (atom_to_binary(Group, utf8))/binary>>)
|
||||
})),
|
||||
Party = create_party(Config),
|
||||
Token = issue_token(Party, [{[party], write}], unlimited),
|
||||
Config1 = [{party, Party} | Config],
|
||||
[{context, wapi_ct_helper:get_context(Token)} | Config1];
|
||||
init_per_group(_, Config) ->
|
||||
Config.
|
||||
|
||||
-spec end_per_group(group_name(), config()) ->
|
||||
_.
|
||||
end_per_group(_Group, _C) ->
|
||||
ok.
|
||||
|
||||
-spec init_per_testcase(test_case_name(), config()) ->
|
||||
config().
|
||||
init_per_testcase(Name, C) ->
|
||||
C1 = ct_helper:makeup_cfg([ct_helper:test_case_name(Name), ct_helper:woody_ctx()], C),
|
||||
ok = ct_helper:set_context(C1),
|
||||
[{test_sup, wapi_ct_helper:start_mocked_service_sup(?MODULE)} | C1].
|
||||
|
||||
-spec end_per_testcase(test_case_name(), config()) ->
|
||||
config().
|
||||
end_per_testcase(_Name, C) ->
|
||||
ok = ct_helper:unset_context(),
|
||||
wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)),
|
||||
ok.
|
||||
|
||||
%%% Tests
|
||||
-spec create_report_ok_test(config()) ->
|
||||
_.
|
||||
create_report_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{fistful_report, fun
|
||||
('GenerateReport', _) -> {ok, ?REPORT_ID};
|
||||
('GetReport', _) -> {ok, ?REPORT}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_reports_api:create_report/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID
|
||||
},
|
||||
body => #{
|
||||
<<"reportType">> => <<"withdrawalRegistry">>,
|
||||
<<"fromTime">> => ?TIMESTAMP,
|
||||
<<"toTime">> => ?TIMESTAMP
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_report_ok_test(config()) ->
|
||||
_.
|
||||
get_report_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{fistful_report, fun
|
||||
('GetReport', _) -> {ok, ?REPORT}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_reports_api:get_report/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID,
|
||||
<<"reportID">> => ?INTEGER
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_reports_ok_test(config()) ->
|
||||
_.
|
||||
get_reports_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{fistful_report, fun
|
||||
('GetReports', _) -> {ok, [
|
||||
?REPORT_EXT(pending, []),
|
||||
?REPORT_EXT(created, undefined),
|
||||
?REPORT_WITH_STATUS(canceled)]}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_reports_api:get_reports/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID
|
||||
},
|
||||
qs_val => #{
|
||||
<<"fromTime">> => ?TIMESTAMP,
|
||||
<<"toTime">> => ?TIMESTAMP,
|
||||
<<"type">> => <<"withdrawalRegistry">>
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec reports_with_wrong_identity_ok_test(config()) ->
|
||||
_.
|
||||
reports_with_wrong_identity_ok_test(C) ->
|
||||
IdentityID = <<"WrongIdentity">>,
|
||||
wapi_ct_helper:mock_services([{fistful_report, fun
|
||||
('GenerateReport', _) -> {ok, ?REPORT_ID};
|
||||
('GetReport', _) -> {ok, ?REPORT};
|
||||
('GetReports', _) -> {ok, [?REPORT, ?REPORT, ?REPORT]}
|
||||
end}], C),
|
||||
?emptyresp(400) = call_api(
|
||||
fun swag_client_wallet_reports_api:create_report/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID
|
||||
},
|
||||
body => #{
|
||||
<<"reportType">> => <<"withdrawalRegistry">>,
|
||||
<<"fromTime">> => ?TIMESTAMP,
|
||||
<<"toTime">> => ?TIMESTAMP
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
?emptyresp(400) = call_api(
|
||||
fun swag_client_wallet_reports_api:get_report/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID,
|
||||
<<"reportID">> => ?INTEGER
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
?emptyresp(400) = call_api(
|
||||
fun swag_client_wallet_reports_api:get_reports/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID
|
||||
},
|
||||
qs_val => #{
|
||||
<<"fromTime">> => ?TIMESTAMP,
|
||||
<<"toTime">> => ?TIMESTAMP,
|
||||
<<"type">> => <<"withdrawalRegistry">>
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec download_file_ok_test(config()) ->
|
||||
_.
|
||||
download_file_ok_test(C) ->
|
||||
wapi_ct_helper:mock_services([{file_storage, fun
|
||||
('GenerateDownloadUrl', _) -> {ok, ?STRING}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_downloads_api:download_file/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"fileID">> => ?STRING
|
||||
},
|
||||
qs_val => #{
|
||||
<<"expiresAt">> => ?TIMESTAMP
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
%%
|
||||
|
||||
-spec call_api(function(), map(), wapi_client_lib:context()) ->
|
||||
{ok, term()} | {error, term()}.
|
||||
call_api(F, Params, Context) ->
|
||||
{Url, PreparedParams, Opts} = wapi_client_lib:make_request(Context, Params),
|
||||
Response = F(Url, PreparedParams, Opts),
|
||||
wapi_client_lib:handle_response(Response).
|
||||
|
||||
create_party(_C) ->
|
||||
ID = genlib:bsuuid(),
|
||||
_ = ff_party:create(ID),
|
||||
ID.
|
||||
|
||||
create_identity(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
Params = #{
|
||||
<<"provider">> => <<"good-one">>,
|
||||
<<"class">> => <<"person">>,
|
||||
<<"name">> => <<"HAHA NO2">>
|
||||
},
|
||||
wapi_wallet_ff_backend:create_identity(Params, create_context(PartyID, C)).
|
||||
|
||||
create_context(PartyID, C) ->
|
||||
maps:merge(create_auth_ctx(PartyID), create_woody_ctx(C)).
|
||||
|
||||
create_woody_ctx(C) ->
|
||||
#{
|
||||
woody_context => ct_helper:get_woody_ctx(C)
|
||||
}.
|
||||
|
||||
create_auth_ctx(PartyID) ->
|
||||
#{
|
||||
swagger_context => #{auth_context => {{PartyID, empty}, #{}}}
|
||||
}.
|
||||
|
||||
issue_token(PartyID, ACL, LifeTime) ->
|
||||
Claims = #{?STRING => ?STRING},
|
||||
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),
|
||||
Token.
|
||||
|
338
apps/wapi/test/wapi_thrift_SUITE.erl
Normal file
338
apps/wapi/test/wapi_thrift_SUITE.erl
Normal file
@ -0,0 +1,338 @@
|
||||
-module(wapi_thrift_SUITE).
|
||||
|
||||
-include_lib("stdlib/include/assert.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_fistful_thrift.hrl").
|
||||
-include_lib("wapi_wallet_dummy_data.hrl").
|
||||
|
||||
-export([all/0]).
|
||||
-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]).
|
||||
|
||||
-export([wallet_check_test/1]).
|
||||
-export([identity_check_test/1]).
|
||||
-export([identity_challenge_check_test/1]).
|
||||
|
||||
-type config() :: ct_helper:config().
|
||||
-type test_case_name() :: ct_helper:test_case_name().
|
||||
-type group_name() :: ct_helper:group_name().
|
||||
-type test_return() :: _ | no_return().
|
||||
|
||||
% -import(ct_helper, [cfg/2]).
|
||||
|
||||
-spec all() -> [test_case_name() | {group, group_name()}].
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, default}
|
||||
].
|
||||
|
||||
-spec groups() -> [{group_name(), list(), [test_case_name()]}].
|
||||
|
||||
groups() ->
|
||||
[
|
||||
{default, [sequence], [
|
||||
identity_check_test,
|
||||
identity_challenge_check_test,
|
||||
wallet_check_test
|
||||
]}
|
||||
].
|
||||
|
||||
-spec init_per_suite(config()) -> config().
|
||||
|
||||
init_per_suite(C) ->
|
||||
ct_helper:makeup_cfg([
|
||||
ct_helper:test_case_name(init),
|
||||
ct_payment_system:setup(#{
|
||||
default_termset => get_default_termset(),
|
||||
optional_apps => [
|
||||
bender_client,
|
||||
wapi_woody_client,
|
||||
wapi
|
||||
]
|
||||
})
|
||||
], C).
|
||||
|
||||
-spec end_per_suite(config()) -> _.
|
||||
|
||||
end_per_suite(C) ->
|
||||
ok = ct_payment_system:shutdown(C).
|
||||
|
||||
%%
|
||||
|
||||
-spec init_per_group(group_name(), config()) -> config().
|
||||
|
||||
init_per_group(G, C) ->
|
||||
ok = ff_context:save(ff_context:create(#{
|
||||
party_client => party_client:create_client(),
|
||||
woody_context => woody_context:new(<<"init_per_group/", (atom_to_binary(G, utf8))/binary>>)
|
||||
})),
|
||||
Party = create_party(C),
|
||||
% Token = issue_token(Party, [{[party], write}], unlimited),
|
||||
Token = issue_token(Party, [{[party], write}], {deadline, 10}),
|
||||
Context = get_context("localhost:8080", Token),
|
||||
ContextPcidss = get_context("wapi-pcidss:8080", Token),
|
||||
[{context, Context}, {context_pcidss, ContextPcidss}, {party, Party} | C].
|
||||
|
||||
-spec end_per_group(group_name(), config()) -> _.
|
||||
|
||||
end_per_group(_, _) ->
|
||||
ok.
|
||||
%%
|
||||
|
||||
-spec init_per_testcase(test_case_name(), config()) -> config().
|
||||
|
||||
init_per_testcase(Name, C) ->
|
||||
C1 = ct_helper:makeup_cfg([ct_helper:test_case_name(Name), ct_helper:woody_ctx()], C),
|
||||
ok = ct_helper:set_context(C1),
|
||||
ok = application:set_env(wapi, transport, not_thrift),
|
||||
C1.
|
||||
|
||||
-spec end_per_testcase(test_case_name(), config()) -> _.
|
||||
|
||||
end_per_testcase(_Name, _C) ->
|
||||
ok = ct_helper:unset_context().
|
||||
|
||||
-define(ID_PROVIDER, <<"good-one">>).
|
||||
-define(ID_PROVIDER2, <<"good-two">>).
|
||||
-define(ID_CLASS, <<"person">>).
|
||||
|
||||
-spec identity_check_test(config()) -> test_return().
|
||||
|
||||
identity_check_test(C) ->
|
||||
Name = <<"Keyn Fawkes">>,
|
||||
Provider = ?ID_PROVIDER,
|
||||
Class = ?ID_CLASS,
|
||||
IdentityID1 = create_identity(Name, Provider, Class, C),
|
||||
ok = check_identity(Name, IdentityID1, Provider, Class, C),
|
||||
Keys = maps:keys(get_identity(IdentityID1, C)),
|
||||
ok = application:set_env(wapi, transport, thrift),
|
||||
IdentityID2 = create_identity(Name, Provider, Class, C),
|
||||
ok = check_identity(Name, IdentityID2, Provider, Class, C),
|
||||
?assertEqual(Keys, maps:keys(get_identity(IdentityID2, C))).
|
||||
|
||||
-spec identity_challenge_check_test(config()) -> test_return().
|
||||
|
||||
identity_challenge_check_test(C) ->
|
||||
Name = <<"Keyn Fawkes">>,
|
||||
Provider = ?ID_PROVIDER,
|
||||
Class = ?ID_CLASS,
|
||||
IdentityID1 = create_identity(Name, Provider, Class, C),
|
||||
ok = check_identity(Name, IdentityID1, Provider, Class, C),
|
||||
IdentityChallengeID1 = create_identity_challenge(IdentityID1, C),
|
||||
Keys = maps:keys(get_identity_challenge(IdentityID1, IdentityChallengeID1, C)),
|
||||
ok = application:set_env(wapi, transport, thrift),
|
||||
IdentityID2 = create_identity(Name, Provider, Class, C),
|
||||
ok = check_identity(Name, IdentityID2, Provider, Class, C),
|
||||
IdentityChallengeID2 = create_identity_challenge(IdentityID2, C),
|
||||
?assertEqual(Keys, maps:keys(get_identity_challenge(IdentityID2, IdentityChallengeID2, C))).
|
||||
|
||||
-spec wallet_check_test(config()) -> test_return().
|
||||
|
||||
wallet_check_test(C) ->
|
||||
Name = <<"Keyn Fawkes">>,
|
||||
Provider = ?ID_PROVIDER,
|
||||
Class = ?ID_CLASS,
|
||||
IdentityID1 = create_identity(Name, Provider, Class, C),
|
||||
WalletID1 = create_wallet(IdentityID1, C),
|
||||
ok = check_wallet(WalletID1, C),
|
||||
Keys = maps:keys(get_wallet(WalletID1, C)),
|
||||
ok = application:set_env(wapi, transport, thrift),
|
||||
IdentityID2 = create_identity(Name, Provider, Class, C),
|
||||
WalletID2 = create_wallet(IdentityID2, C),
|
||||
ok = check_wallet(WalletID2, C),
|
||||
?assertEqual(Keys, maps:keys(get_wallet(WalletID2, C))).
|
||||
|
||||
%%
|
||||
|
||||
-spec call_api(function(), map(), wapi_client_lib:context()) ->
|
||||
{ok, term()} | {error, term()}.
|
||||
call_api(F, Params, Context) ->
|
||||
{Url, PreparedParams, Opts} = wapi_client_lib:make_request(Context, Params),
|
||||
Response = F(Url, PreparedParams, Opts),
|
||||
wapi_client_lib:handle_response(Response).
|
||||
|
||||
%%
|
||||
|
||||
create_party(_C) ->
|
||||
ID = genlib:bsuuid(),
|
||||
_ = ff_party:create(ID),
|
||||
ID.
|
||||
|
||||
issue_token(PartyID, ACL, LifeTime) ->
|
||||
Claims = #{?STRING => ?STRING},
|
||||
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),
|
||||
Token.
|
||||
|
||||
get_context(Endpoint, Token) ->
|
||||
wapi_client_lib:get_context(Endpoint, Token, 10000, ipv4).
|
||||
|
||||
%%
|
||||
|
||||
create_identity(Name, Provider, Class, C) ->
|
||||
{ok, Identity} = call_api(
|
||||
fun swag_client_wallet_identities_api:create_identity/3,
|
||||
#{body => #{
|
||||
<<"name">> => Name,
|
||||
<<"provider">> => Provider,
|
||||
<<"class">> => Class,
|
||||
<<"metadata">> => #{
|
||||
?STRING => ?STRING
|
||||
}
|
||||
}},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
maps:get(<<"id">>, Identity).
|
||||
|
||||
check_identity(Name, IdentityID, Provider, Class, C) ->
|
||||
Identity = get_identity(IdentityID, C),
|
||||
#{
|
||||
<<"name">> := Name,
|
||||
<<"provider">> := Provider,
|
||||
<<"class">> := Class,
|
||||
<<"metadata">> := #{
|
||||
?STRING := ?STRING
|
||||
}
|
||||
} = maps:with([<<"name">>,
|
||||
<<"provider">>,
|
||||
<<"class">>,
|
||||
<<"metadata">>], Identity),
|
||||
ok.
|
||||
|
||||
get_identity(IdentityID, C) ->
|
||||
{ok, Identity} = call_api(
|
||||
fun swag_client_wallet_identities_api:get_identity/3,
|
||||
#{binding => #{<<"identityID">> => IdentityID}},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
Identity.
|
||||
|
||||
create_identity_challenge(IdentityID, C) ->
|
||||
{_Cert, CertToken} = ct_identdocstore:rus_retiree_insurance_cert(genlib:unique(), C),
|
||||
{_Passport, PassportToken} = ct_identdocstore:rus_domestic_passport(C),
|
||||
{ok, IdentityChallenge} = call_api(
|
||||
fun swag_client_wallet_identities_api:start_identity_challenge/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID
|
||||
},
|
||||
body => #{
|
||||
<<"type">> => <<"sword-initiation">>,
|
||||
<<"proofs">> => [
|
||||
#{
|
||||
<<"token">> => wapi_utils:map_to_base64url(#{
|
||||
<<"type">> => <<"RUSRetireeInsuranceCertificate">>,
|
||||
<<"token">> => CertToken
|
||||
})
|
||||
},
|
||||
#{
|
||||
<<"token">> => wapi_utils:map_to_base64url(#{
|
||||
<<"type">> => <<"RUSDomesticPassport">>,
|
||||
<<"token">> => PassportToken
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
maps:get(<<"id">>, IdentityChallenge).
|
||||
|
||||
get_identity_challenge(IdentityID, ChallengeID, C) ->
|
||||
{ok, IdentityChallenge} = call_api(
|
||||
fun swag_client_wallet_identities_api:get_identity_challenge/3,
|
||||
#{binding => #{<<"identityID">> => IdentityID, <<"challengeID">> => ChallengeID}},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
IdentityChallenge.
|
||||
|
||||
create_wallet(IdentityID, C) ->
|
||||
create_wallet(IdentityID, #{}, C).
|
||||
|
||||
create_wallet(IdentityID, Params, C) ->
|
||||
DefaultParams = #{
|
||||
<<"name">> => <<"Worldwide PHP Awareness Initiative">>,
|
||||
<<"identity">> => IdentityID,
|
||||
<<"currency">> => <<"RUB">>,
|
||||
<<"metadata">> => #{
|
||||
?STRING => ?STRING
|
||||
}
|
||||
},
|
||||
{ok, Wallet} = call_api(
|
||||
fun swag_client_wallet_wallets_api:create_wallet/3,
|
||||
#{body => maps:merge(DefaultParams, Params)},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
maps:get(<<"id">>, Wallet).
|
||||
|
||||
check_wallet(WalletID, C) ->
|
||||
Wallet = get_wallet(WalletID, C),
|
||||
#{
|
||||
<<"name">> := <<"Worldwide PHP Awareness Initiative">>,
|
||||
<<"currency">> := <<"RUB">>,
|
||||
<<"metadata">> := #{
|
||||
?STRING := ?STRING
|
||||
}
|
||||
} = maps:with([<<"name">>, <<"currency">>, <<"metadata">>], Wallet),
|
||||
ok.
|
||||
|
||||
get_wallet(WalletID, C) ->
|
||||
{ok, Wallet} = call_api(
|
||||
fun swag_client_wallet_wallets_api:get_wallet/3,
|
||||
#{binding => #{<<"walletID">> => WalletID}},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
Wallet.
|
||||
|
||||
%%
|
||||
|
||||
-include_lib("ff_cth/include/ct_domain.hrl").
|
||||
|
||||
get_default_termset() ->
|
||||
#domain_TermSet{
|
||||
wallets = #domain_WalletServiceTerms{
|
||||
currencies = {value, ?ordset([?cur(<<"RUB">>)])},
|
||||
wallet_limit = {decisions, [
|
||||
#domain_CashLimitDecision{
|
||||
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
|
||||
then_ = {value, ?cashrng(
|
||||
{inclusive, ?cash(-10000000, <<"RUB">>)},
|
||||
{exclusive, ?cash( 10000001, <<"RUB">>)}
|
||||
)}
|
||||
}
|
||||
]},
|
||||
withdrawals = #domain_WithdrawalServiceTerms{
|
||||
currencies = {value, ?ordset([?cur(<<"RUB">>)])},
|
||||
cash_limit = {decisions, [
|
||||
#domain_CashLimitDecision{
|
||||
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
|
||||
then_ = {value, ?cashrng(
|
||||
{inclusive, ?cash( 0, <<"RUB">>)},
|
||||
{exclusive, ?cash(10000000, <<"RUB">>)}
|
||||
)}
|
||||
}
|
||||
]},
|
||||
cash_flow = {decisions, [
|
||||
#domain_CashFlowDecision{
|
||||
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
|
||||
then_ = {value, [
|
||||
?cfpost(
|
||||
{wallet, sender_settlement},
|
||||
{wallet, receiver_destination},
|
||||
?share(1, 1, operation_amount)
|
||||
),
|
||||
?cfpost(
|
||||
{wallet, receiver_destination},
|
||||
{system, settlement},
|
||||
?share(10, 100, operation_amount)
|
||||
)
|
||||
]}
|
||||
}
|
||||
]}
|
||||
}
|
||||
}
|
||||
}.
|
@ -10,6 +10,13 @@
|
||||
-define(TIMESTAMP, <<"2016-03-22T06:12:27Z">>).
|
||||
-define(MD5, <<"033BD94B1168D7E4F0D644C3C95E35BF">>).
|
||||
-define(SHA256, <<"94EE059335E587E501CC4BF90613E0814F00A7B08BC7C648FD865A2AF6A22CC2">>).
|
||||
-define(DEFAULT_CONTEXT(PartyID), #{
|
||||
<<"com.rbkmoney.wapi">> => {obj, #{
|
||||
{str, <<"owner">>} => {str, PartyID},
|
||||
{str, <<"name">>} => {str, ?STRING},
|
||||
{str, <<"metadata">>} => {obj, #{{str, <<"somedata">>} => {str, ?STRING}}}
|
||||
}}
|
||||
}).
|
||||
|
||||
-define(CASH, #'Cash'{
|
||||
amount = ?INTEGER,
|
||||
@ -29,13 +36,65 @@
|
||||
accounter_account_id = ?INTEGER
|
||||
}).
|
||||
|
||||
-define(WALLET_STATE, #wlt_WalletState{
|
||||
id = ?STRING,
|
||||
name = ?STRING,
|
||||
blocking = ?BLOCKING,
|
||||
account = ?ACCOUNT
|
||||
-define(WALLET(PartyID), #wlt_Wallet{
|
||||
id = ?STRING,
|
||||
name = ?STRING,
|
||||
blocking = ?BLOCKING,
|
||||
account = ?ACCOUNT,
|
||||
external_id = ?STRING,
|
||||
created_at = ?TIMESTAMP,
|
||||
context = ?DEFAULT_CONTEXT(PartyID)
|
||||
}).
|
||||
|
||||
-define(IDENTITY(PartyID), #idnt_Identity{
|
||||
id = ?STRING,
|
||||
party = ?STRING,
|
||||
provider = ?STRING,
|
||||
cls = ?STRING,
|
||||
context = ?DEFAULT_CONTEXT(PartyID)
|
||||
}).
|
||||
|
||||
-define(IDENTITY_CHALLENGE(Status), #idnt_Challenge{
|
||||
cls = ?STRING,
|
||||
proofs = [
|
||||
#idnt_ChallengeProof{
|
||||
type = rus_domestic_passport,
|
||||
token = ?STRING
|
||||
}
|
||||
],
|
||||
id = ?STRING,
|
||||
status = Status
|
||||
}).
|
||||
|
||||
-define(IDENTITY_CHALLENGE_STATUS_COMPLETED, {completed, #idnt_ChallengeCompleted{
|
||||
resolution = approved,
|
||||
valid_until = ?TIMESTAMP
|
||||
}}).
|
||||
|
||||
-define(IDENTITY_CHALLENGE_EVENT(Change), #idnt_IdentityEvent{
|
||||
change = Change,
|
||||
occured_at = ?TIMESTAMP,
|
||||
sequence = ?INTEGER
|
||||
}).
|
||||
|
||||
-define(CHALLENGE_STATUS_CHANGE, {identity_challenge, #idnt_ChallengeChange{
|
||||
id = ?STRING,
|
||||
payload = {status_changed, ?IDENTITY_CHALLENGE_STATUS_COMPLETED}
|
||||
}}).
|
||||
|
||||
-define(IDENT_DOC, {russian_domestic_passport, #'identdocstore_RussianDomesticPassport'{
|
||||
issuer = ?STRING,
|
||||
issuer_code = ?STRING,
|
||||
issued_at = ?TIMESTAMP,
|
||||
birth_date = ?TIMESTAMP,
|
||||
birth_place = ?STRING,
|
||||
series = ?STRING,
|
||||
number = ?STRING,
|
||||
first_name = ?STRING,
|
||||
family_name = ?STRING,
|
||||
patronymic = ?STRING
|
||||
}}).
|
||||
|
||||
-define(REPORT_ID, ?INTEGER).
|
||||
|
||||
-define(REPORT_EXT(Status, FilesList), #ff_reports_Report{
|
||||
|
@ -3,11 +3,11 @@
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-include_lib("damsel/include/dmsl_domain_config_thrift.hrl").
|
||||
-include_lib("fistful_reporter_proto/include/ff_reporter_reports_thrift.hrl").
|
||||
-include_lib("file_storage_proto/include/fs_file_storage_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_base_thrift.hrl").
|
||||
|
||||
-include_lib("jose/include/jose_jwk.hrl").
|
||||
-include_lib("wapi_wallet_dummy_data.hrl").
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_wallet_thrift.hrl").
|
||||
-include_lib("fistful_proto/include/ff_proto_webhooker_thrift.hrl").
|
||||
|
||||
-export([all/0]).
|
||||
@ -22,15 +22,8 @@
|
||||
-export([init/1]).
|
||||
|
||||
-export([
|
||||
create_report_ok_test/1,
|
||||
get_report_ok_test/1,
|
||||
get_reports_ok_test/1,
|
||||
reports_with_wrong_identity_ok_test/1,
|
||||
download_file_ok_test/1,
|
||||
create_webhook_ok_test/1,
|
||||
get_webhooks_ok_test/1,
|
||||
get_webhook_ok_test/1,
|
||||
delete_webhook_ok_test/1
|
||||
create_wallet/1,
|
||||
get_wallet/1
|
||||
]).
|
||||
|
||||
-define(badresp(Code), {error, {invalid_response_code, Code}}).
|
||||
@ -60,15 +53,8 @@ groups() ->
|
||||
[
|
||||
{base, [],
|
||||
[
|
||||
create_report_ok_test,
|
||||
get_report_ok_test,
|
||||
get_reports_ok_test,
|
||||
reports_with_wrong_identity_ok_test,
|
||||
download_file_ok_test,
|
||||
create_webhook_ok_test,
|
||||
get_webhooks_ok_test,
|
||||
get_webhook_ok_test,
|
||||
delete_webhook_ok_test
|
||||
create_wallet,
|
||||
get_wallet
|
||||
]
|
||||
}
|
||||
].
|
||||
@ -80,6 +66,7 @@ groups() ->
|
||||
config().
|
||||
init_per_suite(Config) ->
|
||||
%% TODO remove this after cut off wapi
|
||||
ok = application:set_env(wapi, transport, thrift),
|
||||
ct_helper:makeup_cfg([
|
||||
ct_helper:test_case_name(init),
|
||||
ct_payment_system:setup(#{
|
||||
@ -95,6 +82,7 @@ init_per_suite(Config) ->
|
||||
_.
|
||||
end_per_suite(C) ->
|
||||
%% TODO remove this after cut off wapi
|
||||
ok = application:unset_env(wapi, transport),
|
||||
ok = ct_payment_system:shutdown(C).
|
||||
|
||||
-spec init_per_group(group_name(), config()) ->
|
||||
@ -133,224 +121,42 @@ end_per_testcase(_Name, C) ->
|
||||
|
||||
%%% Tests
|
||||
|
||||
-spec create_report_ok_test(config()) ->
|
||||
-spec create_wallet(config()) ->
|
||||
_.
|
||||
create_report_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{fistful_report, fun
|
||||
('GenerateReport', _) -> {ok, ?REPORT_ID};
|
||||
('GetReport', _) -> {ok, ?REPORT}
|
||||
end}], C),
|
||||
create_wallet(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_identity, fun('Get', _) -> {ok, ?IDENTITY(PartyID)} end},
|
||||
{fistful_wallet, fun('Create', _) -> {ok, ?WALLET(PartyID)} end}
|
||||
], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_reports_api:create_report/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID
|
||||
},
|
||||
body => #{
|
||||
<<"reportType">> => <<"withdrawalRegistry">>,
|
||||
<<"fromTime">> => ?TIMESTAMP,
|
||||
<<"toTime">> => ?TIMESTAMP
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_report_ok_test(config()) ->
|
||||
_.
|
||||
get_report_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{fistful_report, fun
|
||||
('GetReport', _) -> {ok, ?REPORT}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_reports_api:get_report/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID,
|
||||
<<"reportID">> => ?INTEGER
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_reports_ok_test(config()) ->
|
||||
_.
|
||||
get_reports_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{fistful_report, fun
|
||||
('GetReports', _) -> {ok, [
|
||||
?REPORT_EXT(pending, []),
|
||||
?REPORT_EXT(created, undefined),
|
||||
?REPORT_WITH_STATUS(canceled)]}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_reports_api:get_reports/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID
|
||||
},
|
||||
qs_val => #{
|
||||
<<"fromTime">> => ?TIMESTAMP,
|
||||
<<"toTime">> => ?TIMESTAMP,
|
||||
<<"type">> => <<"withdrawalRegistry">>
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec reports_with_wrong_identity_ok_test(config()) ->
|
||||
_.
|
||||
reports_with_wrong_identity_ok_test(C) ->
|
||||
IdentityID = <<"WrongIdentity">>,
|
||||
wapi_ct_helper:mock_services([{fistful_report, fun
|
||||
('GenerateReport', _) -> {ok, ?REPORT_ID};
|
||||
('GetReport', _) -> {ok, ?REPORT};
|
||||
('GetReports', _) -> {ok, [?REPORT, ?REPORT, ?REPORT]}
|
||||
end}], C),
|
||||
?emptyresp(400) = call_api(
|
||||
fun swag_client_wallet_reports_api:create_report/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID
|
||||
},
|
||||
body => #{
|
||||
<<"reportType">> => <<"withdrawalRegistry">>,
|
||||
<<"fromTime">> => ?TIMESTAMP,
|
||||
<<"toTime">> => ?TIMESTAMP
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
?emptyresp(400) = call_api(
|
||||
fun swag_client_wallet_reports_api:get_report/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID,
|
||||
<<"reportID">> => ?INTEGER
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
),
|
||||
?emptyresp(400) = call_api(
|
||||
fun swag_client_wallet_reports_api:get_reports/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"identityID">> => IdentityID
|
||||
},
|
||||
qs_val => #{
|
||||
<<"fromTime">> => ?TIMESTAMP,
|
||||
<<"toTime">> => ?TIMESTAMP,
|
||||
<<"type">> => <<"withdrawalRegistry">>
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec download_file_ok_test(config()) ->
|
||||
_.
|
||||
download_file_ok_test(C) ->
|
||||
wapi_ct_helper:mock_services([{file_storage, fun
|
||||
('GenerateDownloadUrl', _) -> {ok, ?STRING}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_downloads_api:download_file/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"fileID">> => ?STRING
|
||||
},
|
||||
qs_val => #{
|
||||
<<"expiresAt">> => ?TIMESTAMP
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec create_webhook_ok_test(config()) ->
|
||||
_.
|
||||
create_webhook_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{webhook_manager, fun
|
||||
('Create', _) -> {ok, ?WEBHOOK(?DESTINATION_EVENT_FILTER)}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_webhooks_api:create_webhook/3,
|
||||
fun swag_client_wallet_wallets_api:create_wallet/3,
|
||||
#{
|
||||
body => #{
|
||||
<<"identityID">> => IdentityID,
|
||||
<<"url">> => ?STRING,
|
||||
<<"scope">> => #{
|
||||
<<"topic">> => <<"DestinationsTopic">>,
|
||||
<<"eventTypes">> => [<<"DestinationCreated">>]
|
||||
}
|
||||
}
|
||||
<<"name">> => ?STRING,
|
||||
<<"identity">> => ?STRING,
|
||||
<<"currency">> => ?RUB
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_webhooks_ok_test(config()) ->
|
||||
-spec get_wallet(config()) ->
|
||||
_.
|
||||
get_webhooks_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{webhook_manager, fun
|
||||
('GetList', _) -> {ok, [?WEBHOOK(?WITHDRAWAL_EVENT_FILTER), ?WEBHOOK(?DESTINATION_EVENT_FILTER)]}
|
||||
end}], C),
|
||||
get_wallet(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
wapi_ct_helper:mock_services([
|
||||
{fistful_wallet, fun('Get', _) -> {ok, ?WALLET(PartyID)} end}
|
||||
], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_webhooks_api:get_webhooks/3,
|
||||
#{
|
||||
qs_val => #{
|
||||
<<"identityID">> => IdentityID
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_webhook_ok_test(config()) ->
|
||||
_.
|
||||
get_webhook_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{webhook_manager, fun
|
||||
('Get', _) -> {ok, ?WEBHOOK(?WITHDRAWAL_EVENT_FILTER)}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_webhooks_api:get_webhook_by_id/3,
|
||||
fun swag_client_wallet_wallets_api:get_wallet/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"webhookID">> => integer_to_binary(?INTEGER)
|
||||
},
|
||||
qs_val => #{
|
||||
<<"identityID">> => IdentityID
|
||||
<<"walletID">> => ?STRING
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec delete_webhook_ok_test(config()) ->
|
||||
_.
|
||||
delete_webhook_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{webhook_manager, fun
|
||||
('Delete', _) -> {ok, ok}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_webhooks_api:delete_webhook_by_id/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"webhookID">> => integer_to_binary(?INTEGER)
|
||||
},
|
||||
qs_val => #{
|
||||
<<"identityID">> => IdentityID
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
%%
|
||||
|
||||
@ -366,28 +172,6 @@ create_party(_C) ->
|
||||
_ = ff_party:create(ID),
|
||||
ID.
|
||||
|
||||
create_identity(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
Params = #{
|
||||
<<"provider">> => <<"good-one">>,
|
||||
<<"class">> => <<"person">>,
|
||||
<<"name">> => <<"HAHA NO2">>
|
||||
},
|
||||
wapi_wallet_ff_backend:create_identity(Params, create_context(PartyID, C)).
|
||||
|
||||
create_context(PartyID, C) ->
|
||||
maps:merge(create_auth_ctx(PartyID), create_woody_ctx(C)).
|
||||
|
||||
create_woody_ctx(C) ->
|
||||
#{
|
||||
woody_context => ct_helper:get_woody_ctx(C)
|
||||
}.
|
||||
|
||||
create_auth_ctx(PartyID) ->
|
||||
#{
|
||||
swagger_context => #{auth_context => {{PartyID, empty}, empty}}
|
||||
}.
|
||||
|
||||
issue_token(PartyID, ACL, LifeTime) ->
|
||||
Claims = #{?STRING => ?STRING},
|
||||
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),
|
||||
|
249
apps/wapi/test/wapi_webhook_tests_SUITE.erl
Normal file
249
apps/wapi/test/wapi_webhook_tests_SUITE.erl
Normal file
@ -0,0 +1,249 @@
|
||||
-module(wapi_webhook_tests_SUITE).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-include_lib("damsel/include/dmsl_domain_config_thrift.hrl").
|
||||
|
||||
-include_lib("jose/include/jose_jwk.hrl").
|
||||
-include_lib("wapi_wallet_dummy_data.hrl").
|
||||
|
||||
-include_lib("fistful_proto/include/ff_proto_webhooker_thrift.hrl").
|
||||
|
||||
-export([all/0]).
|
||||
-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]).
|
||||
|
||||
-export([init/1]).
|
||||
|
||||
-export([
|
||||
create_webhook_ok_test/1,
|
||||
get_webhooks_ok_test/1,
|
||||
get_webhook_ok_test/1,
|
||||
delete_webhook_ok_test/1
|
||||
]).
|
||||
|
||||
-define(badresp(Code), {error, {invalid_response_code, Code}}).
|
||||
-define(emptyresp(Code), {error, {Code, #{}}}).
|
||||
|
||||
-type test_case_name() :: atom().
|
||||
-type config() :: [{atom(), any()}].
|
||||
-type group_name() :: atom().
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
-spec init([]) ->
|
||||
{ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
init([]) ->
|
||||
{ok, {#{strategy => one_for_all, intensity => 1, period => 1}, []}}.
|
||||
|
||||
-spec all() ->
|
||||
[test_case_name()].
|
||||
all() ->
|
||||
[
|
||||
{group, base}
|
||||
].
|
||||
|
||||
-spec groups() ->
|
||||
[{group_name(), list(), [test_case_name()]}].
|
||||
groups() ->
|
||||
[
|
||||
{base, [],
|
||||
[
|
||||
create_webhook_ok_test,
|
||||
get_webhooks_ok_test,
|
||||
get_webhook_ok_test,
|
||||
delete_webhook_ok_test
|
||||
]
|
||||
}
|
||||
].
|
||||
|
||||
%%
|
||||
%% starting/stopping
|
||||
%%
|
||||
-spec init_per_suite(config()) ->
|
||||
config().
|
||||
init_per_suite(Config) ->
|
||||
%% TODO remove this after cut off wapi
|
||||
ok = application:set_env(wapi, transport, thrift),
|
||||
ct_helper:makeup_cfg([
|
||||
ct_helper:test_case_name(init),
|
||||
ct_payment_system:setup(#{
|
||||
optional_apps => [
|
||||
bender_client,
|
||||
wapi_woody_client,
|
||||
wapi
|
||||
]
|
||||
})
|
||||
], Config).
|
||||
|
||||
-spec end_per_suite(config()) ->
|
||||
_.
|
||||
end_per_suite(C) ->
|
||||
%% TODO remove this after cut off wapi
|
||||
ok = application:unset_env(wapi, transport),
|
||||
ok = ct_payment_system:shutdown(C).
|
||||
|
||||
-spec init_per_group(group_name(), config()) ->
|
||||
config().
|
||||
init_per_group(Group, Config) when Group =:= base ->
|
||||
ok = ff_context:save(ff_context:create(#{
|
||||
party_client => party_client:create_client(),
|
||||
woody_context => woody_context:new(<<"init_per_group/", (atom_to_binary(Group, utf8))/binary>>)
|
||||
})),
|
||||
Party = create_party(Config),
|
||||
Token = issue_token(Party, [{[party], write}], unlimited),
|
||||
Config1 = [{party, Party} | Config],
|
||||
[{context, wapi_ct_helper:get_context(Token)} | Config1];
|
||||
init_per_group(_, Config) ->
|
||||
Config.
|
||||
|
||||
-spec end_per_group(group_name(), config()) ->
|
||||
_.
|
||||
end_per_group(_Group, _C) ->
|
||||
ok.
|
||||
|
||||
-spec init_per_testcase(test_case_name(), config()) ->
|
||||
config().
|
||||
init_per_testcase(Name, C) ->
|
||||
C1 = ct_helper:makeup_cfg([ct_helper:test_case_name(Name), ct_helper:woody_ctx()], C),
|
||||
ok = ct_helper:set_context(C1),
|
||||
[{test_sup, wapi_ct_helper:start_mocked_service_sup(?MODULE)} | C1].
|
||||
|
||||
-spec end_per_testcase(test_case_name(), config()) ->
|
||||
config().
|
||||
end_per_testcase(_Name, C) ->
|
||||
ok = ct_helper:unset_context(),
|
||||
wapi_ct_helper:stop_mocked_service_sup(?config(test_sup, C)),
|
||||
ok.
|
||||
|
||||
%%% Tests
|
||||
|
||||
-spec create_webhook_ok_test(config()) ->
|
||||
_.
|
||||
create_webhook_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{webhook_manager, fun
|
||||
('Create', _) -> {ok, ?WEBHOOK(?DESTINATION_EVENT_FILTER)}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_webhooks_api:create_webhook/3,
|
||||
#{
|
||||
body => #{
|
||||
<<"identityID">> => IdentityID,
|
||||
<<"url">> => ?STRING,
|
||||
<<"scope">> => #{
|
||||
<<"topic">> => <<"DestinationsTopic">>,
|
||||
<<"eventTypes">> => [<<"DestinationCreated">>]
|
||||
}
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_webhooks_ok_test(config()) ->
|
||||
_.
|
||||
get_webhooks_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{webhook_manager, fun
|
||||
('GetList', _) -> {ok, [?WEBHOOK(?WITHDRAWAL_EVENT_FILTER), ?WEBHOOK(?DESTINATION_EVENT_FILTER)]}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_webhooks_api:get_webhooks/3,
|
||||
#{
|
||||
qs_val => #{
|
||||
<<"identityID">> => IdentityID
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec get_webhook_ok_test(config()) ->
|
||||
_.
|
||||
get_webhook_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{webhook_manager, fun
|
||||
('Get', _) -> {ok, ?WEBHOOK(?WITHDRAWAL_EVENT_FILTER)}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_webhooks_api:get_webhook_by_id/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"webhookID">> => integer_to_binary(?INTEGER)
|
||||
},
|
||||
qs_val => #{
|
||||
<<"identityID">> => IdentityID
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
-spec delete_webhook_ok_test(config()) ->
|
||||
_.
|
||||
delete_webhook_ok_test(C) ->
|
||||
{ok, Identity} = create_identity(C),
|
||||
IdentityID = maps:get(<<"id">>, Identity),
|
||||
wapi_ct_helper:mock_services([{webhook_manager, fun
|
||||
('Delete', _) -> {ok, ok}
|
||||
end}], C),
|
||||
{ok, _} = call_api(
|
||||
fun swag_client_wallet_webhooks_api:delete_webhook_by_id/3,
|
||||
#{
|
||||
binding => #{
|
||||
<<"webhookID">> => integer_to_binary(?INTEGER)
|
||||
},
|
||||
qs_val => #{
|
||||
<<"identityID">> => IdentityID
|
||||
}
|
||||
},
|
||||
ct_helper:cfg(context, C)
|
||||
).
|
||||
|
||||
%%
|
||||
|
||||
-spec call_api(function(), map(), wapi_client_lib:context()) ->
|
||||
{ok, term()} | {error, term()}.
|
||||
call_api(F, Params, Context) ->
|
||||
{Url, PreparedParams, Opts} = wapi_client_lib:make_request(Context, Params),
|
||||
Response = F(Url, PreparedParams, Opts),
|
||||
wapi_client_lib:handle_response(Response).
|
||||
|
||||
create_party(_C) ->
|
||||
ID = genlib:bsuuid(),
|
||||
_ = ff_party:create(ID),
|
||||
ID.
|
||||
|
||||
create_identity(C) ->
|
||||
PartyID = ?config(party, C),
|
||||
Params = #{
|
||||
<<"provider">> => <<"good-one">>,
|
||||
<<"class">> => <<"person">>,
|
||||
<<"name">> => <<"HAHA NO2">>
|
||||
},
|
||||
wapi_wallet_ff_backend:create_identity(Params, create_context(PartyID, C)).
|
||||
|
||||
create_context(PartyID, C) ->
|
||||
maps:merge(create_auth_ctx(PartyID), create_woody_ctx(C)).
|
||||
|
||||
create_woody_ctx(C) ->
|
||||
#{
|
||||
woody_context => ct_helper:get_woody_ctx(C)
|
||||
}.
|
||||
|
||||
create_auth_ctx(PartyID) ->
|
||||
#{
|
||||
swagger_context => #{auth_context => {{PartyID, empty}, #{}}}
|
||||
}.
|
||||
|
||||
issue_token(PartyID, ACL, LifeTime) ->
|
||||
Claims = #{?STRING => ?STRING},
|
||||
{ok, Token} = wapi_authorizer_jwt:issue({{PartyID, wapi_acl:from_list(ACL)}, Claims}, LifeTime),
|
||||
Token.
|
||||
|
@ -82,6 +82,10 @@ get_service_modname(fistful_report) ->
|
||||
{ff_reporter_reports_thrift, 'Reporting'};
|
||||
get_service_modname(file_storage) ->
|
||||
{fs_file_storage_thrift, 'FileStorage'};
|
||||
get_service_modname(fistful_identity) ->
|
||||
{ff_proto_identity_thrift, 'Management'};
|
||||
get_service_modname(fistful_wallet) ->
|
||||
{ff_proto_wallet_thrift, 'Management'};
|
||||
get_service_modname(webhook_manager) ->
|
||||
{ff_proto_webhooker_thrift, 'WebhookManager'}.
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit b9b18f3ee375aa5fd105daf57189ac242c40f572
|
||||
Subproject commit ea4aa042f482551d624fd49a570d28488f479e93
|
@ -1,5 +1,4 @@
|
||||
[
|
||||
|
||||
{kernel, [
|
||||
{log_level, info},
|
||||
{logger, [
|
||||
@ -120,6 +119,7 @@
|
||||
%% 500 => "oops_bodies/500_body"
|
||||
%% }},
|
||||
{realm, <<"external">>},
|
||||
{transport, thrift},
|
||||
{public_endpoint, <<"http://wapi">>},
|
||||
{authorizers, #{
|
||||
jwt => #{
|
||||
|
Loading…
Reference in New Issue
Block a user