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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 3 additions & 26 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,12 @@

{deps, [
{buoy, {git, "https://github.com/lpgauth/buoy.git", {branch, "master"}}},
{erf, {git, "https://github.com/nomasystems/erf.git", {tag, "0.1.1"}}},
{ndto, {git, "https://github.com/nomasystems/ndto.git", {tag, "0.1.0"}}},
{erf, {git, "https://github.com/nomasystems/erf.git", {tag, "0.1.2"}}},
{ndto, {git, "https://github.com/nomasystems/ndto.git", {tag, "0.3.1"}}},
{njson, {git, "https://github.com/nomasystems/njson.git", {branch, "main"}}},
{triq, {git, "https://github.com/nomasystems/triq.git", {branch, "master"}}}
]}.

{overrides, [
{override, erf, [
{plugins, [
{rebar3_ndto, {git, "https://github.com/nomasystems/rebar3_ndto.git", {tag, "0.1.0"}}}
]}
]},
{override, rebar3_ndto, [
{deps, [
{ndto, {git, "https://github.com/nomasystems/ndto.git", {tag, "0.1.0"}}}
]}
]},
{override, ndto, [
{deps, [
{ncalendar, {git, "https://github.com/nomasystems/ncalendar.git", {tag, "0.3.0"}}},
{njson, {git, "https://github.com/nomasystems/njson.git", {tag, "1.0.0"}}}
]}
]}
]}.

{project_plugins, [
{erlfmt, {git, "https://github.com/WhatsApp/erlfmt.git", {branch, "main"}}},
{gradualizer, {git, "https://github.com/josefs/Gradualizer.git", {branch, "master"}}},
Expand Down Expand Up @@ -90,13 +71,9 @@
restcheck_triq
]}.

