%%-------------------------------------------------------------------- %% Copyright (c) 2021-2022 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %%-------------------------------------------------------------------- -module(emqx_gateway_cli_SUITE). -compile(export_all). -compile(nowarn_export_all). -include_lib("eunit/include/eunit.hrl"). -define(GP(S), begin S, receive {fmt, P} -> P; O -> O end end). %% this parses to #{}, will not cause config cleanup %% so we will need call emqx_config:erase -define(CONF_DEFAULT, <<" gateway {} ">>). %% The config with json format for mqtt-sn gateway -define(CONF_MQTTSN, " {\"idle_timeout\": \"30s\", \"enable_stats\": true, \"mountpoint\": \"mqttsn/\", \"gateway_id\": 1, \"broadcast\": true, \"enable_qos3\": true, \"predefined\": [{\"id\": 1001, \"topic\": \"pred/a\"}], \"listeners\": [{\"type\": \"udp\", \"name\": \"ct\", \"bind\": \"1884\" }] } "). %%-------------------------------------------------------------------- %% Setup %%-------------------------------------------------------------------- all() -> emqx_common_test_helpers:all(?MODULE). init_per_suite(Conf) -> emqx_config:erase(gateway), emqx_common_test_helpers:load_config(emqx_gateway_schema, ?CONF_DEFAULT), emqx_mgmt_api_test_util:init_suite([emqx_conf, emqx_authn, emqx_gateway]), Conf. end_per_suite(Conf) -> emqx_mgmt_api_test_util:end_suite([emqx_gateway, emqx_authn, emqx_conf]), Conf. init_per_testcase(_, Conf) -> Self = self(), ok = meck:new(emqx_ctl, [passthrough, no_history, no_link]), ok = meck:expect(emqx_ctl, usage, fun(L) -> emqx_ctl:format_usage(L) end), ok = meck:expect(emqx_ctl, print, fun(Fmt) -> Self ! {fmt, emqx_ctl:format(Fmt, [])} end), ok = meck:expect(emqx_ctl, print, fun(Fmt, Args) -> Self ! {fmt, emqx_ctl:format(Fmt, Args)} end), Conf. end_per_testcase(_, _) -> meck:unload([emqx_ctl]), ok. %%-------------------------------------------------------------------- %% Cases %%-------------------------------------------------------------------- %% TODO: t_load_unload(_) -> ok. t_gateway_registry_usage(_) -> ?assertEqual( ["gateway-registry list # List all registered gateways\n"], emqx_gateway_cli:'gateway-registry'(usage)). t_gateway_registry_list(_) -> emqx_gateway_cli:'gateway-registry'(["list"]), ?assertEqual( "Registered Name: coap, Callback Module: emqx_coap_impl\n" "Registered Name: exproto, Callback Module: emqx_exproto_impl\n" "Registered Name: lwm2m, Callback Module: emqx_lwm2m_impl\n" "Registered Name: mqttsn, Callback Module: emqx_sn_impl\n" "Registered Name: stomp, Callback Module: emqx_stomp_impl\n" , acc_print()). t_gateway_usage(_) -> ?assertEqual( ["gateway list # List all gateway\n", "gateway lookup # Lookup a gateway detailed information\n", "gateway load # Load a gateway with config\n", "gateway unload # Unload the gateway\n", "gateway stop # Stop the gateway\n", "gateway start # Start the gateway\n"], emqx_gateway_cli:gateway(usage) ). t_gateway_list(_) -> emqx_gateway_cli:gateway(["list"]), ?assertEqual( "Gateway(name=coap, status=unloaded)\n" "Gateway(name=exproto, status=unloaded)\n" "Gateway(name=lwm2m, status=unloaded)\n" "Gateway(name=mqttsn, status=unloaded)\n" "Gateway(name=stomp, status=unloaded)\n" , acc_print()), emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]), ?assertEqual("ok\n", acc_print()), emqx_gateway_cli:gateway(["list"]), %% TODO: assert it. _ = acc_print(), emqx_gateway_cli:gateway(["unload", "mqttsn"]), ?assertEqual("ok\n", acc_print()). t_gateway_load_unload_lookup(_) -> emqx_gateway_cli:gateway(["lookup", "mqttsn"]), ?assertEqual("undefined\n", acc_print()), emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]), ?assertEqual("ok\n", acc_print()), %% TODO: bad config name, format??? emqx_gateway_cli:gateway(["lookup", "mqttsn"]), %% TODO: assert it. for example: %% name: mqttsn %% status: running %% created_at: 2022-01-05T14:40:20.039+08:00 %% started_at: 2022-01-05T14:42:37.894+08:00 %% config: #{broadcast => false,enable => true,enable_qos3 => true, %% enable_stats => true,gateway_id => 1,idle_timeout => 30000, %% mountpoint => <<>>,predefined => []} _ = acc_print(), emqx_gateway_cli:gateway(["load", "mqttsn", "{}"]), ?assertEqual( "Error: The mqttsn gateway already loaded\n" , acc_print()), emqx_gateway_cli:gateway(["load", "bad-gw-name", "{}"]), %% TODO: assert it. for example: %% Error: Illegal gateway name _ = acc_print(), emqx_gateway_cli:gateway(["unload", "mqttsn"]), ?assertEqual("ok\n", acc_print()), %% Always return ok, even the gateway has unloaded emqx_gateway_cli:gateway(["unload", "mqttsn"]), ?assertEqual("ok\n", acc_print()), emqx_gateway_cli:gateway(["lookup", "mqttsn"]), ?assertEqual("undefined\n", acc_print()). t_gateway_start_stop(_) -> emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]), ?assertEqual("ok\n", acc_print()), emqx_gateway_cli:gateway(["stop", "mqttsn"]), ?assertEqual("ok\n", acc_print()), %% duplicated stop gateway, return ok emqx_gateway_cli:gateway(["stop", "mqttsn"]), ?assertEqual("ok\n", acc_print()), emqx_gateway_cli:gateway(["start", "mqttsn"]), ?assertEqual("ok\n", acc_print()), %% duplicated start gateway, return ok emqx_gateway_cli:gateway(["start", "mqttsn"]), ?assertEqual("ok\n", acc_print()), emqx_gateway_cli:gateway(["unload", "mqttsn"]), ?assertEqual("ok\n", acc_print()). t_gateway_clients_usage(_) -> ?assertEqual( ["gateway-clients list " "# List all clients for a gateway\n", "gateway-clients lookup " "# Lookup the Client Info for specified client\n", "gateway-clients kick " "# Kick out a client\n"], emqx_gateway_cli:'gateway-clients'(usage) ). t_gateway_clients(_) -> emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]), ?assertEqual("ok\n", acc_print()), Socket = sn_client_connect(<<"client1">>), _ = emqx_gateway_cli:'gateway-clients'(["list", "mqttsn"]), ClientDesc1 = acc_print(), _ = emqx_gateway_cli:'gateway-clients'(["lookup", "mqttsn", "client1"]), ClientDesc2 = acc_print(), ?assertEqual(ClientDesc1, ClientDesc2), sn_client_disconnect(Socket), timer:sleep(500), _ = emqx_gateway_cli:'gateway-clients'(["lookup", "mqttsn", "bad-client"]), ?assertEqual("Not Found.\n", acc_print()), _ = emqx_gateway_cli:'gateway-clients'(["lookup", "bad-gw", "bad-client"]), ?assertEqual("Bad Gateway Name.\n", acc_print()), _ = emqx_gateway_cli:'gateway-clients'(["list", "mqttsn"]), %% no print for empty client list _ = emqx_gateway_cli:'gateway-clients'(["list", "bad-gw"]), ?assertEqual("Bad Gateway Name.\n", acc_print()), emqx_gateway_cli:gateway(["unload", "mqttsn"]), ?assertEqual("ok\n", acc_print()). t_gateway_clients_kick(_) -> emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]), ?assertEqual("ok\n", acc_print()), Socket = sn_client_connect(<<"client1">>), _ = emqx_gateway_cli:'gateway-clients'(["list", "mqttsn"]), _ = acc_print(), _ = emqx_gateway_cli:'gateway-clients'(["kick", "mqttsn", "bad-client"]), ?assertEqual("Not Found.\n", acc_print()), _ = emqx_gateway_cli:'gateway-clients'(["kick", "mqttsn", "client1"]), ?assertEqual("ok\n", acc_print()), sn_client_disconnect(Socket), emqx_gateway_cli:gateway(["unload", "mqttsn"]), ?assertEqual("ok\n", acc_print()). t_gateway_metrcis_usage(_) -> ?assertEqual( [ "gateway-metrics " "# List all metrics for a gateway\n"], emqx_gateway_cli:'gateway-metrics'(usage) ). t_gateway_metrcis(_) -> ok. acc_print() -> lists:concat(lists:reverse(acc_print([]))). acc_print(Acc) -> receive {fmt, S} -> acc_print([S|Acc]) after 200 -> Acc end. sn_client_connect(ClientId) -> {ok, Socket} = gen_udp:open(0, [binary]), _ = emqx_sn_protocol_SUITE:send_connect_msg(Socket, ClientId), ?assertEqual(<<3, 16#05, 0>>, emqx_sn_protocol_SUITE:receive_response(Socket)), Socket. sn_client_disconnect(Socket) -> _ = emqx_sn_protocol_SUITE:send_disconnect_msg(Socket, undefined), gen_udp:close(Socket), ok.