فهرست منبع

test(proper): test message hooks

JianBo He 5 سال پیش
والد
کامیت
9f088bcb7f
2فایلهای تغییر یافته به همراه245 افزوده شده و 213 حذف شده
  1. 48 6
      apps/emqx_exhook/test/emqx_exhook_demo_svr.erl
  2. 197 207
      apps/emqx_exhook/test/props/prop_exhook_hooks.erl

+ 48 - 6
apps/emqx_exhook/test/emqx_exhook_demo_svr.erl

@@ -179,18 +179,44 @@ on_client_disconnected(Req, Md) ->
 -spec on_client_authenticate(emqx_exhook_pb:client_authenticate_request(), grpc:metadata())
     -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
      | {error, grpc_cowboy_h:error_response()}.
-on_client_authenticate(Req, Md) ->
+on_client_authenticate(#{clientinfo := #{username := Username}} = Req, Md) ->
     ?MODULE:in({?FUNCTION_NAME, Req}),
     %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
-    {ok, #{type => 'IGNORE'}, Md}.
+    %% some cases for testing
+    case Username of
+        <<"baduser">> ->
+            {ok, #{type => 'STOP_AND_RETURN',
+                   value => {bool_result, false}}, Md};
+        <<"gooduser">> ->
+            {ok, #{type => 'STOP_AND_RETURN',
+                   value => {bool_result, true}}, Md};
+        <<"normaluser">> ->
+            {ok, #{type => 'CONTINUE',
+                   value => {bool_result, true}}, Md};
+        _ ->
+            {ok, #{type => 'IGNORE'}, Md}
+    end.
 
 -spec on_client_check_acl(emqx_exhook_pb:client_check_acl_request(), grpc:metadata())
     -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
      | {error, grpc_cowboy_h:error_response()}.
-on_client_check_acl(Req, Md) ->
+on_client_check_acl(#{clientinfo := #{username := Username}} = Req, Md) ->
     ?MODULE:in({?FUNCTION_NAME, Req}),
     %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
-    {ok, #{type => 'STOP_AND_RETURN', value => {bool_result, true}}, Md}.
+    %% some cases for testing
+    case Username of
+        <<"baduser">> ->
+            {ok, #{type => 'STOP_AND_RETURN',
+                   value => {bool_result, false}}, Md};
+        <<"gooduser">> ->
+            {ok, #{type => 'STOP_AND_RETURN',
+                   value => {bool_result, true}}, Md};
+        <<"normaluser">> ->
+            {ok, #{type => 'CONTINUE',
+                   value => {bool_result, true}}, Md};
+        _ ->
+            {ok, #{type => 'IGNORE'}, Md}
+    end.
 
 -spec on_client_subscribe(emqx_exhook_pb:client_subscribe_request(), grpc:metadata())
     -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}
@@ -267,10 +293,26 @@ on_session_terminated(Req, Md) ->
 -spec on_message_publish(emqx_exhook_pb:message_publish_request(), grpc:metadata())
     -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()}
      | {error, grpc_cowboy_h:error_response()}.
-on_message_publish(Req, Md) ->
+on_message_publish(#{message := #{from := From} = Msg} = Req, Md) ->
     ?MODULE:in({?FUNCTION_NAME, Req}),
     %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]),
-    {ok, #{}, Md}.
+    %% some cases for testing
+    case From of
+        <<"baduser">> ->
+            NMsg = Msg#{qos => 0,
+                        topic => <<"">>,
+                        payload => <<"">>
+                       },
+            {ok, #{type => 'STOP_AND_RETURN',
+                   value => {message, NMsg}}, Md};
+        <<"gooduser">> ->
+            NMsg = Msg#{topic => From,
+                        payload => From},
+            {ok, #{type => 'STOP_AND_RETURN',
+                   value => {message, NMsg}}, Md};
+        _ ->
+            {ok, #{type => 'IGNORE'}, Md}
+    end.
 
 -spec on_message_delivered(emqx_exhook_pb:message_delivered_request(), grpc:metadata())
     -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()}