%% TODO: address this
{gradualizer_opts, [
{exclude, [
"src/restcheck.erl",
"src/restcheck_client.erl",
"src/restcheck_schema.erl",
"src/restcheck_suite.erl",
"src/restcheck_triq.erl"
"src/restcheck_client.erl"
]}
]}.
40 changes: 20 additions & 20 deletions rebar.lock
Original file line number Diff line number Diff line change
@@ -1,48 +1,48 @@
{"1.2.0",
[{<<"buoy">>,
{git,"https://github.com/lpgauth/buoy.git",
{ref,"410180cecd8e279a0b2d9dad291925516920e965"}},
{ref,"ed7ec83750600325dc0ac0016303092d8d21cbf3"}},
0},
{<<"elli">>,
{git,"https://github.com/elli-lib/elli.git",
{ref,"3ec352293ef493c142767127f4113c85541c32cc"}},
1},
{<<"erf">>,
{git,"https://github.com/nomasystems/erf.git",
{ref,"494a47b9fc638d64900cb96abbdea94fae62766f"}},
{ref,"0dac504f9988d3b96154c1e80b8af3defb772b58"}},
0},
{<<"foil">>,
{git,"https://github.com/lpgauth/foil.git",
{ref,"b55c30ff8b21daf89a85d1565d4b836c7f9e549b"}},
1},
{<<"granderl">>,
{git,"https://github.com/tokenrove/granderl.git",
{ref,"baafd1bc825cb1fc022760eae913f774fa6af91b"}},
2},
{<<"metal">>,{pkg,<<"metal">>,<<"0.1.1">>},2},
{<<"foil">>,{pkg,<<"foil">>,<<"0.1.6">>},1},
{<<"knot">>,{pkg,<<"knot">>,<<"0.1.2">>},2},
{<<"metal">>,{pkg,<<"metal">>,<<"0.1.2">>},2},
{<<"ncalendar">>,
{git,"https://github.com/nomasystems/ncalendar.git",
{ref,"0237766de898145bbd55bb26eef8917535f341ca"}},
{ref,"aa5615f6723585e45e82fa5524cb976cdfe3d7f7"}},
1},
{<<"ndto">>,
{git,"https://github.com/nomasystems/ndto.git",
{ref,"491a2441e43afa2fb037c6e7e826c45a383e3bd9"}},
{ref,"295281b72ea4ac85e7c4d5ca42337c68b1aac137"}},
0},
{<<"njson">>,
{git,"https://github.com/nomasystems/njson.git",
{ref,"76ab40033ee977f876e7b3addca5de981ff4a9ef"}},
{ref,"541754472e8d9713fe8a320a7007c15015095c45"}},
0},
{<<"shackle">>,
{git,"https://github.com/lpgauth/shackle.git",
{ref,"a4f7d82d10115cf0d676582b638a56260be685d5"}},
1},
{<<"shackle">>,{pkg,<<"shackle">>,<<"0.7.3">>},1},
{<<"telemetry">>,{pkg,<<"telemetry">>,<<"1.4.2">>},1},
{<<"triq">>,
{git,"https://github.com/nomasystems/triq.git",
{ref,"18b8198de2f19ac3a99236eead774f9934d727be"}},
0}]}.
[
{pkg_hash,[
{<<"metal">>, <<"5D3D1322DA7BCD34B94FED5486F577973685298883954F7A3E517EF5EF6953F5">>}]},
{<<"foil">>, <<"31A9048E8EC6E8AC951ED3C4E4EB8F549465920C70F19E92CD9FC8A6867E7253">>},
{<<"knot">>, <<"3B6C07CD24831D62752D879F031BCEDCFADB343738679EB1774A029D0A8B7D48">>},
{<<"metal">>, <<"7501F14B075380F829498C4CE605643D6C7F0C55F60EBFCFBF694C33A558DF98">>},
{<<"shackle">>, <<"DEDE0777BC297B212DE3DBE4377B391DA991FC011B4D5201960A5C4F11E83B2C">>},
{<<"telemetry">>, <<"A0CB522801DFFB1C49FE6E30561BADFFC7B6D0E180DB1300DF759FAA22062855">>}]},
{pkg_hash_ext,[
{<<"metal">>, <<"88B82B634998A1A768DEDCD372C2F7E657B19445325C0AF5CCBAC62C77210F1D">>}]}
{<<"foil">>, <<"8D34B12D712FFB30AD9E192F57885ABB1A61510CD3D9F6AA44470510A50B979E">>},
{<<"knot">>, <<"42CB91A8024A8857D70A61B33E83D31370D5EB48681B366AE1203CB7C7CCFB38">>},
{<<"metal">>, <<"CAC84E5CF2875B56E68874F3F0B3D598AF3EC30397EC2151D5C81C408961E6E4">>},
{<<"shackle">>, <<"056944FEEB85ED0F3E1B7505FBC7174CDE0788993A19E9ADA85137A23CA56349">>},
{<<"telemetry">>, <<"928F6495066506077862C0D1646609EED891A4326BEE3126BA54B60AF61FEBB1">>}]}
].
38 changes: 32 additions & 6 deletions src/restcheck.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@
spec_parser => module(),
pbt_backend => restcheck_pbt:backend(),
host => binary(),
base_path => binary(),
port => inet:port_number(),
ssl => boolean(),
auth => restcheck_client:auth(),
timeout => non_neg_integer(),
num_requests => pos_integer(),
max_string_length => pos_integer(),
max_array_items => non_neg_integer(),
output_fun => restcheck_pbt:output_fun()
}.
-type test_result() :: {OperationId :: binary(), Result :: ok | {error, Reason :: term()}}.
Expand Down Expand Up @@ -83,14 +86,16 @@ init(State) ->
"usage:\n"
"{restcheck, [\n"
" {spec_path, string()}, % path to spec file\n"
" {spec_parser, module()}, % spec to API AST parser, defaults to erf_oas_3_0\n"
" {spec_parser, module()}, % spec to API AST parser, defaults to erf_parser_oas_3_0\n"
" {pbt_backend, module()}, % restcheck_pbt backend, defaults to restcheck_triq\n"
" {host, string()}, % server host, defaults to \"localhost\"\n"
" {port, inet:port_number()}, % server port number, defaults to 8080\n"
" {ssl, boolean()}, % enable/disable ssl, defaults to false\n"
" {auth, restcheck_client:auth()}, % includes auth headers in the request\n"
" {timeout, pos_integer()} % timeout per request in ms, defaults to 5000\n"
" {num_requests, pos_integer()} % number of requests per operation, defaults to 5000\n"
" {max_string_length, pos_integer()} % default max length for strings without maxLength, defaults to 255\n"
" {max_array_items, non_neg_integer()} % default max items for arrays without maxItems, defaults to 3\n"
" {log_file, string()} % path to file where timestamped events are logged, disabled if no path is provided\n"
"]}."}
]),
Expand All @@ -106,14 +111,17 @@ do(State) ->
RawConf = rebar_state:get(State, restcheck, []),
Conf = #{
spec_path => unicode:characters_to_binary(proplists:get_value(spec_path, RawConf)),
spec_parser => proplists:get_value(spec_parser, RawConf, erf_oas_3_0),
spec_parser => proplists:get_value(spec_parser, RawConf, erf_parser_oas_3_0),
pbt_backend => proplists:get_value(pbt_backend, RawConf, restcheck_triq),
host => unicode:characters_to_binary(proplists:get_value(host, RawConf, "localhost")),
port => proplists:get_value(port, RawConf, 8080),
ssl => proplists:get_value(ssl, RawConf, false),
base_path => unicode:characters_to_binary(proplists:get_value(base_path, RawConf, "")),
timeout => proplists:get_value(timeout, RawConf, 5000),
num_requests => proplists:get_value(num_requests, RawConf, 100),
auth => proplists:get_value(auth, RawConf, undefined)
auth => proplists:get_value(auth, RawConf, undefined),
max_string_length => proplists:get_value(max_string_length, RawConf, undefined),
max_array_items => proplists:get_value(max_array_items, RawConf, undefined)
},
LogFile = proplists:get_value(log_file, RawConf, undefined),
LogEnabled =
Expand Down Expand Up @@ -142,7 +150,8 @@ do(State) ->
ClientConf = #{
host => Host,
port => Port,
ssl => SSL
ssl => SSL,
base_path => maps:get(base_path, Conf, <<>>)
},
{ok, _Pid} = restcheck_client:start_link(ClientName, ClientConf),
case LogEnabled of
Expand Down Expand Up @@ -289,7 +298,8 @@ run(Conf) ->
ClientConf = #{
host => Host,
port => Port,
ssl => SSL
ssl => SSL,
base_path => maps:get(base_path, Conf, <<>>)
},
{ok, _Pid} = restcheck_client:start_link(ClientName, ClientConf),
TestResults = lists:map(
Expand Down Expand Up @@ -333,8 +343,9 @@ file_log(LogFile, Format, Args) ->
LazyTest :: fun(() -> true | {false, Reason :: term()}),
Reason :: term().
generate_and_load_suite(Conf) ->
ok = apply_generation_conf(Conf),
SpecPath = maps:get(spec_path, Conf),
SpecParser = maps:get(spec_format, Conf, erf_oas_3_0),
SpecParser = maps:get(spec_format, Conf, erf_parser_oas_3_0),
case erf_parser:parse(SpecPath, SpecParser) of
{ok, API} ->
APIName = maps:get(name, API),
Expand Down Expand Up @@ -377,6 +388,21 @@ generate_and_load_suite(Conf) ->
{error, Reason}
end.

-spec apply_generation_conf(Conf) -> ok when
Conf :: conf().
apply_generation_conf(Conf) ->
lists:foreach(
fun(Key) ->
case maps:get(Key, Conf, undefined) of
undefined ->
application:unset_env(restcheck, Key);
Value ->
application:set_env(restcheck, Key, Value)
end
end,
[max_string_length, max_array_items]
).

-spec load_dtos(DTOs) -> Result when
DTOs :: [{erf_parser:ref(), ndto:schema()}],
Result :: ok.
Expand Down
70 changes: 43 additions & 27 deletions src/restcheck_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
-type client_config() :: #{
host := binary(),
port => integer(),
ssl => boolean()
ssl => boolean(),
base_path => binary()
}.
-type req_config() :: #{
headers => [{binary(), binary()}],
Expand Down Expand Up @@ -96,6 +97,8 @@
{send_timeout, 50},
{send_timeout_close, true}
]).
-define(BUOY_MAX_RETRIES, 20).
-define(BUOY_RETRY_INTERVAL, 100).

