-module(admin_handler_banned_words_tests). -include_lib("eunit/include/eunit.hrl"). -include("records.hrl"). setup() -> ok = meck:new(cowboy_req, [non_strict]), ok = meck:new(handler_auth, [non_strict]), ok = meck:new(core_user, [non_strict]), ok = meck:new(core_banned_words, [non_strict]), ok = meck:expect(cowboy_req, reply, fun(Code, Headers, Body, Req) -> put(test_reply, {Code, Headers, Body, Req}) end), ok. cleanup(_) -> meck:unload(core_banned_words), meck:unload(core_user), meck:unload(handler_auth), meck:unload(cowboy_req). admin_banned_words_test_() -> {setup, fun setup/0, fun cleanup/1, [ {"GET /admin/banned-words – success", fun test_list/0}, {"GET /admin/banned-words – forbidden", fun test_list_forbidden/0}, {"POST /admin/banned-words – success", fun test_add/0}, {"POST /admin/banned-words – missing field", fun test_add_missing/0}, {"POST /admin/banned-words – already exists", fun test_add_exists/0}, {"DELETE /admin/banned-words/:word – success", fun test_delete/0}, {"DELETE /admin/banned-words/:word – not found", fun test_delete_not_found/0}, {"PUT /admin/banned-words/:word – success", fun test_update/0}, {"PUT /admin/banned-words/:word – not found", fun test_update_not_found/0}, {"PATCH /admin/banned-words – method not allowed", fun test_wrong_method/0} ]}. test_list() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> undefined end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end), ok = meck:expect(handler_auth, authenticate, fun(Req) -> {ok, <<"adm1">>, Req} end), AdminUser = #user{id = <<"adm1">>, role = admin}, ok = meck:expect(core_user, get_by_id, fun(<<"adm1">>) -> {ok, AdminUser} end), Words = [ #banned_word{id = <<"bw1">>, word = <<"badword">>, added_by = <<"adm1">>, added_at = {{2026,4,27},{12,0,0}}}, #banned_word{id = <<"bw2">>, word = <<"spamword">>, added_by = <<"adm2">>, added_at = {{2026,4,27},{12,30,0}}} ], ok = meck:expect(core_banned_words, list_banned_words, fun() -> Words end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, RespBody, _} = erase(test_reply), ?assertEqual(200, Status), Result = jsx:decode(RespBody, [return_maps]), ?assertEqual(2, length(Result)), First = hd(Result), ?assertEqual(<<"badword">>, maps:get(<<"word">>, First)), ?assertEqual(<<"adm1">>, maps:get(<<"added_by">>, First)). test_list_forbidden() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> undefined end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end), ok = meck:expect(handler_auth, authenticate, fun(Req) -> {error, 403, <<"Admin access required">>, Req} end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, _, _} = erase(test_reply), ?assertEqual(403, Status). test_add() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> undefined end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end), ok = meck:expect(handler_auth, authenticate, fun(Req) -> {ok, <<"adm1">>, Req} end), AdminUser = #user{id = <<"adm1">>, role = admin}, ok = meck:expect(core_user, get_by_id, fun(<<"adm1">>) -> {ok, AdminUser} end), ok = meck:expect(cowboy_req, read_body, fun(Req) -> {ok, jsx:encode(#{<<"word">> => <<"banned">>}), Req} end), NewBW = #banned_word{id = <<"bw_new">>, word = <<"banned">>, added_by = <<"adm1">>, added_at = {{2026,4,27},{13,0,0}}}, ok = meck:expect(core_banned_words, add_banned_word, fun(<<"banned">>, <<"adm1">>) -> {ok, NewBW} end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, RespBody, _} = erase(test_reply), ?assertEqual(201, Status), #{<<"word">> := <<"banned">>, <<"added_by">> := <<"adm1">>} = jsx:decode(RespBody, [return_maps]). test_add_missing() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> undefined end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end), ok = meck:expect(handler_auth, authenticate, fun(Req) -> {ok, <<"adm1">>, Req} end), AdminUser = #user{id = <<"adm1">>, role = admin}, ok = meck:expect(core_user, get_by_id, fun(<<"adm1">>) -> {ok, AdminUser} end), ok = meck:expect(cowboy_req, read_body, fun(Req) -> {ok, jsx:encode(#{<<"other">> => <<"data">>}), Req} end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, _, _} = erase(test_reply), ?assertEqual(400, Status). test_add_exists() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> undefined end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end), ok = meck:expect(handler_auth, authenticate, fun(Req) -> {ok, <<"adm1">>, Req} end), AdminUser = #user{id = <<"adm1">>, role = admin}, ok = meck:expect(core_user, get_by_id, fun(<<"adm1">>) -> {ok, AdminUser} end), ok = meck:expect(cowboy_req, read_body, fun(Req) -> {ok, jsx:encode(#{<<"word">> => <<"already">>}), Req} end), ok = meck:expect(core_banned_words, add_banned_word, fun(_, _) -> {error, already_exists} end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, _, _} = erase(test_reply), ?assertEqual(409, Status). test_delete() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> <<"badword">> end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"DELETE">> end), ok = meck:expect(handler_auth, authenticate, fun(Req) -> {ok, <<"adm1">>, Req} end), AdminUser = #user{id = <<"adm1">>, role = admin}, ok = meck:expect(core_user, get_by_id, fun(<<"adm1">>) -> {ok, AdminUser} end), ok = meck:expect(core_banned_words, remove_banned_word, fun(<<"badword">>) -> {ok, deleted} end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, RespBody, _} = erase(test_reply), ?assertEqual(200, Status), #{<<"status">> := <<"deleted">>} = jsx:decode(RespBody, [return_maps]). test_delete_not_found() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> <<"unknown">> end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"DELETE">> end), ok = meck:expect(handler_auth, authenticate, fun(Req) -> {ok, <<"adm1">>, Req} end), AdminUser = #user{id = <<"adm1">>, role = admin}, ok = meck:expect(core_user, get_by_id, fun(<<"adm1">>) -> {ok, AdminUser} end), ok = meck:expect(core_banned_words, remove_banned_word, fun(_) -> {error, not_found} end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, _, _} = erase(test_reply), ?assertEqual(404, Status). test_update() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> <<"oldword">> end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"PUT">> end), ok = meck:expect(handler_auth, authenticate, fun(Req) -> {ok, <<"adm1">>, Req} end), AdminUser = #user{id = <<"adm1">>, role = admin}, ok = meck:expect(core_user, get_by_id, fun(<<"adm1">>) -> {ok, AdminUser} end), ok = meck:expect(cowboy_req, read_body, fun(Req) -> {ok, jsx:encode(#{<<"word">> => <<"newword">>}), Req} end), UpdatedBW = #banned_word{id = <<"bw1">>, word = <<"newword">>, added_by = <<"adm1">>, added_at = {{2026,4,27},{12,0,0}}}, ok = meck:expect(core_banned_words, update_banned_word, fun(_, _) -> {ok, UpdatedBW} end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, RespBody, _} = erase(test_reply), ?assertEqual(200, Status), Resp = jsx:decode(RespBody, [return_maps]), ?assertEqual(<<"newword">>, maps:get(<<"word">>, Resp)), ?assertEqual(<<"adm1">>, maps:get(<<"added_by">>, Resp)). test_update_not_found() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> <<"missing">> end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"PUT">> end), ok = meck:expect(handler_auth, authenticate, fun(Req) -> {ok, <<"adm1">>, Req} end), AdminUser = #user{id = <<"adm1">>, role = admin}, ok = meck:expect(core_user, get_by_id, fun(<<"adm1">>) -> {ok, AdminUser} end), ok = meck:expect(cowboy_req, read_body, fun(Req) -> {ok, jsx:encode(#{<<"word">> => <<"newword">>}), Req} end), ok = meck:expect(core_banned_words, update_banned_word, fun(_, _) -> {error, not_found} end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, _, _} = erase(test_reply), ?assertEqual(404, Status). test_wrong_method() -> ok = meck:expect(cowboy_req, binding, fun(word, _) -> undefined end), ok = meck:expect(cowboy_req, method, fun(_) -> <<"PATCH">> end), {ok, _, _} = admin_handler_banned_words:init(req, []), {Status, _, RespBody, _} = erase(test_reply), ?assertEqual(405, Status), #{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).