1: -module(ejabberd_hooks_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -define(HOST, <<"localhost">>).
    5: 
    6: all() ->
    7:     [
    8:       a_module_fun_can_be_added,
    9:       a_module_fun_can_be_removed,
   10: 
   11:       hooks_run_launches_nullary_fun,
   12:       hooks_run_launches_unary_fun,
   13:       hooks_run_ignores_different_arity_funs,
   14:       hooks_run_stops_when_fun_returns_stop,
   15: 
   16:       hooks_run_fold_folds_with_unary_fun,
   17:       hooks_run_fold_folds_with_binary_fun,
   18:       hooks_run_fold_passes_acc_along,
   19:       hooks_run_fold_stops_when_fun_returns_stop,
   20:       hooks_run_fold_preserves_order,
   21: 
   22:       error_in_run_fold_is_ignored,
   23:       throw_in_run_fold_is_ignored,
   24:       exit_in_run_fold_is_ignored
   25:     ].
   26: 
   27: init_per_suite(C) ->
   28:     application:ensure_all_started(exometer_core),
   29:     mongoose_config:set_opt(all_metrics_are_global, false),
   30:     C.
   31: 
   32: end_per_suite(_C) ->
   33:     mongoose_config:unset_opt(all_metrics_are_global),
   34:     application:stop(exometer_core).
   35: 
   36: a_module_fun_can_be_added(_) ->
   37:     given_hooks_started(),
   38:     given_module(hook_mod, fun_a, fun(_) -> ok end),
   39: 
   40:     % when
   41:     ejabberd_hooks:add(test_run_hook, ?HOST, hook_mod, fun_a, 1),
   42: 
   43:     FunEjabberdHooksWrapper = fun ejabberd_hooks:gen_hook_fn_wrapper/3,
   44:     % then
   45:     [{{test_run_hook, <<"localhost">>},
   46:       [{hook_handler, 1, FunEjabberdHooksWrapper,
   47:         #{function := fun_a, module := hook_mod}}]}] = get_hooks().
   48: 
   49: a_module_fun_can_be_removed(_) ->
   50:     given_hooks_started(),
   51:     given_module(hook_mod, fun_nullary, fun() -> success0 end),
   52:     given_hook_added(test_run_hook, hook_mod, fun_nullary, 1),
   53: 
   54:     % when
   55:     ejabberd_hooks:delete(test_run_hook, ?HOST, hook_mod, fun_nullary, 1),
   56: 
   57:     % then
   58:     [{{test_run_hook,<<"localhost">>}, []}] = get_hooks().
   59: 
   60: 
   61: hooks_run_launches_nullary_fun(_) ->
   62:     given_hooks_started(),
   63:     given_module(hook_mod, fun_nullary, fun(_) -> #{result => success0} end),
   64:     given_hook_added(test_run_hook, hook_mod, fun_nullary, 1),
   65: 
   66:     %% when
   67:     run_fold(test_run_hook, ?HOST, ok, []),
   68: 
   69:     %% then
   70:     H = meck:history(hook_mod),
   71:     [{_,{hook_mod,fun_nullary,[ok]}, #{result := success0}}] = H.
   72: 
   73: hooks_run_launches_unary_fun(_) ->
   74:     given_hooks_started(),
   75:     given_module(hook_mod, fun_onearg, fun(Acc, Val) -> Val end),
   76:     given_hook_added(test_run_hook, hook_mod, fun_onearg, 1),
   77: 
   78:     %% when
   79:     run_fold(test_run_hook, ?HOST, ok, [oneval]),
   80: 
   81:     %% then
   82:     [{_,{hook_mod,fun_onearg,[ok, oneval]}, oneval}] = meck:history(hook_mod).
   83: 
   84: hooks_run_ignores_different_arity_funs(_) ->
   85:     given_hooks_started(),
   86:     given_module(hook_mod, fun_onearg, fun(ok, unused) -> never_return end),
   87:     given_fun(hook_mod, fun_twoarg, fun(ok, one, two)-> success2 end),
   88: 
   89:     given_hook_added(test_run_hook, hook_mod, fun_onearg, 1),
   90:     given_hook_added(test_run_hook, hook_mod, fun_twoarg, 1),
   91: 
   92:     %% when
   93:     run_fold(test_run_hook, ?HOST, ok, [one, two]),
   94: 
   95:     %% then
   96:     [{_,{hook_mod, fun_twoarg, [ok, one, two]}, success2}] = meck:history(hook_mod).
   97: 
   98: hooks_run_stops_when_fun_returns_stop(_) ->
   99:     given_hooks_started(),
  100:     given_module(hook_mod, a_fun, const(stop)),
  101:     given_fun(hook_mod, another_fun, const(success)),
  102: 
  103:     given_hook_added(test_run_hook, hook_mod, a_fun, 1),
  104:     given_hook_added(test_run_hook, hook_mod, another_fun, 2),
  105: 
  106:     %% when
  107:     run_fold(test_run_hook, ?HOST, ok, []),
  108: 
  109:     %% then
  110:     [{_,{hook_mod,a_fun,[ok]}, stop}] = meck:history(hook_mod).
  111: 
  112: 
  113: hooks_run_fold_folds_with_unary_fun(_) ->
  114:     given_hooks_started(),
  115:     given_module(hook_mod, unary_folder, fun(initial) -> done end),
  116:     given_hook_added(test_fold_hook, hook_mod, unary_folder, 1),
  117: 
  118:     %% when
  119:     run_fold(test_fold_hook, ?HOST, initial, []),
  120: 
  121:     %% then
  122:     [{_,{hook_mod,unary_folder,[initial]}, done}] = meck:history(hook_mod).
  123: 
  124: hooks_run_fold_folds_with_binary_fun(_) ->
  125:     given_hooks_started(),
  126:     given_module(hook_mod, binary_folder, fun(initial, arg1) -> done end),
  127:     given_hook_added(test_fold_hook, hook_mod, binary_folder, 1),
  128: 
  129:     %% when
  130:     run_fold(test_fold_hook, ?HOST, initial, [arg1]),
  131: 
  132:     %% then
  133:     [{_,{hook_mod,binary_folder,[initial, arg1]}, done}] = meck:history(hook_mod).
  134: 
  135: hooks_run_fold_passes_acc_along(_) ->
  136:     given_hooks_started(),
  137:     given_module(hook_mod1, first_folder, fun(N, Const) -> N+Const end),
  138:     given_module(hook_mod2, second_folder, fun(N, Const) -> N-Const*2 end),
  139: 
  140:     given_hook_added(test_fold_hook, hook_mod1, first_folder, 1),
  141:     given_hook_added(test_fold_hook, hook_mod2, second_folder, 2),
  142: 
  143:     %% when
  144:     R = run_fold(test_fold_hook, ?HOST, 0, [10]),
  145: 
  146:     %% then
  147:     -10 = R.
  148: 
  149: hooks_run_fold_stops_when_fun_returns_stop(_) ->
  150:     given_hooks_started(),
  151:     given_module(hook_mod1, stopper, const(stop)),
  152:     given_module(hook_mod2, folder, const(continue)),
  153: 
  154:     given_hook_added(test_fold_hook, hook_mod1, stopper, 1),
  155:     given_hook_added(test_fold_hook, hook_mod2, folder, 2),
  156: 
  157:     %% when
  158:     R = run_fold(test_fold_hook, ?HOST, continue, []),
  159: 
  160:     %% then
  161:     [{_,{hook_mod1,stopper,[continue]}, stop}] = meck:history(hook_mod1),
  162:     [] = meck:history(hook_mod2),
  163:     stopped = R.
  164: 
  165: 
  166: hooks_run_fold_preserves_order(_) ->
  167:     given_hooks_started(),
  168:     given_module(hook_mod1, first_folder, const(1)),
  169:     given_module(hook_mod2, second_folder, const(2)),
  170: 
  171:     given_hook_added(test_fold_hook, hook_mod1, first_folder, 1),
  172:     given_hook_added(test_fold_hook, hook_mod2, second_folder, 2),
  173: 
  174:     %% when
  175:     R = run_fold(test_fold_hook, ?HOST, 0, []),
  176: 
  177:     %% then
  178:     2 = R.
  179: 
  180: 
  181: error_in_run_fold_is_ignored(_) ->
  182:     given_hooks_started(),
  183: 
  184:     given_module(failing_mod, broken, fun(_) -> error(broken) end),
  185:     given_hook_added(test_fold_hook, failing_mod, broken, 1),
  186: 
  187:     given_module(working_mod, good, const(i_was_run)),
  188:     given_hook_added(test_fold_hook, working_mod, good, 2),
  189: 
  190:     %% when
  191:     R = run_fold(test_fold_hook, ?HOST, initial, []),
  192: 
  193:     %% then
  194:     i_was_run = R,
  195:     [{_Pid, {failing_mod,broken,[initial]}, error,broken, _Stacktrace}] =
  196:         meck:history(failing_mod).
  197: 
  198: 
  199: throw_in_run_fold_is_ignored(_) ->
  200:     given_hooks_started(),
  201: 
  202:     given_module(throwing_mod, throwing_fun, fun(_) -> throw(ball) end),
  203:     given_hook_added(test_fold_hook, throwing_mod, throwing_fun, 1),
  204: 
  205:     given_module(working_mod, good, fun(X) -> X end),
  206:     given_hook_added(test_fold_hook, working_mod, good, 2),
  207: 
  208:     %% when
  209:     R = run_fold(test_fold_hook, ?HOST, initial, []),
  210: 
  211:     %% then
  212:     initial = R,
  213:     [{_Pid, {throwing_mod,throwing_fun,[initial]}, throw, ball, _ST}] =
  214:         meck:history(throwing_mod).
  215: 
  216: 
  217: exit_in_run_fold_is_ignored(_) ->
  218:     given_hooks_started(),
  219: 
  220:     given_module(exiting_mod, exiting_fun,
  221:                  fun(_) -> meck:exception(exit,oops) end),
  222:     given_hook_added(test_fold_hook, exiting_mod, exiting_fun, 1),
  223: 
  224:     given_module(working_mod, good, fun(X) -> X end),
  225:     given_hook_added(test_fold_hook, working_mod, good, 2),
  226: 
  227:     %% when
  228:     R = run_fold(test_fold_hook, ?HOST, initial, []),
  229: 
  230:     %% then
  231:     initial = R,
  232:     [{_Pid, {exiting_mod,exiting_fun,[initial]}, exit, oops, _ST}] =
  233:         meck:history(exiting_mod).
  234: 
  235: 
  236: 
  237: 
  238: %% Givens
  239: const(N) -> fun(_) -> N end.
  240: 
  241: given_hooks_started() ->
  242:     gen_hook:start_link().
  243: 
  244: given_hook_added(HookName, ModName, FunName, Prio) ->
  245:     ejabberd_hooks:add(HookName, ?HOST, ModName, FunName, Prio).
  246: 
  247: given_module(ModName, FunName, Fun) ->
  248:     catch meck:unload(ModName),
  249:     meck:new(ModName, [non_strict]),
  250:     meck:expect(ModName, FunName, Fun).
  251: 
  252: given_fun(ModName, FunName, Fun) ->
  253:     meck:expect(ModName, FunName, Fun).
  254: 
  255: run_fold(HookName, HostType, Acc, Args) ->
  256:     Params = ejabberd_hooks:add_args(#{}, Args),
  257:     {_, RetValue} = gen_hook:run_fold(HookName, HostType, Acc, Params),
  258:     RetValue.
  259: 
  260: get_hooks() ->
  261:     ets:tab2list(gen_hook).