%%%-----------------------------------------------------------------------------
%%% START/STOP EXPORTS
Expand All @@ -110,8 +113,7 @@ start_link(Name, ClientConfig) ->

-spec stop(Name) -> Result when
Name :: atom(),
Result :: ok | {error, Reason},
Reason :: term().
Result :: ok | {error, term()}.
%% @doc Stops the client with given name.
stop(Name) ->
case persistent_term:get(?PERSISTENT_TERM(Name), undefined) of
Expand Down Expand Up @@ -156,9 +158,8 @@ stop(Name) ->
Name :: atom(),
Config :: req_config(),
Opts :: req_opts(),
Result :: {ok, Response} | {error, Reason},
Response :: response(),
Reason :: term().
Result :: {ok, Response} | {error, term()},
Response :: response().
%% @doc Sends a request.
request(Name, Config, Opts) ->
case persistent_term:get(?PERSISTENT_TERM(Name), undefined) of
Expand All @@ -170,7 +171,8 @@ request(Name, Config, Opts) ->
Host = maps:get(host, ClientConfig),
Port = maps:get(port, ClientConfig),
Protocol = protocol(maps:get(ssl, ClientConfig, false)),
RawPath = maps:get(path, Config, <<"/">>),
BasePath = maps:get(base_path, ClientConfig, <<>>),
RawPath = <<BasePath/binary, (maps:get(path, Config, <<"/">>))/binary>>,
Path =
case maps:get(query_parameters, Config, undefined) of
undefined ->
Expand Down Expand Up @@ -248,23 +250,7 @@ request(Name, Config, Opts) ->
headers => Headers,
timeout => Timeout
},
case buoy:request(Method, BuoyUrl, BuoyOpts) of
{ok, BuoyResp} ->
RespStatus = BuoyResp#buoy_resp.status_code,
RespHeaders = headers(BuoyResp#buoy_resp.headers),
RespBody = body(
proplists:get_value(<<"content-type">>, RespHeaders, undefined),
BuoyResp#buoy_resp.body
),
Response = #{
status => RespStatus,
headers => RespHeaders,
body => RespBody
},
{ok, Response};
{error, Reason} ->
{error, Reason}
end
buoy_request(Method, BuoyUrl, BuoyOpts, ?BUOY_MAX_RETRIES)
end.

