328 lines
14 KiB
Erlang
328 lines
14 KiB
Erlang
-module(emqx_swagger_response_SUITE).
|
|
|
|
-behaviour(minirest_api).
|
|
-behaviour(hocon_schema).
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
-include_lib("typerefl/include/types.hrl").
|
|
-include_lib("hocon/include/hoconsc.hrl").
|
|
-import(hoconsc, [mk/2]).
|
|
|
|
-export([all/0, suite/0, groups/0]).
|
|
-export([paths/0, api_spec/0, schema/1, fields/1]).
|
|
-export([t_simple_binary/1, t_object/1, t_nest_object/1, t_empty/1, t_error/1,
|
|
t_raw_local_ref/1, t_raw_remote_ref/1, t_hocon_schema_function/1,
|
|
t_local_ref/1, t_remote_ref/1, t_bad_ref/1, t_none_ref/1, t_nest_ref/1,
|
|
t_ref_array_with_key/1, t_ref_array_without_key/1, t_api_spec/1]).
|
|
|
|
all() -> [{group, spec}].
|
|
suite() -> [{timetrap, {minutes, 1}}].
|
|
groups() -> [
|
|
{spec, [parallel], [
|
|
t_api_spec, t_simple_binary, t_object, t_nest_object, t_error,
|
|
t_raw_local_ref, t_raw_remote_ref, t_empty, t_hocon_schema_function,
|
|
t_local_ref, t_remote_ref, t_bad_ref, t_none_ref,
|
|
t_ref_array_with_key, t_ref_array_without_key, t_nest_ref]}
|
|
].
|
|
|
|
t_simple_binary(_config) ->
|
|
Path = "/simple/bin",
|
|
ExpectSpec = #{description => <<"binary ok">>},
|
|
ExpectRefs = [],
|
|
validate(Path, ExpectSpec, ExpectRefs),
|
|
ok.
|
|
|
|
t_object(_config) ->
|
|
Path = "/object",
|
|
Object =
|
|
#{<<"content">> => #{<<"application/json">> =>
|
|
#{<<"schema">> => #{required => [<<"timeout">>, <<"per_page">>],
|
|
<<"properties">> => [
|
|
{<<"per_page">>, #{description => <<"good per page desc">>, example => 1, maximum => 100, minimum => 1, type => integer}},
|
|
{<<"timeout">>, #{default => 5, <<"oneOf">> => [#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}},
|
|
{<<"inner_ref">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}],
|
|
<<"type">> => object}}}},
|
|
ExpectRefs = [{?MODULE, good_ref}],
|
|
validate(Path, Object, ExpectRefs),
|
|
ok.
|
|
|
|
t_error(_Config) ->
|
|
Path = "/error",
|
|
Error400 = #{<<"content">> =>
|
|
#{<<"application/json">> => #{<<"schema">> => #{<<"type">> => object,
|
|
<<"properties">> =>
|
|
[
|
|
{<<"code">>, #{enum => ['Bad1','Bad2'], type => string}},
|
|
{<<"message">>, #{description => <<"Details description of the error.">>,
|
|
example => <<"Bad request desc">>, type => string}}]
|
|
}}}},
|
|
Error404 = #{<<"content">> =>
|
|
#{<<"application/json">> => #{<<"schema">> => #{<<"type">> => object,
|
|
<<"properties">> =>
|
|
[
|
|
{<<"code">>, #{enum => ['Not-Found'], type => string}},
|
|
{<<"message">>, #{description => <<"Details description of the error.">>,
|
|
example => <<"Error code to troubleshoot problems.">>, type => string}}]
|
|
}}}},
|
|
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
|
?assertEqual(test, OperationId),
|
|
Response = maps:get(responses, maps:get(get, Spec)),
|
|
?assertEqual(Error400, maps:get(<<"400">>, Response)),
|
|
?assertEqual(Error404, maps:get(<<"404">>, Response)),
|
|
?assertEqual(#{}, maps:without([<<"400">>, <<"404">>], Response)),
|
|
?assertEqual([], Refs),
|
|
ok.
|
|
|
|
t_nest_object(_Config) ->
|
|
Path = "/nest/object",
|
|
Object =
|
|
#{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
|
|
#{required => [<<"timeout">>], <<"type">> => object, <<"properties">> => [
|
|
{<<"per_page">>, #{description => <<"good per page desc">>, example => 1, maximum => 100, minimum => 1, type => integer}},
|
|
{<<"timeout">>, #{default => 5, <<"oneOf">> =>
|
|
[#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}},
|
|
{<<"nest_object">>, #{<<"type">> => object, <<"properties">> => [
|
|
{<<"good_nest_1">>, #{example => 100, type => integer}},
|
|
{<<"good_nest_2">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}
|
|
}]}},
|
|
{<<"inner_ref">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}]
|
|
}}}},
|
|
ExpectRefs = [{?MODULE, good_ref}],
|
|
validate(Path, Object, ExpectRefs),
|
|
ok.
|
|
|
|
t_empty(_Config) ->
|
|
?assertThrow({error,
|
|
#{msg := <<"Object only supports not empty proplists">>,
|
|
args := [], module := ?MODULE}}, validate("/empty", error, [])),
|
|
ok.
|
|
|
|
t_raw_local_ref(_Config) ->
|
|
Path = "/raw/ref/local",
|
|
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
|
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}}},
|
|
ExpectRefs = [{?MODULE, good_ref}],
|
|
validate(Path, Object, ExpectRefs),
|
|
ok.
|
|
|
|
t_raw_remote_ref(_Config) ->
|
|
Path = "/raw/ref/remote",
|
|
Object = #{<<"content">> =>
|
|
#{<<"application/json">> => #{<<"schema">> => #{
|
|
<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>}}}},
|
|
ExpectRefs = [{emqx_swagger_remote_schema, "ref1"}],
|
|
validate(Path, Object, ExpectRefs),
|
|
ok.
|
|
|
|
t_local_ref(_Config) ->
|
|
Path = "/ref/local",
|
|
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
|
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}}},
|
|
ExpectRefs = [{?MODULE, good_ref}],
|
|
validate(Path, Object, ExpectRefs),
|
|
ok.
|
|
|
|
t_remote_ref(_Config) ->
|
|
Path = "/ref/remote",
|
|
Object = #{<<"content">> =>
|
|
#{<<"application/json">> => #{<<"schema">> => #{
|
|
<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>}}}},
|
|
ExpectRefs = [{emqx_swagger_remote_schema, "ref1"}],
|
|
validate(Path, Object, ExpectRefs),
|
|
ok.
|
|
|
|
t_bad_ref(_Config) ->
|
|
Path = "/ref/bad",
|
|
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
|
|
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.bad_ref">>}}}},
|
|
ExpectRefs = [{?MODULE, bad_ref}],
|
|
?assertThrow({error, #{module := ?MODULE, msg := <<"Object only supports not empty proplists">>}},
|
|
validate(Path, Object, ExpectRefs)),
|
|
ok.
|
|
|
|
t_none_ref(_Config) ->
|
|
Path = "/ref/none",
|
|
?assertThrow({error, #{mfa := {?MODULE, schema, ["/ref/none"]},
|
|
reason := function_clause}}, validate(Path, #{}, [])),
|
|
ok.
|
|
|
|
t_nest_ref(_Config) ->
|
|
Path = "/ref/nest/ref",
|
|
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
|
<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.nest_ref">>}}}},
|
|
ExpectRefs = [{?MODULE, nest_ref}],
|
|
validate(Path, Object, ExpectRefs),
|
|
ok.
|
|
|
|
t_ref_array_with_key(_Config) ->
|
|
Path = "/ref/array/with/key",
|
|
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
|
required => [<<"timeout">>], <<"type">> => object, <<"properties">> => [
|
|
{<<"per_page">>, #{description => <<"good per page desc">>, example => 1, maximum => 100, minimum => 1, type => integer}},
|
|
{<<"timeout">>, #{default => 5, <<"oneOf">> =>
|
|
[#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}},
|
|
{<<"assert">>, #{description => <<"money">>, example => 3.14159,type => number}},
|
|
{<<"number_ex">>, #{description => <<"number example">>, example => 42,type => number}},
|
|
{<<"percent_ex">>, #{description => <<"percent example">>, example => <<"12%">>,type => number}},
|
|
{<<"duration_ms_ex">>, #{description => <<"duration ms example">>, example => <<"32s">>,type => string}},
|
|
{<<"atom_ex">>, #{description => <<"atom ex">>, example => atom, type => string}},
|
|
{<<"array_refs">>, #{items => #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}, type => array}}
|
|
]}
|
|
}}},
|
|
ExpectRefs = [{?MODULE, good_ref}],
|
|
validate(Path, Object, ExpectRefs),
|
|
ok.
|
|
|
|
t_ref_array_without_key(_Config) ->
|
|
Path = "/ref/array/without/key",
|
|
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
|
|
items => #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>},
|
|
type => array}}}},
|
|
ExpectRefs = [{?MODULE, good_ref}],
|
|
validate(Path, Object, ExpectRefs),
|
|
ok.
|
|
t_hocon_schema_function(_Config) ->
|
|
Path = "/ref/hocon/schema/function",
|
|
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
|
|
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.root">>}}}},
|
|
ExpectComponents = [
|
|
#{<<"emqx_swagger_remote_schema.ref1">> => #{<<"type">> => object,
|
|
<<"properties">> => [
|
|
{<<"protocol">>, #{enum => [http, https], type => string}},
|
|
{<<"port">>, #{default => 18083, example => 100, type => integer}}]
|
|
}},
|
|
#{<<"emqx_swagger_remote_schema.ref2">> => #{<<"type">> => object,
|
|
<<"properties">> => [
|
|
{<<"page">>, #{description => <<"good page">>, example => 1, maximum => 100, minimum => 1, type => integer}},
|
|
{<<"another_ref">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref3">>}}
|
|
]
|
|
}},
|
|
#{<<"emqx_swagger_remote_schema.ref3">> => #{<<"type">> => object,
|
|
<<"properties">> => [
|
|
{<<"ip">>, #{description => <<"IP:Port">>, example => <<"127.0.0.1:80">>,type => string}},
|
|
{<<"version">>, #{description => <<"a good version">>, example => <<"1.0.0">>, type => string}}]
|
|
}},
|
|
#{<<"emqx_swagger_remote_schema.root">> => #{required => [<<"default_password">>, <<"default_username">>],
|
|
<<"properties">> => [{<<"listeners">>, #{items =>
|
|
#{<<"oneOf">> =>
|
|
[#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref2">>},
|
|
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>}]}, type => array}},
|
|
{<<"default_username">>,
|
|
#{default => <<"admin">>, example => <<"string example">>, type => string}},
|
|
{<<"default_password">>, #{default => <<"public">>, example => <<"string example">>, type => string}},
|
|
{<<"sample_interval">>, #{default => <<"10s">>, example => <<"1h">>, type => string}},
|
|
{<<"token_expired_time">>, #{default => <<"30m">>, example => <<"12m">>, type => string}}],
|
|
<<"type">> => object}}],
|
|
ExpectRefs = [{emqx_swagger_remote_schema, "root"}],
|
|
{_, Components} = validate(Path, Object, ExpectRefs),
|
|
?assertEqual(ExpectComponents, Components),
|
|
ok.
|
|
|
|
t_api_spec(_Config) ->
|
|
emqx_dashboard_swagger:spec(?MODULE),
|
|
ok.
|
|
|
|
api_spec() -> emqx_dashboard_swagger:spec(?MODULE).
|
|
|
|
paths() ->
|
|
["/simple/bin", "/object", "/nest/object", "/ref/local",
|
|
"/ref/nest/ref", "/raw/ref/local", "/raw/ref/remote",
|
|
"/ref/array/with/key", "/ref/array/without/key",
|
|
"/ref/hocon/schema/function"].
|
|
|
|
schema("/simple/bin") ->
|
|
to_schema(<<"binary ok">>);
|
|
schema("/object") ->
|
|
Object = [
|
|
{per_page, mk(range(1, 100), #{nullable => false, desc => <<"good per page desc">>})},
|
|
{timeout, mk(hoconsc:union([infinity, emqx_schema:duration_s()]),
|
|
#{default => 5, nullable => false})},
|
|
{inner_ref, mk(hoconsc:ref(?MODULE, good_ref), #{})}
|
|
],
|
|
to_schema(Object);
|
|
schema("/nest/object") ->
|
|
Response = [
|
|
{per_page, mk(range(1, 100), #{desc => <<"good per page desc">>})},
|
|
{timeout, mk(hoconsc:union([infinity, emqx_schema:duration_s()]),
|
|
#{default => 5, nullable => false})},
|
|
{nest_object, [
|
|
{good_nest_1, mk(integer(), #{})},
|
|
{good_nest_2, mk(hoconsc:ref(?MODULE, good_ref), #{})}
|
|
]},
|
|
{inner_ref, mk(hoconsc:ref(?MODULE, good_ref), #{})}],
|
|
to_schema(Response);
|
|
schema("/empty") ->
|
|
to_schema([]);
|
|
schema("/raw/ref/local") ->
|
|
to_schema(hoconsc:ref(good_ref));
|
|
schema("/raw/ref/remote") ->
|
|
to_schema(hoconsc:ref(emqx_swagger_remote_schema, "ref1"));
|
|
schema("/ref/local") ->
|
|
to_schema(mk(hoconsc:ref(good_ref), #{}));
|
|
schema("/ref/remote") ->
|
|
to_schema(mk(hoconsc:ref(emqx_swagger_remote_schema, "ref1"), #{}));
|
|
schema("/ref/bad") ->
|
|
to_schema(mk(hoconsc:ref(?MODULE, bad_ref), #{}));
|
|
schema("/ref/nest/ref") ->
|
|
to_schema(mk(hoconsc:ref(?MODULE, nest_ref), #{}));
|
|
schema("/ref/array/with/key") ->
|
|
to_schema([
|
|
{per_page, mk(range(1, 100), #{desc => <<"good per page desc">>})},
|
|
{timeout, mk(hoconsc:union([infinity, emqx_schema:duration_s()]),
|
|
#{default => 5, required => true})},
|
|
{assert, mk(float(), #{desc => <<"money">>})},
|
|
{number_ex, mk(number(), #{desc => <<"number example">>})},
|
|
{percent_ex, mk(emqx_schema:percent(), #{desc => <<"percent example">>})},
|
|
{duration_ms_ex, mk(emqx_schema:duration_ms(), #{desc => <<"duration ms example">>})},
|
|
{atom_ex, mk(atom(), #{desc => <<"atom ex">>})},
|
|
{array_refs, mk(hoconsc:array(hoconsc:ref(?MODULE, good_ref)), #{})}
|
|
]);
|
|
schema("/ref/array/without/key") ->
|
|
to_schema(mk(hoconsc:array(hoconsc:ref(?MODULE, good_ref)), #{}));
|
|
schema("/ref/hocon/schema/function") ->
|
|
to_schema(mk(hoconsc:ref(emqx_swagger_remote_schema, "root"), #{}));
|
|
schema("/error") ->
|
|
#{
|
|
operationId => test,
|
|
get => #{responses => #{
|
|
400 => emqx_dashboard_swagger:error_codes(['Bad1', 'Bad2'], <<"Bad request desc">>),
|
|
404 => emqx_dashboard_swagger:error_codes(['Not-Found'])
|
|
}}
|
|
}.
|
|
|
|
validate(Path, ExpectObject, ExpectRefs) ->
|
|
{OperationId, Spec, Refs} = emqx_dashboard_swagger:parse_spec_ref(?MODULE, Path),
|
|
?assertEqual(test, OperationId),
|
|
Response = maps:get(responses, maps:get(post, Spec)),
|
|
?assertEqual(ExpectObject, maps:get(<<"200">>, Response)),
|
|
?assertEqual(ExpectObject, maps:get(<<"201">>, Response)),
|
|
?assertEqual(#{}, maps:without([<<"201">>, <<"200">>], Response)),
|
|
?assertEqual(ExpectRefs, Refs),
|
|
{Spec, emqx_dashboard_swagger:components(Refs)}.
|
|
|
|
to_schema(Object) ->
|
|
#{
|
|
operationId => test,
|
|
post => #{responses => #{200 => Object, 201 => Object}}
|
|
}.
|
|
|
|
fields(good_ref) ->
|
|
[
|
|
{'webhook-host', mk(emqx_schema:ip_port(), #{default => "127.0.0.1:80"})},
|
|
{log_dir, mk(emqx_schema:file(), #{example => "var/log/emqx"})},
|
|
{tag, mk(binary(), #{desc => <<"tag">>})}
|
|
];
|
|
fields(nest_ref) ->
|
|
[
|
|
{env, mk(hoconsc:enum([test, dev, prod]), #{})},
|
|
{another_ref, mk(hoconsc:ref(good_ref), #{desc => "nest ref"})}
|
|
];
|
|
|
|
fields(bad_ref) -> %% don't support maps
|
|
#{
|
|
username => mk(string(), #{}),
|
|
is_admin => mk(boolean(), #{})
|
|
}.
|