mirror of
https://github.com/valitydev/capi-v2.git
synced 2024-11-06 01:55:20 +00:00
CAPI-23 Add initial project structure (#1)
* CAPI-23 Add initial project structure. Add mock-backend and basic tests
This commit is contained in:
parent
9185ca8366
commit
64d7c30607
28
.gitignore
vendored
28
.gitignore
vendored
@ -1,10 +1,20 @@
|
|||||||
.eunit
|
# general
|
||||||
deps
|
log
|
||||||
*.o
|
/_build/
|
||||||
*.beam
|
*~
|
||||||
*.plt
|
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
ebin
|
/*.config
|
||||||
rel/example_project
|
.tags*
|
||||||
.concrete/DEV_MODE
|
*.sublime-workspace
|
||||||
.rebar
|
.DS_Store
|
||||||
|
|
||||||
|
# wercker
|
||||||
|
/_builds/
|
||||||
|
/_cache/
|
||||||
|
/_projects/
|
||||||
|
/_steps/
|
||||||
|
/_temp/
|
||||||
|
/.wercker/
|
||||||
|
|
||||||
|
# compiled
|
||||||
|
swagger
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "schemes/swag"]
|
||||||
|
path = schemes/swag
|
||||||
|
url = git@github.com:rbkmoney/swag.git
|
80
Makefile
Normal file
80
Makefile
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
REBAR := $(shell which rebar3 2>/dev/null || which ./rebar3)
|
||||||
|
RELNAME = capi
|
||||||
|
SUBMODULES = schemes/swag
|
||||||
|
SUBTARGETS = $(patsubst %,%/.git,$(SUBMODULES))
|
||||||
|
|
||||||
|
SWAGGER_SCHEME = schemes/swag/swagger.yaml
|
||||||
|
SWAGGER_APP_PATH = apps/swagger
|
||||||
|
SWAGGER_APP_TARGET = $(SWAGGER_APP_PATH)/rebar.config
|
||||||
|
|
||||||
|
which = $(if $(shell which $(1) 2>/dev/null),\
|
||||||
|
$(shell which $(1) 2>/dev/null),\
|
||||||
|
$(error "Error: could not locate $(1)!"))
|
||||||
|
|
||||||
|
DOCKER = $(call which, docker)
|
||||||
|
PACKER = $(call which, packer)
|
||||||
|
SWAGGER_CODEGEN = $(call which, SWAGGER_CODEGEN)
|
||||||
|
|
||||||
|
.PHONY: all submodules compile devrel start test clean distclean dialyze release containerize swagger_regenerate
|
||||||
|
|
||||||
|
all: compile
|
||||||
|
|
||||||
|
rebar-update:
|
||||||
|
$(REBAR) update
|
||||||
|
|
||||||
|
$(SUBTARGETS): %/.git: %
|
||||||
|
git submodule update --init $<
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
submodules: $(SUBTARGETS) $(SWAGGER_APP_TARGET)
|
||||||
|
|
||||||
|
compile: submodules
|
||||||
|
$(REBAR) compile
|
||||||
|
|
||||||
|
devrel: submodules
|
||||||
|
$(REBAR) release
|
||||||
|
|
||||||
|
start: submodules
|
||||||
|
$(REBAR) run
|
||||||
|
|
||||||
|
test: submodules
|
||||||
|
$(REBAR) ct
|
||||||
|
|
||||||
|
lint: compile
|
||||||
|
elvis rock
|
||||||
|
|
||||||
|
xref: submodules
|
||||||
|
$(REBAR) xref
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(REBAR) clean
|
||||||
|
|
||||||
|
distclean:
|
||||||
|
$(REBAR) clean -a
|
||||||
|
rm -rfv _build _builds _cache _steps _temp
|
||||||
|
|
||||||
|
dialyze:
|
||||||
|
$(REBAR) dialyzer
|
||||||
|
|
||||||
|
BASE_DIR := $(shell pwd)
|
||||||
|
|
||||||
|
release: ~/.docker/config.json distclean
|
||||||
|
$(DOCKER) run --rm -v $(BASE_DIR):$(BASE_DIR) --workdir $(BASE_DIR) rbkmoney/build rebar3 as prod release
|
||||||
|
|
||||||
|
containerize: release ./packer.json
|
||||||
|
$(PACKER) build packer.json
|
||||||
|
|
||||||
|
~/.docker/config.json:
|
||||||
|
test -f ~/.docker/config.json || (echo "Please run: docker login" ; exit 1)
|
||||||
|
|
||||||
|
# Shitty generation. Will be replaced when a container with swagger-codegen appear
|
||||||
|
define swagger_regenerate
|
||||||
|
rm -rf $(SWAGGER_APP_PATH)
|
||||||
|
$(SWAGGER_CODEGEN) generate -i $(SWAGGER_SCHEME) -l erlang-server -o $(SWAGGER_APP_PATH);
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(SWAGGER_APP_TARGET): $(SWAGGER_SCHEME)
|
||||||
|
$(call swagger_regenerate)
|
||||||
|
|
||||||
|
swagger_regenerate:
|
||||||
|
$(call swagger_regenerate)
|
32
README.md
32
README.md
@ -1,2 +1,30 @@
|
|||||||
# erlang_capi
|
# capi
|
||||||
Erlang CAPI version
|
|
||||||
|
A service that does something
|
||||||
|
|
||||||
|
## Сборка
|
||||||
|
|
||||||
|
Для запуска процесса сборки достаточно выполнить просто:
|
||||||
|
|
||||||
|
make
|
||||||
|
|
||||||
|
Чтобы запустить полученную сборку в режиме разработки и получить стандартный [Erlang shell][2], нужно всего лишь:
|
||||||
|
|
||||||
|
make start
|
||||||
|
|
||||||
|
> _Хозяйке на заметку._ При этом используется стандартный Erlang релиз, собранный при помощи [relx][3] в режиме разработчика.
|
||||||
|
|
||||||
|
Рекомендуется вести разработку и сборку проекта в рамках локальной виртуальной среды, предоставляемой [wercker][1]. Настоятельно рекомендуется прогоны тестовых сценариев проводить только в этой среде.
|
||||||
|
|
||||||
|
$ wercker dev
|
||||||
|
|
||||||
|
> _Хозяйке на заметку._ В зависимости от вашего окружения и операционной системы вам может понадобиться [Docker Machine][4].
|
||||||
|
|
||||||
|
## Документация
|
||||||
|
|
||||||
|
Дальнейшую документацию можно почерпнуть, пройдясь по ссылкам в [соответствующем документе](doc/index.md).
|
||||||
|
|
||||||
|
[1]: http://devcenter.wercker.com/learn/basics/the-wercker-cli.html
|
||||||
|
[2]: http://erlang.org/doc/man/shell.html
|
||||||
|
[3]: https://github.com/erlware/relx
|
||||||
|
[4]: https://docs.docker.com/machine/install-machine/
|
||||||
|
19
apps/capi/src/capi.app.src
Normal file
19
apps/capi/src/capi.app.src
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{application, capi , [
|
||||||
|
{description, "A service that does something"},
|
||||||
|
{vsn, "1"},
|
||||||
|
{registered, []},
|
||||||
|
{mod, { capi , []}},
|
||||||
|
{applications, [
|
||||||
|
kernel,
|
||||||
|
stdlib,
|
||||||
|
genlib,
|
||||||
|
swagger
|
||||||
|
]},
|
||||||
|
{env, []},
|
||||||
|
{modules, []},
|
||||||
|
{maintainers, [
|
||||||
|
"Artem Ocheredko <galaxie.stern@gmail.com>"
|
||||||
|
]},
|
||||||
|
{licenses, []},
|
||||||
|
{links, []}
|
||||||
|
]}.
|
21
apps/capi/src/capi.erl
Normal file
21
apps/capi/src/capi.erl
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
%% @doc Public API and application startup.
|
||||||
|
%% @end
|
||||||
|
|
||||||
|
-module(capi).
|
||||||
|
-behaviour(application).
|
||||||
|
|
||||||
|
%% Application callbacks
|
||||||
|
-export([start/2]).
|
||||||
|
-export([stop/1]).
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-spec start(normal, any()) -> {ok, pid()} | {error, any()}.
|
||||||
|
|
||||||
|
start(_StartType, _StartArgs) ->
|
||||||
|
capi_sup:start_link().
|
||||||
|
|
||||||
|
-spec stop(any()) -> ok.
|
||||||
|
|
||||||
|
stop(_State) ->
|
||||||
|
ok.
|
25
apps/capi/src/capi_auth.erl
Normal file
25
apps/capi/src/capi_auth.erl
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
-module(capi_auth).
|
||||||
|
|
||||||
|
-export([auth_api_key/2]).
|
||||||
|
|
||||||
|
-type context() :: #{binary() => any()}.
|
||||||
|
|
||||||
|
-spec auth_api_key(ApiKey :: binary(), OperationID :: atom()) -> {true, Context :: context()} | false.
|
||||||
|
auth_api_key(ApiKey, OperationID) ->
|
||||||
|
{ok, Type, Credentials} = parse_auth_token(ApiKey),
|
||||||
|
{ok, Context} = process_auth(Type, Credentials, OperationID),
|
||||||
|
{true, Context}.
|
||||||
|
|
||||||
|
-spec parse_auth_token(ApiKey :: binary()) -> {ok, bearer, Credentials :: binary()} | {error, Reason :: atom()}.
|
||||||
|
parse_auth_token(ApiKey) ->
|
||||||
|
case ApiKey of
|
||||||
|
<<"Bearer ", Credentials/binary>> ->
|
||||||
|
{ok, bearer, Credentials};
|
||||||
|
_ ->
|
||||||
|
{error, unsupported_auth_scheme}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec process_auth(Type :: atom(), AuthToken :: binary(), OperationID :: atom()) -> {ok, Context :: context()} | {error, Reason :: atom()}.
|
||||||
|
process_auth(bearer, _AuthToken, _OperationID) ->
|
||||||
|
%% @TODO find a jwt library :(
|
||||||
|
{ok, #{}}.
|
185
apps/capi/src/capi_mock_handler.erl
Normal file
185
apps/capi/src/capi_mock_handler.erl
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
-module(capi_mock_handler).
|
||||||
|
|
||||||
|
-behaviour(swagger_logic_handler).
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
%% API callbacks
|
||||||
|
-export([start_link/0]).
|
||||||
|
-export([handle_request/2]).
|
||||||
|
-export([authorize_api_key/2]).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
-export([init/1]).
|
||||||
|
-export([handle_call/3]).
|
||||||
|
-export([handle_cast/2]).
|
||||||
|
-export([handle_info/2]).
|
||||||
|
-export([terminate/2]).
|
||||||
|
-export([code_change/3]).
|
||||||
|
|
||||||
|
-record(state, {
|
||||||
|
tid :: ets:tid(),
|
||||||
|
last_id = 0 ::integer()
|
||||||
|
}).
|
||||||
|
|
||||||
|
-spec start_link() -> {ok, Pid :: pid()} | ignore | {error, Error :: any()}.
|
||||||
|
start_link() ->
|
||||||
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
-spec authorize_api_key(ApiKey :: binary(), OperationID :: atom()) -> Result :: boolean() | {boolean(), #{binary() => any()}}.
|
||||||
|
authorize_api_key(ApiKey, OperationID) -> capi_auth:auth_api_key(ApiKey, OperationID).
|
||||||
|
|
||||||
|
-spec handle_request(OperationID :: atom(), Req :: #{}) -> {Code :: integer, Headers :: [], Response :: #{}}.
|
||||||
|
handle_request('CreateInvoice', Req) ->
|
||||||
|
InvoiceParams = maps:get('CreateInvoiceArgs', Req),
|
||||||
|
ID = new_id(),
|
||||||
|
Invoice = #{
|
||||||
|
<<"id">> => ID,
|
||||||
|
<<"amount">> => maps:get(<<"amount">>, InvoiceParams),
|
||||||
|
<<"context">> => maps:get(<<"context">>, InvoiceParams),
|
||||||
|
<<"currency">> => maps:get(<<"currency">>, InvoiceParams),
|
||||||
|
<<"description">> => maps:get(<<"description">>, InvoiceParams),
|
||||||
|
<<"dueDate">> => maps:get(<<"dueDate">>, InvoiceParams),
|
||||||
|
<<"product">> => maps:get(<<"product">>, InvoiceParams),
|
||||||
|
<<"shopID">> => maps:get(<<"shopID">>, InvoiceParams)
|
||||||
|
},
|
||||||
|
put_data(ID, invoice, Invoice),
|
||||||
|
Resp = #{
|
||||||
|
<<"id">> => ID
|
||||||
|
},
|
||||||
|
{201, [], Resp};
|
||||||
|
|
||||||
|
handle_request('CreatePayment', Req) ->
|
||||||
|
InvoiceID = maps:get('invoice_id', Req),
|
||||||
|
PaymentParams = maps:get('CreatePaymentArgs', Req),
|
||||||
|
PaymentSession = maps:get(<<"paymentSession">>, PaymentParams),
|
||||||
|
case match_data({{'$1', session}, PaymentSession}) of
|
||||||
|
[[_SessionID]] ->
|
||||||
|
delete_data({{'$1', session}, '_'}),
|
||||||
|
PaymentID = new_id(),
|
||||||
|
Payment = #{
|
||||||
|
<<"id">> => PaymentID ,
|
||||||
|
<<"invoiceID">> => InvoiceID,
|
||||||
|
<<"createdAt">> => <<"2016-12-12 17:00:00">>,
|
||||||
|
<<"status">> => <<"pending">>,
|
||||||
|
<<"paymentToolToken">> => maps:get(<<"paymentToolToken">>, PaymentParams)
|
||||||
|
},
|
||||||
|
put_data(PaymentID, payment, Payment),
|
||||||
|
Resp = #{
|
||||||
|
<<"id">> => PaymentID
|
||||||
|
},
|
||||||
|
{201, [], Resp};
|
||||||
|
_ ->
|
||||||
|
Resp = logic_error(<<"expired_session">>, <<"Payment session is not valid">>),
|
||||||
|
{400, [], Resp}
|
||||||
|
end;
|
||||||
|
|
||||||
|
handle_request('CreatePaymentToolToken', Req) ->
|
||||||
|
Params = maps:get('PaymentTool', Req),
|
||||||
|
Token = tokenize_payment_tool(Params),
|
||||||
|
put_data(new_id(), token, Token),
|
||||||
|
Session = generate_session(),
|
||||||
|
put_data(new_id(), session, Session),
|
||||||
|
Resp = #{
|
||||||
|
<<"token">> => Token,
|
||||||
|
<<"session">> => Session
|
||||||
|
},
|
||||||
|
{201, [], Resp};
|
||||||
|
|
||||||
|
handle_request('GetInvoiceByID', Req) ->
|
||||||
|
InvoiceID = maps:get(invoice_id, Req),
|
||||||
|
[{_, Invoice}] = get_data(InvoiceID, invoice),
|
||||||
|
{200, [], Invoice};
|
||||||
|
|
||||||
|
handle_request('GetInvoiceEvents', _Req) ->
|
||||||
|
Events = [],
|
||||||
|
{200, [], Events};
|
||||||
|
|
||||||
|
handle_request('GetPaymentByID', Req) ->
|
||||||
|
PaymentID = maps:get(payment_id, Req),
|
||||||
|
[{_, Payment}] = get_data(PaymentID, payment),
|
||||||
|
{200, [], Payment};
|
||||||
|
|
||||||
|
handle_request(OperationID, Req) ->
|
||||||
|
io:format(user, "Got request to process: ~p~n", [{OperationID, Req}]),
|
||||||
|
{501, [], <<"Not implemented">>}.
|
||||||
|
|
||||||
|
|
||||||
|
%%%
|
||||||
|
|
||||||
|
-type callref() :: {pid(), Tag :: reference()}.
|
||||||
|
-type st() :: #state{}.
|
||||||
|
|
||||||
|
-spec init( Args :: any()) -> {ok, st()}.
|
||||||
|
init(_Args) ->
|
||||||
|
TID = ets:new(mock_storage, [ordered_set, private, {heir, none}]),
|
||||||
|
{ok, #state{tid = TID}}.
|
||||||
|
|
||||||
|
-spec handle_call(Request :: any(), From :: callref(), st()) -> {reply, term(), st()} | {noreply, st()}.
|
||||||
|
handle_call({put, ID, Type, Data}, _From, State = #state{tid = TID}) ->
|
||||||
|
Result = ets:insert(TID, {wrap_id(ID, Type), Data}),
|
||||||
|
{reply, Result, State};
|
||||||
|
|
||||||
|
handle_call({get, ID, Type}, _From, State = #state{tid = TID}) ->
|
||||||
|
Result = ets:lookup(TID, wrap_id(ID, Type)),
|
||||||
|
{reply, Result, State};
|
||||||
|
|
||||||
|
handle_call({match, Pattern}, _From, State = #state{tid = TID}) ->
|
||||||
|
Result = ets:match(TID, Pattern),
|
||||||
|
{reply, Result, State};
|
||||||
|
|
||||||
|
handle_call({delete, Pattern}, _From, State = #state{tid = TID}) ->
|
||||||
|
Result = ets:match_delete(TID, Pattern),
|
||||||
|
{reply, Result, State};
|
||||||
|
|
||||||
|
handle_call(id, _From, State = #state{last_id = ID}) ->
|
||||||
|
NewID = ID + 1,
|
||||||
|
{reply, NewID, State#state{last_id = NewID}}.
|
||||||
|
|
||||||
|
-spec handle_cast(Request :: any(), st()) -> {noreply, st()}.
|
||||||
|
handle_cast(_Request, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
-spec handle_info(any(), st()) -> {noreply, st()}.
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
-spec terminate(any(), st()) -> ok.
|
||||||
|
terminate(_Reason, _State) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
-spec code_change(Vsn :: term() | {down, Vsn :: term()}, st(), term()) -> {error, noimpl}.
|
||||||
|
code_change(_OldVsn, _State, _Extra) ->
|
||||||
|
{error, noimpl}.
|
||||||
|
|
||||||
|
put_data(ID, Type, Data) ->
|
||||||
|
gen_server:call(?MODULE, {put, ID, Type, Data}).
|
||||||
|
|
||||||
|
get_data(ID, Type) ->
|
||||||
|
gen_server:call(?MODULE, {get, ID, Type}).
|
||||||
|
|
||||||
|
match_data(Pattern) ->
|
||||||
|
gen_server:call(?MODULE, {match, Pattern}).
|
||||||
|
|
||||||
|
delete_data(Pattern) ->
|
||||||
|
gen_server:call(?MODULE, {delete, Pattern}).
|
||||||
|
|
||||||
|
new_id() ->
|
||||||
|
ID = gen_server:call(?MODULE, id),
|
||||||
|
genlib:to_binary(ID).
|
||||||
|
|
||||||
|
tokenize_payment_tool(Params = #{<<"paymentToolType">> := <<"cardData">>}) ->
|
||||||
|
CardNumber = genlib:to_binary(maps:get(<<"cardNumber">>, Params)),
|
||||||
|
ExpDate = maps:get(<<"expDate">>, Params),
|
||||||
|
erlang:md5(<<CardNumber/binary, ExpDate/binary>>);
|
||||||
|
|
||||||
|
tokenize_payment_tool(_) ->
|
||||||
|
error(unsupported_payment_tool). %%@TODO move this error to upper level
|
||||||
|
|
||||||
|
generate_session() ->
|
||||||
|
integer_to_binary(rand:uniform(100000)).
|
||||||
|
|
||||||
|
logic_error(Code, Message) ->
|
||||||
|
#{code => Code, message => Message}.
|
||||||
|
|
||||||
|
wrap_id(ID, Type) ->
|
||||||
|
{ID, Type}.
|
46
apps/capi/src/capi_sup.erl
Normal file
46
apps/capi/src/capi_sup.erl
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
%% @doc Top level supervisor.
|
||||||
|
%% @end
|
||||||
|
|
||||||
|
-module(capi_sup).
|
||||||
|
-behaviour(supervisor).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start_link/0]).
|
||||||
|
|
||||||
|
%% Supervisor callbacks
|
||||||
|
-export([init/1]).
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-spec start_link() -> {ok, pid()} | {error, {already_started, pid()}}.
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
-spec init([]) -> {ok, tuple()}.
|
||||||
|
init([]) ->
|
||||||
|
{LogicHandler, LogicHandlerSpec} = get_logic_handler_info(),
|
||||||
|
SwaggerSpec = swagger_server:child_spec(swagger, #{
|
||||||
|
ip => capi_utils:get_hostname_ip(genlib_app:env(capi, host, "0.0.0.0")),
|
||||||
|
port => genlib_app:env(capi, port, 8080),
|
||||||
|
net_opts => [],
|
||||||
|
logic_handler => LogicHandler
|
||||||
|
}),
|
||||||
|
{ok, {
|
||||||
|
{one_for_all, 0, 1}, [LogicHandlerSpec, SwaggerSpec]
|
||||||
|
}}.
|
||||||
|
|
||||||
|
-spec get_logic_handler_info() -> {Handler :: atom(), Spec :: supervisor:child_spec()}.
|
||||||
|
get_logic_handler_info() ->
|
||||||
|
case genlib_app:env(capi, service_type) of
|
||||||
|
mock ->
|
||||||
|
Spec = genlib_app:permanent(
|
||||||
|
{capi_mock_handler, capi_mock_handler, start_link},
|
||||||
|
none,
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
{capi_mock_handler, Spec};
|
||||||
|
undefined -> exit(undefined_service_type)
|
||||||
|
end.
|
16
apps/capi/src/capi_utils.erl
Normal file
16
apps/capi/src/capi_utils.erl
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
-module(capi_utils).
|
||||||
|
|
||||||
|
-export([get_hostname_ip/1]).
|
||||||
|
|
||||||
|
-spec get_hostname_ip(Hostname | IP) -> IP when
|
||||||
|
Hostname :: string(),
|
||||||
|
IP :: inet:ip_address().
|
||||||
|
|
||||||
|
get_hostname_ip(Host) ->
|
||||||
|
% TODO: respect preferred address family
|
||||||
|
case inet:getaddr(Host, inet) of
|
||||||
|
{ok, IP} ->
|
||||||
|
IP;
|
||||||
|
{error, Error} ->
|
||||||
|
exit(Error)
|
||||||
|
end.
|
180
apps/capi/test/capi_tests_SUITE.erl
Normal file
180
apps/capi/test/capi_tests_SUITE.erl
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
-module(capi_tests_SUITE).
|
||||||
|
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
|
||||||
|
-export([all/0]).
|
||||||
|
-export([init_per_suite/1]).
|
||||||
|
-export([end_per_suite/1]).
|
||||||
|
|
||||||
|
%% test cases
|
||||||
|
-export([
|
||||||
|
authorization_error_test/1,
|
||||||
|
create_invoice_badard_test/1,
|
||||||
|
create_invoice_ok_test/1,
|
||||||
|
create_payment_ok_test/1,
|
||||||
|
create_payment_tool_token_ok_test/1,
|
||||||
|
get_invoice_by_id_ok_test/1,
|
||||||
|
get_invoice_events_ok_test/1,
|
||||||
|
get_payment_by_id_ok_test/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(CAPI_HOST, "0.0.0.0").
|
||||||
|
-define(CAPI_PORT, 8080).
|
||||||
|
-define(CAPI_SERVICE_TYPE, mock).
|
||||||
|
|
||||||
|
all() ->
|
||||||
|
[
|
||||||
|
authorization_error_test,
|
||||||
|
create_invoice_badard_test,
|
||||||
|
create_invoice_ok_test,
|
||||||
|
create_payment_ok_test,
|
||||||
|
create_payment_tool_token_ok_test,
|
||||||
|
get_invoice_by_id_ok_test,
|
||||||
|
get_invoice_events_ok_test,
|
||||||
|
get_payment_by_id_ok_test
|
||||||
|
].
|
||||||
|
|
||||||
|
%%
|
||||||
|
%% starting/stopping
|
||||||
|
%%
|
||||||
|
init_per_suite(C) ->
|
||||||
|
{_, Seed} = calendar:local_time(),
|
||||||
|
random:seed(Seed),
|
||||||
|
test_configuration(),
|
||||||
|
{ok, Apps1} = application:ensure_all_started(capi),
|
||||||
|
{ok, Apps2} = application:ensure_all_started(hackney),
|
||||||
|
[{apps, Apps1 ++ Apps2} | C].
|
||||||
|
|
||||||
|
end_per_suite(C) ->
|
||||||
|
[application_stop(App) || App <- proplists:get_value(apps, C)].
|
||||||
|
|
||||||
|
application_stop(App = sasl) ->
|
||||||
|
%% hack for preventing sasl deadlock
|
||||||
|
%% http://erlang.org/pipermail/erlang-questions/2014-May/079012.html
|
||||||
|
error_logger:delete_report_handler(cth_log_redirect),
|
||||||
|
_ = application:stop(App),
|
||||||
|
error_logger:add_report_handler(cth_log_redirect),
|
||||||
|
ok;
|
||||||
|
application_stop(App) ->
|
||||||
|
application:stop(App).
|
||||||
|
|
||||||
|
%% tests
|
||||||
|
authorization_error_test(_) ->
|
||||||
|
{ok, 401, _RespHeaders, _Body} = call(get, "/invoices/22?limit=22", #{}, []).
|
||||||
|
|
||||||
|
create_invoice_badard_test(_) ->
|
||||||
|
{ok, 400, _RespHeaders, _Body} = default_call(post, "/invoices", #{}).
|
||||||
|
|
||||||
|
create_invoice_ok_test(_) ->
|
||||||
|
#{<<"id">> := _InvoiceID} = default_create_invoice().
|
||||||
|
|
||||||
|
create_payment_ok_test(_) ->
|
||||||
|
#{<<"id">> := InvoiceID} = default_create_invoice(),
|
||||||
|
#{
|
||||||
|
<<"session">> := PaymentSession,
|
||||||
|
<<"token">> := PaymentToolToken
|
||||||
|
} = default_tokenize_card(),
|
||||||
|
#{<<"id">> := _PaymentID} = default_create_payment(InvoiceID, PaymentSession, PaymentToolToken).
|
||||||
|
|
||||||
|
create_payment_tool_token_ok_test(_) ->
|
||||||
|
#{<<"token">> := _Token, <<"session">> := _Session} = default_tokenize_card().
|
||||||
|
|
||||||
|
get_invoice_by_id_ok_test(_) ->
|
||||||
|
#{<<"id">> := InvoiceID} = default_create_invoice(),
|
||||||
|
Path = "/invoices/" ++ genlib:to_list(InvoiceID),
|
||||||
|
{ok, 200, _RespHeaders, _Body} = default_call(get, Path, #{}).
|
||||||
|
|
||||||
|
get_invoice_events_ok_test(_) ->
|
||||||
|
#{<<"id">> := InvoiceID} = default_create_invoice(),
|
||||||
|
#{
|
||||||
|
<<"session">> := PaymentSession,
|
||||||
|
<<"token">> := PaymentToolToken
|
||||||
|
} = default_tokenize_card(),
|
||||||
|
#{<<"id">> := _PaymentID} = default_create_payment(InvoiceID, PaymentSession, PaymentToolToken),
|
||||||
|
|
||||||
|
timer:sleep(1000),
|
||||||
|
Path = "/invoices/" ++ genlib:to_list(InvoiceID) ++ "/events/?limit=100",
|
||||||
|
{ok, 200, _RespHeaders, _Body} = default_call(get, Path, #{}).
|
||||||
|
|
||||||
|
get_payment_by_id_ok_test(_) ->
|
||||||
|
#{<<"id">> := InvoiceID} = default_create_invoice(),
|
||||||
|
#{
|
||||||
|
<<"session">> := PaymentSession,
|
||||||
|
<<"token">> := PaymentToolToken
|
||||||
|
} = default_tokenize_card(),
|
||||||
|
#{<<"id">> := PaymentID} = default_create_payment(InvoiceID, PaymentSession, PaymentToolToken),
|
||||||
|
|
||||||
|
Path = "/invoices/" ++ genlib:to_list(InvoiceID) ++ "/payments/" ++ genlib:to_list(PaymentID),
|
||||||
|
{ok, 200, _RespHeaders, _Body} = default_call(get, Path, #{}).
|
||||||
|
%% helpers
|
||||||
|
|
||||||
|
test_configuration() ->
|
||||||
|
application:set_env(capi, host, ?CAPI_HOST),
|
||||||
|
application:set_env(capi, port, ?CAPI_PORT),
|
||||||
|
application:set_env(capi, service_type, ?CAPI_SERVICE_TYPE).
|
||||||
|
|
||||||
|
default_call(Method, Path, Body) ->
|
||||||
|
call(Method, Path, Body, [x_request_id_header(), auth_header(), json_content_type_header()]).
|
||||||
|
|
||||||
|
call(Method, Path, Body, Headers) ->
|
||||||
|
Url = get_url(Path),
|
||||||
|
PreparedBody = jsx:encode(Body),
|
||||||
|
{ok, Code, RespHeaders, ClientRef} = hackney:request(Method, Url, Headers, PreparedBody),
|
||||||
|
{ok, Code, RespHeaders, get_body(ClientRef)}.
|
||||||
|
|
||||||
|
get_url(Path) ->
|
||||||
|
?CAPI_HOST ++ ":" ++ integer_to_list(?CAPI_PORT) ++ Path.
|
||||||
|
|
||||||
|
x_request_id_header() ->
|
||||||
|
{<<"X-Request-ID">>, integer_to_binary(rand:uniform(100000))}.
|
||||||
|
|
||||||
|
auth_header() ->
|
||||||
|
{<<"Authorization">>, <<"Bearer ", (auth_token())/binary>>} .
|
||||||
|
|
||||||
|
auth_token() ->
|
||||||
|
<<"I can't find JWT library :(">>.
|
||||||
|
|
||||||
|
json_content_type_header() ->
|
||||||
|
{<<"Content-Type">>, <<"application/json">>}.
|
||||||
|
|
||||||
|
default_create_invoice() ->
|
||||||
|
Req = #{
|
||||||
|
<<"shopID">> => <<"test_shop_id">>,
|
||||||
|
<<"amount">> => 100000,
|
||||||
|
<<"currency">> => <<"RUB">>,
|
||||||
|
<<"context">> => #{
|
||||||
|
<<"invoice_dummy_context">> => <<"test_value">>
|
||||||
|
},
|
||||||
|
<<"dueDate">> => <<"2017-07-11 10:00:00">>,
|
||||||
|
<<"product">> => <<"test_product">>,
|
||||||
|
<<"description">> => <<"test_invoice_description">>
|
||||||
|
},
|
||||||
|
{ok, 201, _RespHeaders, Body} = default_call(post, "/invoices", Req),
|
||||||
|
decode_body(Body).
|
||||||
|
|
||||||
|
default_tokenize_card() ->
|
||||||
|
Req = #{
|
||||||
|
<<"paymentToolType">> => <<"cardData">>,
|
||||||
|
<<"cardHolder">> => <<"Alexander Weinerschnitzel">>,
|
||||||
|
<<"cardNumber">> => 4111111111111111,
|
||||||
|
<<"expDate">> => <<"08/27">>,
|
||||||
|
<<"cvv">> => <<"232">>
|
||||||
|
},
|
||||||
|
{ok, 201, _RespHeaders, Body} = default_call(post, "/payment_tools", Req),
|
||||||
|
decode_body(Body).
|
||||||
|
|
||||||
|
default_create_payment(InvoiceID, PaymentSession, PaymentToolToken) ->
|
||||||
|
Req = #{
|
||||||
|
<<"paymentSession">> => PaymentSession,
|
||||||
|
<<"paymentToolToken">> => PaymentToolToken
|
||||||
|
},
|
||||||
|
Path = "/invoices/" ++ genlib:to_list(InvoiceID) ++ "/payments",
|
||||||
|
{ok, 201, _RespHeaders, Body} = default_call(post, Path, Req),
|
||||||
|
decode_body(Body).
|
||||||
|
|
||||||
|
get_body(ClientRef) ->
|
||||||
|
{ok, Body} = hackney:body(ClientRef),
|
||||||
|
Body.
|
||||||
|
|
||||||
|
decode_body(Body) ->
|
||||||
|
jsx:decode(Body, [return_maps]).
|
7
config/sys.config
Normal file
7
config/sys.config
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{ capi , [
|
||||||
|
{host, "0.0.0.0"},
|
||||||
|
{port, 8080},
|
||||||
|
{service_type, mock}
|
||||||
|
]}
|
||||||
|
].
|
6
config/vm.args
Normal file
6
config/vm.args
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-sname capi
|
||||||
|
|
||||||
|
-setcookie capi_cookie
|
||||||
|
|
||||||
|
+K true
|
||||||
|
+A 10
|
5
doc/index.md
Normal file
5
doc/index.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Документация
|
||||||
|
|
||||||
|
1. [Общее описание](overview.md)
|
||||||
|
1. [Установка](install.md)
|
||||||
|
1. [Первоначалная настройка](configuration.md)
|
24
packer.json
Normal file
24
packer.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"builders": [
|
||||||
|
{
|
||||||
|
"type": "docker",
|
||||||
|
"image": "rbkmoney/service_erlang",
|
||||||
|
"pull": "true",
|
||||||
|
"commit": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"provisioners": [
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"source": "./_build/prod/rel/capi",
|
||||||
|
"destination": "/opt/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"post-processors": [
|
||||||
|
{
|
||||||
|
"type": "docker-tag",
|
||||||
|
"repository": "rbkmoney/capi_erlang"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
21
rebar.lock
Normal file
21
rebar.lock
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[{<<"certifi">>,{pkg,<<"certifi">>,<<"0.4.0">>},1},
|
||||||
|
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"1.0.4">>},0},
|
||||||
|
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"1.0.2">>},1},
|
||||||
|
{<<"genlib">>,
|
||||||
|
{git,"https://github.com/rbkmoney/genlib.git",
|
||||||
|
{ref,"66db7fe296465a875b6894eb5ac944c90f82f913"}},
|
||||||
|
0},
|
||||||
|
{<<"hackney">>,{pkg,<<"hackney">>,<<"1.5.7">>},0},
|
||||||
|
{<<"idna">>,{pkg,<<"idna">>,<<"1.2.0">>},1},
|
||||||
|
{<<"jesse">>,
|
||||||
|
{git,"https://github.com/for-GET/jesse.git",
|
||||||
|
{ref,"f4270eb0a9bc64291c6e2205645b45b5b7b686f8"}},
|
||||||
|
0},
|
||||||
|
{<<"jsx">>,
|
||||||
|
{git,"https://github.com/talentdeficit/jsx.git",
|
||||||
|
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
|
||||||
|
0},
|
||||||
|
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1},
|
||||||
|
{<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.0.2">>},1},
|
||||||
|
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.2.1">>},1},
|
||||||
|
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.0">>},1}].
|
1
schemes/swag
Submodule
1
schemes/swag
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 60b0337c99be8546179f9473adcc1596395dc831
|
16
wercker.yml
Normal file
16
wercker.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
box: erlang:18
|
||||||
|
|
||||||
|
dev:
|
||||||
|
steps:
|
||||||
|
- internal/shell:
|
||||||
|
code: make compile
|
||||||
|
|
||||||
|
build:
|
||||||
|
steps:
|
||||||
|
- script:
|
||||||
|
name: run test suite
|
||||||
|
code: make test
|
||||||
|
after-steps:
|
||||||
|
- slack-notifier:
|
||||||
|
url: ${SLACK_WEBHOOK_URL}
|
||||||
|
username: "wercker"
|
Loading…
Reference in New Issue
Block a user