%%%-----------------------------------------------------------------------------
Expand All @@ -289,7 +275,7 @@ init([Name, ClientConfig]) ->
http ->
?SOCKET_OPTIONS;
https ->
[{log_level, error} | ?SOCKET_OPTIONS]
[{verify, verify_none}, {log_level, error} | ?SOCKET_OPTIONS]
end,
case buoy_pool:start(BuoyUrl, [{socket_options, SocketOptions}]) of
ok ->
Expand All @@ -301,13 +287,43 @@ init([Name, ClientConfig]) ->
%%%-----------------------------------------------------------------------------
%%% INTERNAL FUNCTIONS
%%%-----------------------------------------------------------------------------
buoy_request(Method, BuoyUrl, BuoyOpts, RetriesLeft) ->
case buoy:request(Method, BuoyUrl, BuoyOpts) of
{ok, BuoyResp} ->
RespStatus = BuoyResp#buoy_resp.status_code,
RespHeaders = headers(BuoyResp#buoy_resp.headers),
RespBody = body(
proplists:get_value(<<"content-type">>, RespHeaders, undefined),
BuoyResp#buoy_resp.body
),
Response = #{
status => RespStatus,
headers => RespHeaders,
body => RespBody
},
{ok, Response};
{error, Reason} when
RetriesLeft > 0 andalso (Reason =:= no_server orelse Reason =:= no_socket)
->
timer:sleep(?BUOY_RETRY_INTERVAL),
buoy_request(Method, BuoyUrl, BuoyOpts, RetriesLeft - 1);
{error, Reason} ->
{error, Reason}
end.

body(Body) ->
njson:encode(Body).
{ok, Encoded} = njson:encode(Body),
Encoded.

body(_ContentType, undefined) ->
undefined;
body(<<"application/json">>, BuoyBody) ->
njson:decode(BuoyBody);
case njson:decode(BuoyBody) of
{ok, Decoded} ->
Decoded;
_Error ->
BuoyBody
end;
body(_ContentType, BuoyBody) ->
BuoyBody.

Expand Down
Loading
Loading