From 2422470ea110bd18e2bdb308685f7a134144bcbb Mon Sep 17 00:00:00 2001 From: Mirjam Friesen Date: Mon, 18 Nov 2024 11:35:09 +0100 Subject: [PATCH] Remove grisp_manager from tests (#50) * Remove grisp_manager from tests * Increase wait timeouts in tests * Increase ping timeout in reconnect tests --- README.md | 4 + rebar.config | 2 +- test/grisp_connect_api_SUITE.erl | 78 ++++++++----- test/grisp_connect_log_SUITE.erl | 155 +++++++++++-------------- test/grisp_connect_manager.erl | 74 ------------ test/grisp_connect_reconnect_SUITE.erl | 62 +++------- test/grisp_connect_test.hrl | 13 +++ test/grisp_connect_test_async.erl | 34 ++++++ test/grisp_connect_test_client.erl | 6 +- test/grisp_connect_test_server.erl | 145 +++++++++++++++++++++++ 10 files changed, 334 insertions(+), 239 deletions(-) delete mode 100644 test/grisp_connect_manager.erl create mode 100644 test/grisp_connect_test.hrl create mode 100644 test/grisp_connect_test_async.erl create mode 100644 test/grisp_connect_test_server.erl diff --git a/README.md b/README.md index 461fd9c..e594b2f 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,10 @@ Run tests: rebar3 ct +To run the tests it might be necessary that you clean out the _build folder in case you compiled with another profile before: + + rm -rf _build + ### Development on GRiSP Hardware Add an entry in the grisp hosts file so the domain www.seawater.local points diff --git a/rebar.config b/rebar.config index 03c5966..44cc1fd 100644 --- a/rebar.config +++ b/rebar.config @@ -65,7 +65,7 @@ {test, [ {deps, [ {grisp_emulation, "0.2.2"}, - {grisp_manager, {git, "git@github.com:stritzinger/grisp_manager", {branch, "main"}}} + {cowboy, "2.12.0"} ]}, {overrides, [ {add, eresu, [{erl_opts, [{d, 'TEST'}]}]}, diff --git a/test/grisp_connect_api_SUITE.erl b/test/grisp_connect_api_SUITE.erl index 5b9462f..b63c1ce 100644 --- a/test/grisp_connect_api_SUITE.erl +++ b/test/grisp_connect_api_SUITE.erl @@ -3,9 +3,13 @@ -behaviour(ct_suite). -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). +-include("grisp_connect_test.hrl"). -compile([export_all, nowarn_export_all]). +-import(grisp_connect_test_async, [async_eval/1]). +-import(grisp_connect_test_async, [async_get_result/1]). + -import(grisp_connect_test_client, [wait_connection/0]). -import(grisp_connect_test_client, [wait_connection/1]). -import(grisp_connect_test_client, [wait_disconnection/0]). @@ -13,6 +17,10 @@ -import(grisp_connect_test_client, [serial_number/0]). -import(grisp_connect_test_client, [cert_dir/0]). +-import(grisp_connect_test_server, [flush/0]). +-import(grisp_connect_test_server, [send_jsonrpc_result/2]). +-import(grisp_connect_test_server, [send_jsonrpc_error/3]). + %--- API ----------------------------------------------------------------------- all() -> @@ -24,35 +32,27 @@ all() -> ]. init_per_suite(Config) -> - PrivDir = ?config(priv_dir, Config), CertDir = cert_dir(), - - PolicyFile = filename:join(PrivDir, "policies.term"), - ?assertEqual(ok, file:write_file(PolicyFile, <<>>)), - application:set_env(seabac, policy_file, PolicyFile), - - Config2 = grisp_connect_manager:start(Config), - grisp_connect_manager:kraft_start(CertDir), - [{cert_dir, CertDir} | Config2]. + Apps = grisp_connect_test_server:start(CertDir), + [{apps, Apps} | Config]. end_per_suite(Config) -> - grisp_connect_manager:cleanup_apps(?config(apps, Config)). + [?assertEqual(ok, application:stop(App)) || App <- ?config(apps, Config)]. init_per_testcase(TestCase, Config) -> {ok, _} = application:ensure_all_started(grisp_emulation), {ok, _} = application:ensure_all_started(grisp_connect), case TestCase of auto_connect_test -> ok; - _ -> ok = wait_connection() + _ -> + ?assertEqual(ok, wait_connection()), + grisp_connect_test_server:listen() end, Config. end_per_testcase(_, Config) -> ok = application:stop(grisp_connect), - mnesia:activity(transaction, fun() -> - mnesia:delete({grisp_device, serial_number()}) - end), - flush(), + ?assertEqual([], flush()), Config. %--- Tests --------------------------------------------------------------------- @@ -61,20 +61,44 @@ auto_connect_test(_) -> ?assertMatch(ok, wait_connection()). ping_test(_) -> - ?assertMatch({ok, <<"pang">>}, grisp_connect:ping()). + Pid = async_eval(fun grisp_connect:ping/0), + Id = ?receiveRequest(<<"post">>, #{type := <<"ping">>}), + send_jsonrpc_result(<<"pong">>, Id), + ?assertEqual({ok, <<"pong">>}, async_get_result(Pid)). link_device_test(_) -> ?assertMatch({error, token_undefined}, grisp_connect:link_device()), - application:set_env(grisp_connect, device_linking_token, <<"token">>), - ?assertMatch({error, invalid_token}, grisp_connect:link_device()), - Token = grisp_manager_token:get_token(<<"Testuser">>), + Token = <<"token">>, application:set_env(grisp_connect, device_linking_token, Token), - ?assertMatch({ok, <<"ok">>}, grisp_connect:link_device()), - ?assertMatch({ok, <<"pong">>}, grisp_connect:ping()). - -%--- Internal ------------------------------------------------------------------ -flush() -> - receive Any -> ct:pal("Flushed: ~p", [Any]), flush() - after 0 -> ok - end. + % handle successful responses + % ok + Pid1 = async_eval(fun grisp_connect:link_device/0), + Id1 = ?receiveRequest(<<"post">>, + #{type := <<"device_linking_token">>, + token := Token}), + send_jsonrpc_result(<<"ok">>, Id1), + ?assertEqual({ok, <<"ok">>}, async_get_result(Pid1)), + % device_already_linked + Pid2 = async_eval(fun grisp_connect:link_device/0), + Id2 = ?receiveRequest(<<"post">>, + #{type := <<"device_linking_token">>, + token := Token}), + send_jsonrpc_result(<<"device_already_linked">>, Id2), + ?assertEqual({ok, <<"device_already_linked">>}, async_get_result(Pid2)), + + % handle error responses + % device_already_linked + Pid3 = async_eval(fun grisp_connect:link_device/0), + Id3 = ?receiveRequest(<<"post">>, + #{type := <<"device_linking_token">>, + token := Token}), + send_jsonrpc_error(-3, <<"device already linked">>, Id3), + ?assertEqual({error, device_already_linked}, async_get_result(Pid3)), + % invalid_token + Pid4 = async_eval(fun grisp_connect:link_device/0), + Id4 = ?receiveRequest(<<"post">>, + #{type := <<"device_linking_token">>, + token := Token}), + send_jsonrpc_error(-4, <<"invalid token">>, Id4), + ?assertEqual({error, invalid_token}, async_get_result(Pid4)). diff --git a/test/grisp_connect_log_SUITE.erl b/test/grisp_connect_log_SUITE.erl index 7e5b594..39cb204 100644 --- a/test/grisp_connect_log_SUITE.erl +++ b/test/grisp_connect_log_SUITE.erl @@ -3,6 +3,7 @@ -behaviour(ct_suite). -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). +-include("grisp_connect_test.hrl"). -compile([export_all, nowarn_export_all]). @@ -10,6 +11,10 @@ -import(grisp_connect_test_client, [serial_number/0]). -import(grisp_connect_test_client, [wait_connection/0]). +-import(grisp_connect_test_server, [flush/0]). +-import(grisp_connect_test_server, [receive_jsonrpc_request/0]). +-import(grisp_connect_test_server, [send_jsonrpc_result/2]). + %--- API ----------------------------------------------------------------------- all() -> @@ -21,20 +26,12 @@ all() -> ]. init_per_suite(Config) -> - PrivDir = ?config(priv_dir, Config), CertDir = cert_dir(), - - PolicyFile = filename:join(PrivDir, "policies.term"), - ?assertEqual(ok, file:write_file(PolicyFile, <<>>)), - application:set_env(seabac, policy_file, PolicyFile), - - Config2 = grisp_connect_manager:start(Config), - grisp_connect_manager:kraft_start(CertDir), - grisp_connect_manager:link_device(), - [{cert_dir, CertDir} | Config2]. + Apps = grisp_connect_test_server:start(CertDir), + [{apps, Apps} | Config]. end_per_suite(Config) -> - grisp_connect_manager:cleanup_apps(?config(apps, Config)). + [?assertEqual(ok, application:stop(App)) || App <- ?config(apps, Config)]. init_per_testcase(log_level_test, Config) -> #{level := Level} = logger:get_primary_config(), @@ -43,16 +40,16 @@ init_per_testcase(log_level_test, Config) -> init_per_testcase(_, Config) -> {ok, _} = application:ensure_all_started(grisp_emulation), {ok, _} = application:ensure_all_started(grisp_connect), - ok = wait_connection(), - ct:sleep(1000), - send_logs(), - ct:sleep(1000), + ?assertEqual(ok, wait_connection()), + grisp_connect_test_server:listen(), Config. end_per_testcase(log_level_test, Config) -> logger:set_primary_config(level, ?config(default_level, Config)), end_per_testcase(other, Config); end_per_testcase(_, Config) -> + ok = application:stop(grisp_connect), + ?assertEqual([], flush()), Config. %--- Tests --------------------------------------------------------------------- @@ -62,13 +59,11 @@ string_logs_test(_) -> S1 = "@#$%^&*()_ +{}|:\"<>?-[];'./,\\`~!\néäüßóçøáîùêñÄÖÜÉÁÍÓÚàèìòùÂÊÎÔÛ€", S2 = <<"@#$%^&*()_ +{}|:\"<>?-[];'./,\\`~!\néäüßóçøáîùêñÄÖÜÉÁÍÓÚàèìòùÂÊÎÔÛ€"/utf8>>, Strings = [S1, S2], - Texts = [<>, <>], + Texts = [<>, <>], Seqs = [LastSeq + 1, LastSeq + 2], Fun = fun({Seq, String, Text}) -> grisp_connect:log(error, [String]), - send_logs(), - ct:sleep(100), - check_log(Seq, error, Text) + check_log(Seq, <<"error">>, Text) end, lists:map(Fun, lists:zip3(Seqs, Strings, Texts)). @@ -79,17 +74,15 @@ formatted_logs_test(_) -> ["~s, ~ts", ["tést", "tést"]], ["~s, ~ts", [<<"tést">>, <<"tést"/utf8>>]]], LastSeq = last_seq(), - Texts = [<<"ä, €\n"/utf8>>, - <<"tést, tést\n"/utf8>>, - <<"<<\"tést\">>, <<\"tést\"/utf8>>\n"/utf8>>, - <<"tést, tést\n"/utf8>>, - <<"tést, tést\n"/utf8>>], + Texts = [<<"ä, €"/utf8>>, + <<"tést, tést"/utf8>>, + <<"<<\"tést\">>, <<\"tést\"/utf8>>"/utf8>>, + <<"tést, tést"/utf8>>, + <<"tést, tést"/utf8>>], Seqs = lists:seq(LastSeq + 1, LastSeq + length(ArgsList)), Fun = fun({Seq, Args, Text}) -> grisp_connect:log(error, Args), - send_logs(), - ct:sleep(100), - check_log(Seq, error, Text) + check_log(Seq, <<"error">>, Text) end, lists:map(Fun, lists:zip3(Seqs, ArgsList, Texts)). @@ -104,21 +97,19 @@ structured_logs_test(_) -> #{event => 0.1}, #{event => {'äh', 'bäh'}}], LastSeq = last_seq(), - Texts = [<<" event: <<\"tést\"/utf8>>\n"/utf8>>, - <<" event: tést\n"/utf8>>, - <<" event: <<\"tést\"/utf8>>\n"/utf8>>, - <<" event: [<<\"tést1\"/utf8>>,<<\"tést2\"/utf8>>]\n"/utf8>>, - <<" event: #{tèst1 => true}\n"/utf8>>, - <<" event: #{<<\"errör\"/utf8>> => false}\n"/utf8>>, - <<" event: 1234\n"/utf8>>, - <<" event: 0.1\n"/utf8>>, - <<"[JSON incompatible term]\n#{event => {äh,bäh}}\n"/utf8>>], + Texts = [#{event => <<"tést"/utf8>>}, + #{event => "tést"}, + #{event => <<"tést"/utf8>>}, + #{event => [<<"tést1"/utf8>>,<<"tést2"/utf8>>]}, + #{event => #{'tèst1' => true}}, + #{event => #{<<"errör"/utf8>> => false}}, + #{event => 1234}, + #{event => 0.1}, + <<"[JSON incompatible term]\n#{event => {äh,bäh}}"/utf8>>], Seqs = lists:seq(LastSeq + 1, LastSeq + length(Events)), Fun = fun({Seq, Event, Text}) -> grisp_connect:log(error, [Event]), - send_logs(), - ct:sleep(100), - check_log(Seq, error, Text) + check_log(Seq, <<"error">>, Text) end, lists:map(Fun, lists:zip3(Seqs, Events, Texts)). @@ -135,9 +126,7 @@ log_level_test(_) -> Seqs = lists:seq(LastSeq + 1, LastSeq + length(Levels)), Fun = fun({Seq, Level}) -> grisp_connect:log(Level, ["level test"]), - send_logs(), - ct:sleep(100), - check_log(Seq, Level, <<"level test\n"/utf8>>) + check_log(Seq, atom_to_binary(Level), <<"level test"/utf8>>) end, lists:map(Fun, lists:zip(Seqs, Levels)), @@ -147,8 +136,7 @@ log_level_test(_) -> grisp_connect:log(info, ["level test"]), send_logs(), ct:sleep(100), - Logs = grisp_manager_device_logs:fetch(serial_number(), last_seq()), - ?assertEqual([], Logs). + ?assertEqual([], flush()). meta_data_test(_) -> Meta = #{custom1 => <<"binäry"/utf8>>, @@ -158,33 +146,26 @@ meta_data_test(_) -> custom5 => #{boolean => true}, custom6 => 6, custom7 => 7.0}, - LoggerConfig = #{legacy_header => true, - single_line => false, - template => [[logger_formatter, header], "\n", - custom1, "\n", - custom2, "\n", - custom3, "\n", - custom4, "\n", - custom5, "\n", - custom6, "\n", - custom7, "\n", - msg, "\n"]}, - application:set_env(grisp_manager, device_log_config, LoggerConfig), - Text = iolist_to_binary( - [<<"<<\"binäry\"/utf8>>\n"/utf8>>, - <<"\[<<\"é1\"/utf8>>,<<\"é2\"/utf8>>\]\n"/utf8>>, - <<"<<\"åtom\"/utf8>>\n"/utf8>>, - <<"#{kèy => <<\"välüe\"/utf8>>}\n"/utf8>>, - <<"#{boolean => true}\n"/utf8>>, - <<"6\n"/utf8>>, - <<"7.0\n"/utf8>>, - <<"Test meta\n"/utf8>>]), LastSeq = last_seq(), Seq = LastSeq + 1, grisp_connect:log(error, ["Test meta", Meta]), send_logs(), - ct:sleep(100), - check_log(Seq, error, Text). + ct:pal("Expected seq:~n~p~n", [Seq]), + Id = ?receiveRequest( + <<"post">>, + #{type := <<"logs">>, + events := + [[Seq, #{msg := <<"Test meta"/utf8>>, + meta := + #{custom1 := <<"binäry"/utf8>>, + custom2 := [<<"é1"/utf8>>,<<"é2"/utf8>>], + custom3 := <<"åtom"/utf8>>, + custom4 := #{'kèy' := <<"välüe"/utf8>>}, + custom5 := #{boolean := true}, + custom6 := 6, + custom7 := 7.0}, + level := <<"error">>}]]}), + send_jsonrpc_result(#{seq => Seq, dropped => 0}, Id). %--- Internal ------------------------------------------------------------------ @@ -193,28 +174,24 @@ send_logs() -> LogServer ! send_logs. last_seq() -> - {Seq, _} = case grisp_manager_device_logs:fetch(serial_number(), -1) of - [] -> {0, []}; - List -> lists:last(List) - end, + send_logs(), + Send = receive_jsonrpc_request(), + ?assertMatch(#{method := <<"post">>, + params := #{type := <<"logs">>, + events := _, + dropped := _}}, + Send), + Id = maps:get(id, Send), + Events = maps:get(events, maps:get(params, Send)), + [Seq, _] = lists:last(Events), + send_jsonrpc_result(#{seq => Seq, dropped => 0}, Id), Seq. check_log(Seq, Level, Text) -> - Logs = grisp_manager_device_logs:fetch(serial_number(), Seq - 1), - ?assertMatch([{Seq, _}], Logs, ["Text: ", Text]), - [{_, Log}] = Logs, - Split = binary:split(Log, <<"\n">>, [trim_all]), - ?assertMatch([_, Text], Split, ["Text: ", Text]), - Header = log_header(Level), - ?assertMatch([Header, _], - binary:split(Log, <<"= ">>), - ["Header: ", Header]). - -log_header(emergency) -> <<"=EMERGENCY REPORT===">>; -log_header(alert) -> <<"=ALERT REPORT===">>; -log_header(critical) -> <<"=CRITICAL REPORT===">>; -log_header(error) -> <<"=ERROR REPORT===">>; -log_header(warning) -> <<"=WARNING REPORT===">>; -log_header(notice) -> <<"=NOTICE REPORT===">>; -log_header(info) -> <<"=INFO REPORT===">>; -log_header(debug) -> <<"=DEBUG REPORT===">>. + send_logs(), + ct:pal("Expected seq:~n~p~nExpected level:~n~p~nExpected text:~n~p", + [Seq, Level, Text]), + Id = ?receiveRequest(<<"post">>, + #{type := <<"logs">>, + events := [[Seq, #{msg := Text, level := Level}]]}), + send_jsonrpc_result(#{seq => Seq, dropped => 0}, Id). diff --git a/test/grisp_connect_manager.erl b/test/grisp_connect_manager.erl deleted file mode 100644 index debd1f7..0000000 --- a/test/grisp_connect_manager.erl +++ /dev/null @@ -1,74 +0,0 @@ --module(grisp_connect_manager). - --compile([export_all, nowarn_export_all]). - - --include_lib("common_test/include/ct.hrl"). - -start(Config) -> - PrivDir = ?config(priv_dir, Config), - application:set_env(mnesia, dir, PrivDir), - - eresu:install([node()]), - {ok, Started1} = application:ensure_all_started(eresu), - register_user(), - - grisp_manager:install([node()]), - - application:start(mnesia), - - {ok, Started2} = application:ensure_all_started(kraft), - - {ok, Started3} = application:ensure_all_started(grisp_manager), - Apps = Started1 ++ Started2 ++ Started3, - [{apps, Apps} | Config]. - -kraft_start(CertDir) -> - kraft_start(CertDir, #{}). - -kraft_start(CertDir, OverrideOpts) -> - SslOpts = [ - {verify, verify_peer}, - {keyfile, filename:join(CertDir, "server.key")}, - {certfile, filename:join(CertDir, "server.crt")}, - {cacertfile, filename:join(CertDir, "CA.crt")} - ], - Opts = #{ - port => 3030, - ssl_opts => SslOpts, - app => grisp_manager - }, - KraftOpts = mapz:deep_merge(Opts, OverrideOpts), - KraftRoutes = [ - {"/grisp-connect/ws", - {ws, grisp_manager_device_api}, #{}, #{type => json_rpc}} - ], - kraft:start(KraftOpts, KraftRoutes). - -cleanup_apps(Apps) -> - mnesia:delete_table(eresu_user), - mnesia:delete_table(grisp_device), - mnesia:delete_table(grisp_manager_token), - [application:stop(App) || App <- Apps], - application:stop(mnesia). - -link_device() -> - grisp_manager:link_device(<<"0000">>, <<"Uuid">>). - -register_user() -> - Hash = erlpass:hash(<<"1234">>), - WriteUser = fun() -> - mnesia:write({eresu_user, - <<"Uuid">>, - <<"Testuser">>, - <<"foo">>, - <<"a@a.a">>, - erlang:system_time(), - Hash, - <<"Max Mustermann">>, - undefined, - undefined, - <<"customer_id">>, - []}) - end, - mnesia:activity(transaction, WriteUser). diff --git a/test/grisp_connect_reconnect_SUITE.erl b/test/grisp_connect_reconnect_SUITE.erl index 6224dfc..d339e0f 100644 --- a/test/grisp_connect_reconnect_SUITE.erl +++ b/test/grisp_connect_reconnect_SUITE.erl @@ -13,6 +13,11 @@ -import(grisp_connect_test_client, [serial_number/0]). -import(grisp_connect_test_client, [cert_dir/0]). +-import(grisp_connect_test_server, [start_cowboy/1]). +-import(grisp_connect_test_server, [stop_cowboy/0]). +-import(grisp_connect_test_server, [close_websocket/0]). +-import(grisp_connect_test_server, [flush/0]). + %--- API ----------------------------------------------------------------------- all() -> @@ -24,36 +29,22 @@ all() -> ]. init_per_suite(Config) -> - PrivDir = ?config(priv_dir, Config), - CertDir = cert_dir(), - - PolicyFile = filename:join(PrivDir, "policies.term"), - ?assertEqual(ok, file:write_file(PolicyFile, <<>>)), - application:set_env(seabac, policy_file, PolicyFile), - - Config2 = grisp_connect_manager:start(Config), - [{cert_dir, CertDir} | Config2]. + {ok, Apps} = application:ensure_all_started(cowboy), + [{apps, Apps} | Config]. end_per_suite(Config) -> - grisp_connect_manager:cleanup_apps(?config(apps, Config)). + [?assertEqual(ok, application:stop(App)) || App <- ?config(apps, Config)]. init_per_testcase(_, Config) -> - % the kraf instance links to this process - process_flag(trap_exit, true), - {ok, _} = application:ensure_all_started(kraft), - KraftRef = grisp_connect_manager:kraft_start(?config(cert_dir, Config)), + start_cowboy(cert_dir()), {ok, _} = application:ensure_all_started(grisp_emulation), - application:set_env(grisp_connect, ws_ping_timeout, 60_000), + application:set_env(grisp_connect, ws_ping_timeout, 120_000), {ok, _} = application:ensure_all_started(grisp_connect), - [{kraft_instance, KraftRef} | Config]. + Config. end_per_testcase(_, Config) -> ok = application:stop(grisp_connect), - kraft:stop(?config(kraft_instance, Config)), - ok = application:stop(kraft), - mnesia:activity(transaction, fun() -> - mnesia:delete({grisp_device, serial_number()}) - end), + stop_cowboy(), flush(), Config. @@ -68,11 +59,11 @@ reconnect_on_gun_crash_test(_) -> reconnect_on_disconnection_test(Config) -> ?assertMatch(ok, wait_connection()), - ok = kraft:stop(?config(kraft_instance, Config)), + stop_cowboy(), ?assertMatch(ok, wait_disconnection()), - KraftRef2 = grisp_connect_manager:kraft_start(cert_dir()), + start_cowboy(cert_dir()), ?assertMatch(ok, wait_connection(100)), - [{kraft_instance, KraftRef2} | proplists:delete(kraft_instance, Config)]. + Config. reconnect_on_ping_timeout_test(_) -> ?assertMatch(ok, wait_connection()), @@ -88,25 +79,6 @@ reconnect_on_ping_timeout_test(_) -> reconnect_on_closed_frame_test(_) -> ?assertMatch(ok, wait_connection()), - % Delete a table to cause an internal crash, this is handled by cowboy - % and generates a close frame, - % triggering a normal exit from gun on the client - mnesia:delete_table(grisp_manager_token), - TestProc = self(), - spawn(fun() -> - Result = grisp_connect:link_device(<<"">>), - TestProc ! Result - end), + close_websocket(), ?assertMatch(ok, wait_disconnection()), - ?assertMatch(ok, wait_connection(100)), - receive - {error, timeout} -> ok - after 5000 -> error(timeout_not_reached) - end. - -%--- Internal ------------------------------------------------------------------ - -flush() -> - receive Any -> ct:pal("Flushed: ~p", [Any]), flush() - after 0 -> ok - end. + ?assertMatch(ok, wait_connection(100)). diff --git a/test/grisp_connect_test.hrl b/test/grisp_connect_test.hrl new file mode 100644 index 0000000..0e542f7 --- /dev/null +++ b/test/grisp_connect_test.hrl @@ -0,0 +1,13 @@ +%--- JSON-RPC asserts ---------------------------------------------------------- + +% Receive a JSON-RPC request, pattern match method and params, return the id +-define(receiveRequest(Method, Params), + (fun() -> + Send = grisp_connect_test_server:receive_jsonrpc_request(), + ?assertMatch(#{method := Method, params := Params}, Send), + maps:get(id, Send) + end)()). +% TODO when needed: +%-define(receiveNotification(Method, Params) +%-define(receiveResult(Pattern, Id) +%-define(receiveError(Code, Message, Id) diff --git a/test/grisp_connect_test_async.erl b/test/grisp_connect_test_async.erl new file mode 100644 index 0000000..82fb1b5 --- /dev/null +++ b/test/grisp_connect_test_async.erl @@ -0,0 +1,34 @@ +%% @doc Helper function for asynchronous function calls +%% @end + +-module(grisp_connect_test_async). + +-export([async_eval/1]). +-export([async_get_result/1]). + +%--- API ----------------------------------------------------------------------- + +async_eval(Fun) -> + spawn_link( + fun() -> + Res = Fun(), + receive + {'$async_get_result', Pid} -> Pid ! {'$async_result', Res} + end + end). + +async_get_result(Pid) -> + MRef = monitor(process, Pid), + unlink(Pid), + Pid ! {'$async_get_result', self()}, + receive + {'$async_result', Res} -> + receive {'DOWN', MRef, process, Pid, normal} -> ok + after 10 -> error({timeout, waiting_exit}) + end, + Res; + {'DOWN', MRef, process, Pid, Reason} -> + error({process_down, Reason}) + after 1000 -> + error({timeout, waiting_result}) + end. diff --git a/test/grisp_connect_test_client.erl b/test/grisp_connect_test_client.erl index b76fefe..63b131f 100644 --- a/test/grisp_connect_test_client.erl +++ b/test/grisp_connect_test_client.erl @@ -1,4 +1,4 @@ -%% @doc Test helper functions for the client setup to connect to grisp_manager +%% @doc Test helper functions for the client setup to connect to the server %% @end -module(grisp_connect_test_client). @@ -17,7 +17,7 @@ cert_dir() -> filename:join(code:lib_dir(grisp_connect, test), "certs"). serial_number() -> <<"0000">>. wait_connection() -> - wait_connection(2000). + wait_connection(30_000). wait_connection(0) -> ct:pal("grisp_connect_ws state:~n~p~n", [sys:get_state(grisp_connect_ws)]), @@ -31,7 +31,7 @@ wait_connection(N) -> end. wait_disconnection() -> - wait_disconnection(2000). + wait_disconnection(30_000). wait_disconnection(0) -> ct:pal("grisp_connect_ws state:~n~p~n", [sys:get_state(grisp_connect_ws)]), diff --git a/test/grisp_connect_test_server.erl b/test/grisp_connect_test_server.erl new file mode 100644 index 0000000..c41aec7 --- /dev/null +++ b/test/grisp_connect_test_server.erl @@ -0,0 +1,145 @@ +-module(grisp_connect_test_server). + +% API +-export([start/1]). +-export([start_cowboy/1]). +-export([stop_cowboy/0]). +-export([close_websocket/0]). +-export([listen/0]). +-export([flush/0]). +-export([receive_text/0]). +-export([receive_jsonrpc/0]). +-export([receive_jsonrpc_request/0]). +-export([send_text/1]). +-export([send_jsonrpc_result/2]). +-export([send_jsonrpc_error/3]). + +% Websocket Callbacks +-export([init/2]). +-export([websocket_init/1]). +-export([websocket_handle/2]). +-export([websocket_info/2]). + +-behaviour(cowboy_websocket). + +%--- API ----------------------------------------------------------------------- + +% Start the cowboy application and server, +% call this in `init_per_suite'. +% Returns the started apps +start(CertDir) -> + {ok, Apps} = application:ensure_all_started(cowboy), + % TODO: Disable ssl for testing + start_cowboy(CertDir), + Apps. + +% Start the cowboy listener. +% You have to make sure cowboy is running before. +start_cowboy(CertDir) -> + SslOpts = [ + {verify, verify_peer}, + {keyfile, filename:join(CertDir, "server.key")}, + {certfile, filename:join(CertDir, "server.crt")}, + {cacertfile, filename:join(CertDir, "CA.crt")} + ], + Dispatch = cowboy_router:compile( + [{'_', + [{"/grisp-connect/ws", grisp_connect_test_server, []}]}]), + {ok, _} = cowboy:start_tls(server_listener, + [{port, 3030} | SslOpts], + #{env => #{dispatch => Dispatch}} + ). + +% Stop the cowboy listener. +stop_cowboy() -> + cowboy:stop_listener(server_listener). + +% Close the websocket. +close_websocket() -> + ?MODULE ! ?FUNCTION_NAME. + +% Listen to websocket messages. +% Call this in `init_per_testcase' after connecting to the websocket +listen() -> + ?MODULE ! {?FUNCTION_NAME, self()}, + receive ok -> ok + after 5000 -> {error, timeout} + end. + +% Flush all messages. +% Call this in `end_per_testcase` to see what messages where missed. +% This is especially useful when test cases fail. +flush() -> flush([]). + +flush(Acc) -> + receive Any -> ct:pal("Flushed: ~p", [Any]), flush([Any | Acc]) + after 0 -> lists:reverse(Acc) + end. + +receive_text() -> + receive {received_text, Msg} -> Msg + after 5000 -> {error, timeout} + end. + +receive_jsonrpc() -> + case receive_text() of + {error, _} = Error -> Error; + Msg -> check_jsonrpc(Msg) + end. + +receive_jsonrpc_request() -> + case receive_jsonrpc() of + {error, _} = Error -> Error; + {error, _, _} = Error -> Error; + #{id := _} = Decoded -> Decoded; + Decoded -> {error, invalid_jsonrpc_request, Decoded} + end. + +check_jsonrpc(Msg) -> + case jsx:decode(Msg, [{labels, attempt_atom}, return_maps]) of + #{jsonrpc := <<"2.0">>} = Decoded -> Decoded; + _ -> {error, invalid_jsonrpc, Msg} + end. + +send_text(Msg) -> + ?MODULE ! {?FUNCTION_NAME, Msg}. + +send_jsonrpc_result(Result, Id) -> + Map = #{jsonrpc => <<"2.0">>, + result => Result, + id => Id}, + send_text(jsx:encode(Map)). + +send_jsonrpc_error(Code, Msg, Id) -> + Map = #{jsonrpc => <<"2.0">>, + error => #{code => Code, message => Msg}, + id => Id}, + send_text(jsx:encode(Map)). + +%--- Websocket Callbacks ------------------------------------------------------- + +init(Req, State) -> + {cowboy_websocket, Req, State}. + +websocket_init(_) -> + register(?MODULE, self()), + {[], []}. + +websocket_handle({text, Msg}, State) -> + ct:pal("Received websocket message:~n~s", [Msg]), + [Pid ! {received_text, Msg} || Pid <- State], + {[], State}; +websocket_handle(Frame, State) -> + ct:pal("Ignore websocket frame:~n~p", [Frame]), + {[], State}. + +websocket_info({listen, Pid}, State) -> + Pid ! ok, + {[], [Pid | State]}; +websocket_info({send_text, Msg}, State) -> + {[{text, Msg}], State}; +websocket_info(close_websocket, State) -> + {[close], State}; +websocket_info(Info, State) -> + ct:pal("Ignore websocket info:~n~p", [Info]), + {[], State}.