+ 197 - 207
apps/emqx_exhook/test/props/prop_exhook_hooks.erl

@@ -44,20 +44,11 @@ prop_client_connect() ->
     ?ALL({ConnInfo, ConnProps},
          {conninfo(), conn_properties()},
        begin
-           _OutConnProps = emqx_hooks:run_fold('client.connect', [ConnInfo], ConnProps),
+           ok = emqx_hooks:run('client.connect', [ConnInfo, ConnProps]),
            {'on_client_connect', Resp} = emqx_exhook_demo_svr:take(),
            Expected =
                #{props => properties(ConnProps),
-                 conninfo =>
-                   #{node => nodestr(),
-                     clientid => maps:get(clientid, ConnInfo),
-                     username => maybe(maps:get(username, ConnInfo, <<>>)),
-                     peerhost => peerhost(ConnInfo),
-                     sockport => sockport(ConnInfo),
-                     proto_name => maps:get(proto_name, ConnInfo),
-                     proto_ver => stringfy(maps:get(proto_ver, ConnInfo)),
-                     keepalive => maps:get(keepalive, ConnInfo)
-                    }
+                 conninfo => from_conninfo(ConnInfo)
                 },
            ?assertEqual(Expected, Resp),
            true
@@ -67,78 +58,86 @@ prop_client_connack() ->
     ?ALL({ConnInfo, Rc, AckProps},
          {conninfo(), connack_return_code(), ack_properties()},
         begin
-            _OutAckProps = emqx_hooks:run_fold('client.connack', [ConnInfo, Rc], AckProps),
+            ok = emqx_hooks:run('client.connack', [ConnInfo, Rc, AckProps]),
             {'on_client_connack', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
                 #{props => properties(AckProps),
                   result_code => atom_to_binary(Rc, utf8),
-                  conninfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ConnInfo),
-                      username => maybe(maps:get(username, ConnInfo, <<>>)),
-                      peerhost => peerhost(ConnInfo),
-                      sockport => sockport(ConnInfo),
-                      proto_name => maps:get(proto_name, ConnInfo),
-                      proto_ver => stringfy(maps:get(proto_ver, ConnInfo)),
-                      keepalive => maps:get(keepalive, ConnInfo)
-                     }
+                  conninfo => from_conninfo(ConnInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
         end).
 
 prop_client_authenticate() ->
-    ?ALL({ClientInfo, AuthResult}, {clientinfo(), authresult()},
+    ?ALL({ClientInfo0, AuthResult},
+         {clientinfo(), authresult()},
         begin
-            _OutAuthResult = emqx_hooks:run_fold('client.authenticate', [ClientInfo], AuthResult),
+            ClientInfo = inject_magic_into(username, ClientInfo0),
+            OutAuthResult = emqx_hooks:run_fold('client.authenticate', [ClientInfo], AuthResult),
+            ExpectedAuthResult = case maps:get(username, ClientInfo) of
+                                     <<"baduser">> ->
+                                         AuthResult#{
+                                           auth_result => not_authorized,
+                                           anonymous => false};
+                                     <<"gooduser">> ->
+                                         AuthResult#{
+                                           auth_result => success,
+                                           anonymous => false};
+                                     <<"normaluser">> ->
+                                         AuthResult#{
+                                           auth_result => success,
+                                           anonymous => false};
+                                     _ ->
+                                         case maps:get(auth_result, AuthResult) of
+                                             success ->
+                                                 #{auth_result => success,
+                                                   anonymous => false};
+                                             _ ->
+                                                 #{auth_result => not_authorized,
+                                                   anonymous => false}
+                                         end
+                                 end,
+            ?assertEqual(ExpectedAuthResult, OutAuthResult),
+
             {'on_client_authenticate', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
                 #{result => authresult_to_bool(AuthResult),
-                  clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                  clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
         end).
 
 prop_client_check_acl() ->
-    ?ALL({ClientInfo, PubSub, Topic, Result},
-         {clientinfo(), oneof([publish, subscribe]), topic(), oneof([allow, deny])},
+    ?ALL({ClientInfo0, PubSub, Topic, Result},
+         {clientinfo(), oneof([publish, subscribe]),
+          topic(), oneof([allow, deny])},
         begin
-            _OutResult = emqx_hooks:run_fold('client.check_acl', [ClientInfo, PubSub, Topic], Result),
+            ClientInfo = inject_magic_into(username, ClientInfo0),
+            OutResult = emqx_hooks:run_fold(
+                          'client.check_acl',
+                          [ClientInfo, PubSub, Topic],
+                          Result),
+            ExpectedOutResult = case maps:get(username, ClientInfo) of
+                                    <<"baduser">> -> deny;
+                                    <<"gooduser">> -> allow;
+                                    <<"normaluser">> -> allow;
+                                    _ -> Result
+                                 end,
+            ?assertEqual(ExpectedOutResult, OutResult),
+
             {'on_client_check_acl', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
                 #{result => aclresult_to_bool(Result),
                   type => pubsub_to_enum(PubSub),
                   topic => Topic,
-                  clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                  clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
         end).
 
-
 prop_client_connected() ->
     ?ALL({ClientInfo, ConnInfo},
          {clientinfo(), conninfo()},
@@ -146,18 +145,7 @@ prop_client_connected() ->
             ok = emqx_hooks:run('client.connected', [ClientInfo, ConnInfo]),
             {'on_client_connected', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
-                #{clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                #{clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
@@ -171,18 +159,7 @@ prop_client_disconnected() ->
             {'on_client_disconnected', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
                 #{reason => stringfy(Reason),
-                  clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                  clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
@@ -192,23 +169,12 @@ prop_client_subscribe() ->
     ?ALL({ClientInfo, SubProps, TopicTab},
          {clientinfo(), sub_properties(), topictab()},
         begin
-            _OutTopicTab = emqx_hooks:run_fold('client.subscribe', [ClientInfo, SubProps], TopicTab),
+            ok = emqx_hooks:run('client.subscribe', [ClientInfo, SubProps, TopicTab]),
             {'on_client_subscribe', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
                 #{props => properties(SubProps),
                   topic_filters => topicfilters(TopicTab),
-                  clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                  clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
@@ -218,23 +184,12 @@ prop_client_unsubscribe() ->
     ?ALL({ClientInfo, UnSubProps, TopicTab},
          {clientinfo(), unsub_properties(), topictab()},
         begin
-            _OutTopicTab = emqx_hooks:run_fold('client.unsubscribe', [ClientInfo, UnSubProps], TopicTab),
+            ok = emqx_hooks:run('client.unsubscribe', [ClientInfo, UnSubProps, TopicTab]),
             {'on_client_unsubscribe', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
                 #{props => properties(UnSubProps),
                   topic_filters => topicfilters(TopicTab),
-                  clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                  clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
@@ -246,18 +201,7 @@ prop_session_created() ->
             ok = emqx_hooks:run('session.created', [ClientInfo, SessInfo]),
             {'on_session_created', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
-                #{clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                #{clientinfo => from_clientinfo(ClientInfo)
                  },
              ?assertEqual(Expected, Resp),
             true
@@ -272,18 +216,7 @@ prop_session_subscribed() ->
             Expected =
                 #{topic => Topic,
                   subopts => subopts(SubOpts),
-                  clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                  clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
@@ -297,18 +230,7 @@ prop_session_unsubscribed() ->
             {'on_session_unsubscribed', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
                 #{topic => Topic,
-                  clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                  clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
@@ -320,18 +242,7 @@ prop_session_resumed() ->
             ok = emqx_hooks:run('session.resumed', [ClientInfo, SessInfo]),
             {'on_session_resumed', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
-                #{clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                #{clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
@@ -343,18 +254,7 @@ prop_session_discared() ->
             ok = emqx_hooks:run('session.discarded', [ClientInfo, SessInfo]),
             {'on_session_discarded', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
-                #{clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                #{clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
@@ -366,18 +266,7 @@ prop_session_takeovered() ->
             ok = emqx_hooks:run('session.takeovered', [ClientInfo, SessInfo]),
             {'on_session_takeovered', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
-                #{clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                #{clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
             true
@@ -391,21 +280,98 @@ prop_session_terminated() ->
             {'on_session_terminated', Resp} = emqx_exhook_demo_svr:take(),
             Expected =
                 #{reason => stringfy(Reason),
-                  clientinfo =>
-                    #{node => nodestr(),
-                      clientid => maps:get(clientid, ClientInfo),
-                      username => maybe(maps:get(username, ClientInfo, <<>>)),
-                      password => maybe(maps:get(password, ClientInfo, <<>>)),
-                      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
-                      sockport => maps:get(sockport, ClientInfo),
-                      protocol => stringfy(maps:get(protocol, ClientInfo)),
-                      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
-                      is_superuser => maps:get(is_superuser, ClientInfo, false),
-                      anonymous => maps:get(anonymous, ClientInfo, true)
-                     }
+                  clientinfo => from_clientinfo(ClientInfo)
                  },
             ?assertEqual(Expected, Resp),
+            true
+        end).
+
+prop_message_publish() ->
+    ?ALL(Msg0, message(),
+        begin
+            Msg = emqx_message:from_map(
+                    inject_magic_into(from, emqx_message:to_map(Msg0))),
+            OutMsg= emqx_hooks:run_fold('message.publish', [], Msg),
+            case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
+                true ->
+                    ?assertEqual(Msg, OutMsg),
+                    skip;
+                _ ->
+                    ExpectedOutMsg = case emqx_message:from(Msg) of
+                                         <<"baduser">> ->
+                                             MsgMap = emqx_message:to_map(Msg),
+                                             emqx_message:from_map(
+                                               MsgMap#{qos => 0,
+                                                       topic => <<"">>,
+                                                       payload => <<"">>
+                                                      });
+                                         <<"gooduser">> = From ->
+                                             MsgMap = emqx_message:to_map(Msg),
+                                             emqx_message:from_map(
+                                               MsgMap#{topic => From,
+                                                       payload => From
+                                                      });
+                                         _ -> Msg
+                                     end,
+                    ?assertEqual(ExpectedOutMsg, OutMsg),
+
+                    {'on_message_publish', Resp} = emqx_exhook_demo_svr:take(),
+                    Expected =
+                        #{message => from_message(Msg)
+                         },
+                    ?assertEqual(Expected, Resp)
+            end,
+            true
+        end).
+
+prop_message_dropped() ->
+    ?ALL({Msg, By, Reason}, {message(), hardcoded, shutdown_reason()},
+        begin
+            ok = emqx_hooks:run('message.dropped', [Msg, By, Reason]),
+            case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
+                true -> skip;
+                _ ->
+                    {'on_message_dropped', Resp} = emqx_exhook_demo_svr:take(),
+                    Expected =
+                        #{reason => stringfy(Reason),
+                          message => from_message(Msg)
+                         },
+                    ?assertEqual(Expected, Resp)
+            end,
+            true
+       end).
 
+prop_message_delivered() ->
+    ?ALL({ClientInfo, Msg}, {clientinfo(), message()},
+        begin
+            ok = emqx_hooks:run('message.delivered', [ClientInfo, Msg]),
+            case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
+                true -> skip;
+                _ ->
+                    {'on_message_delivered', Resp} = emqx_exhook_demo_svr:take(),
+                    Expected =
+                        #{clientinfo => from_clientinfo(ClientInfo),
+                          message => from_message(Msg)
+                         },
+                    ?assertEqual(Expected, Resp)
+            end,
+            true
+       end).
+
+prop_message_acked() ->
+    ?ALL({ClientInfo, Msg}, {clientinfo(), message()},
+        begin
+            ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
+            case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of
+                true -> skip;
+                _ ->
+                    {'on_message_acked', Resp} = emqx_exhook_demo_svr:take(),
+                    Expected =
+                        #{clientinfo => from_clientinfo(ClientInfo),
+                          message => from_message(Msg)
+                         },
+                    ?assertEqual(Expected, Resp)
+            end,
             true
         end).
 
@@ -465,23 +431,39 @@ aclresult_to_bool(Result) ->
 pubsub_to_enum(publish) -> 'PUBLISH';
 pubsub_to_enum(subscribe) -> 'SUBSCRIBE'.
 
-%prop_message_publish() ->
-%    ?ALL({Msg, Env, Encode}, {message(), topic_filter_env()},
-%        begin
-%            true
-%        end).
-%
-%prop_message_delivered() ->
-%    ?ALL({ClientInfo, Msg, Env, Encode}, {clientinfo(), message(), topic_filter_env()},
-%        begin
-%            true
-%        end).
-%
-%prop_message_acked() ->
-%    ?ALL({ClientInfo, Msg, Env, Encode}, {clientinfo(), message()},
-%        begin
-%            true
-%        end).
+from_conninfo(ConnInfo) ->
+    #{node => nodestr(),
+      clientid => maps:get(clientid, ConnInfo),
+      username => maybe(maps:get(username, ConnInfo, <<>>)),
+      peerhost => peerhost(ConnInfo),
+      sockport => sockport(ConnInfo),
+      proto_name => maps:get(proto_name, ConnInfo),
+      proto_ver => stringfy(maps:get(proto_ver, ConnInfo)),
+      keepalive => maps:get(keepalive, ConnInfo)
+     }.
+
+from_clientinfo(ClientInfo) ->
+    #{node => nodestr(),
+      clientid => maps:get(clientid, ClientInfo),
+      username => maybe(maps:get(username, ClientInfo, <<>>)),
+      password => maybe(maps:get(password, ClientInfo, <<>>)),
+      peerhost => ntoa(maps:get(peerhost, ClientInfo)),
+      sockport => maps:get(sockport, ClientInfo),
+      protocol => stringfy(maps:get(protocol, ClientInfo)),
+      mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)),
+      is_superuser => maps:get(is_superuser, ClientInfo, false),
+      anonymous => maps:get(anonymous, ClientInfo, true)
+    }.
+
+from_message(Msg) ->
+    #{node => nodestr(),
+      id => emqx_guid:to_hexstr(emqx_message:id(Msg)),
+      qos => emqx_message:qos(Msg),
+      from => stringfy(emqx_message:from(Msg)),
+      topic => emqx_message:topic(Msg),
+      payload => emqx_message:payload(Msg),
+      timestamp => emqx_message:timestamp(Msg)
+     }.
 
 %%--------------------------------------------------------------------
 %% Helper
@@ -533,7 +515,15 @@ shutdown_reason() ->
     oneof([utf8(), {shutdown, emqx_ct_proper_types:limited_atom()}]).
 
 authresult() ->
-    #{auth_result => connack_return_code()}.
-
-%topic_filter_env() ->
-%    oneof([{<<"#">>}, {undefined}, {topic()}]).
+    ?LET(RC, connack_return_code(), #{auth_result => RC}).
+
+inject_magic_into(Key, Object) ->
+    case castspell() of
+        muggles -> Object;
+        Spell ->
+            Object#{Key => Spell}
+    end.
+
+castspell() ->
+    L = [<<"baduser">>, <<"gooduser">>, <<"normaluser">>, muggles],
+    lists:nth(rand:uniform(length(L)), L).