Rewrite and expand EUnit suite; run unit tests in CI from shared test image. Refs EventHub/EventHubBack#36 [skip ci]
This commit is contained in:
@@ -93,7 +93,7 @@ jobs:
|
||||
--sbom=false \
|
||||
.
|
||||
|
||||
- name: Build API test image
|
||||
- name: Build test image (EUnit + CT)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
bash scripts/retry-docker-build.sh 5 60 -- \
|
||||
@@ -101,14 +101,19 @@ jobs:
|
||||
--network=host \
|
||||
--pull \
|
||||
--file docker/ApiTests.Dockerfile \
|
||||
--tag eventhub-api-tests:latest \
|
||||
--tag eventhub-tests:latest \
|
||||
--load \
|
||||
--provenance=false \
|
||||
--sbom=false \
|
||||
.
|
||||
|
||||
- name: Run unit tests (EUnit)
|
||||
run: >
|
||||
docker run --rm eventhub-tests:latest
|
||||
rebar3 eunit --sname ci_eunit --verbose
|
||||
|
||||
- name: Run API tests (local CT)
|
||||
run: docker run --rm eventhub-api-tests:latest
|
||||
run: docker run --rm eventhub-tests:latest
|
||||
|
||||
- name: Push tested eventhub image
|
||||
if: github.event_name == 'push'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# API common_test — только builder-стадия (полный OTP + компиляторы).
|
||||
# EUnit + API common_test — один builder-образ, разные CMD в CI.
|
||||
ARG ERLANG_BUILDER_IMAGE=git.sabilin.com/eventhub/erlang-builder:latest
|
||||
FROM ${ERLANG_BUILDER_IMAGE}
|
||||
|
||||
@@ -7,9 +7,12 @@ WORKDIR /app
|
||||
COPY rebar.config ./
|
||||
COPY include/ include/
|
||||
COPY src/ src/
|
||||
COPY test/unit/ test/unit/
|
||||
COPY test/api_*_SUITE.erl test/
|
||||
COPY test/api/ test/api/
|
||||
|
||||
RUN rebar3 compile
|
||||
# default profile: CT (test/api); test profile: EUnit (test/unit)
|
||||
RUN rebar3 compile && rebar3 as test compile
|
||||
|
||||
# По умолчанию CT; для eunit: docker run … rebar3 eunit --sname ci_eunit --verbose
|
||||
CMD ["rebar3", "ct", "--sname", "ci_api_test", "-v"]
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
-module(admin_handler_admins_by_id_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(TARGET_ID, <<"adm_target_1">>).
|
||||
-define(PLAIN_ID, <<"adm_plain_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID, role => superadmin}),
|
||||
ok = mnesia:dirty_write(eh_test_support:make_admin(#{
|
||||
id => ?TARGET_ID,
|
||||
email => <<"target@test.local">>,
|
||||
role => admin,
|
||||
nickname => <<"target">>
|
||||
})),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_admins_by_id_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – success", {timeout, 60, fun test_get/0}},
|
||||
{"GET – not found", {timeout, 60, fun test_get_not_found/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"PUT – update nickname", {timeout, 60, fun test_update/0}},
|
||||
{"PUT – not found", {timeout, 60, fun test_update_not_found/0}},
|
||||
{"PUT – forbidden for non-superadmin", {timeout, 60, fun test_update_forbidden/0}},
|
||||
{"DELETE – success", {timeout, 60, fun test_delete/0}},
|
||||
{"DELETE – not found", {timeout, 60, fun test_delete_not_found/0}},
|
||||
{"DELETE – forbidden for non-superadmin", {timeout, 60, fun test_delete_forbidden/0}},
|
||||
{"PATCH – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
test_get() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/admins/", ?TARGET_ID/binary>>,
|
||||
bindings => #{id => ?TARGET_ID},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(?TARGET_ID, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"target@test.local">>, maps:get(<<"email">>, Result)).
|
||||
|
||||
test_get_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/admins/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/admins/", ?TARGET_ID/binary>>,
|
||||
bindings => #{id => ?TARGET_ID},
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_update() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/admins/", ?TARGET_ID/binary>>,
|
||||
bindings => #{id => ?TARGET_ID},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"nickname">> => <<"updated">>})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"updated">>, maps:get(<<"nickname">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
test_update_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/admins/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"nickname">> => <<"x">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_update_forbidden() ->
|
||||
ok = mnesia:dirty_write(eh_test_support:make_admin(#{
|
||||
id => ?PLAIN_ID,
|
||||
email => <<"plain@test.local">>,
|
||||
role => admin
|
||||
})),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/admins/", ?TARGET_ID/binary>>,
|
||||
bindings => #{id => ?TARGET_ID},
|
||||
auth => ?PLAIN_ID,
|
||||
body => jsx:encode(#{<<"nickname">> => <<"nope">>})
|
||||
}),
|
||||
?assertEqual(403, Status).
|
||||
|
||||
test_delete() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/admins/", ?TARGET_ID/binary>>,
|
||||
bindings => #{id => ?TARGET_ID},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(Body, [return_maps]),
|
||||
?assertMatch({error, not_found}, core_admin:get_by_id(?TARGET_ID)).
|
||||
|
||||
test_delete_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/admins/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_delete_forbidden() ->
|
||||
ok = mnesia:dirty_write(eh_test_support:make_admin(#{
|
||||
id => ?PLAIN_ID,
|
||||
email => <<"plain@test.local">>,
|
||||
role => admin
|
||||
})),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/admins/", ?TARGET_ID/binary>>,
|
||||
bindings => #{id => ?TARGET_ID},
|
||||
auth => ?PLAIN_ID
|
||||
}),
|
||||
?assertEqual(403, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins_by_id, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/admins/", ?TARGET_ID/binary>>,
|
||||
bindings => #{id => ?TARGET_ID},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,147 @@
|
||||
-module(admin_handler_admins_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(PLAIN_ID, <<"adm_plain_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
catch application:ensure_all_started(argon2),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID, role => superadmin}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_admins_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", {timeout, 60, fun test_list/0}},
|
||||
{"GET list – filter by role", {timeout, 60, fun test_list_filter/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST create – success", {timeout, 60, fun test_create_ok/0}},
|
||||
{"POST create – forbidden for non-superadmin", {timeout, 60, fun test_create_forbidden/0}},
|
||||
{"POST create – missing fields", {timeout, 60, fun test_create_missing_fields/0}},
|
||||
{"DELETE – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
argon2_ready() ->
|
||||
case catch argon2:hash(<<"probe">>) of
|
||||
{ok, _} -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
require_argon2(Fun) ->
|
||||
case argon2_ready() of
|
||||
true -> Fun();
|
||||
false -> {skip, "argon2 NIF not available"}
|
||||
end.
|
||||
|
||||
seed_extra_admins() ->
|
||||
ok = mnesia:dirty_write(eh_test_support:make_admin(#{
|
||||
id => <<"adm_mod">>,
|
||||
email => <<"mod@test.local">>,
|
||||
role => moderator,
|
||||
nickname => <<"mod">>
|
||||
})),
|
||||
ok = mnesia:dirty_write(eh_test_support:make_admin(#{
|
||||
id => <<"adm_sup">>,
|
||||
email => <<"support@test.local">>,
|
||||
role => support,
|
||||
nickname => <<"sup">>
|
||||
})),
|
||||
ok.
|
||||
|
||||
test_list() ->
|
||||
seed_extra_admins(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_admins, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/admins">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(3, length(Result)).
|
||||
|
||||
test_list_filter() ->
|
||||
seed_extra_admins(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_admins, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/admins">>,
|
||||
auth => ?ADMIN_ID,
|
||||
qs => [{<<"role">>, <<"moderator">>}]
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, length(Result)),
|
||||
[Only] = Result,
|
||||
?assertEqual(<<"mod@test.local">>, maps:get(<<"email">>, Only)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/admins">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_create_ok() ->
|
||||
require_argon2(fun() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_admins, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/admins">>,
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{
|
||||
<<"email">> => <<"newadmin@test.local">>,
|
||||
<<"password">> => <<"SecretPass1!">>,
|
||||
<<"role">> => <<"admin">>
|
||||
})
|
||||
}),
|
||||
?assertEqual(201, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"newadmin@test.local">>, maps:get(<<"email">>, Result)),
|
||||
?assertEqual(<<"admin">>, maps:get(<<"role">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1)
|
||||
end).
|
||||
|
||||
test_create_forbidden() ->
|
||||
ok = mnesia:dirty_write(eh_test_support:make_admin(#{
|
||||
id => ?PLAIN_ID,
|
||||
email => <<"plain@test.local">>,
|
||||
role => admin
|
||||
})),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/admins">>,
|
||||
auth => ?PLAIN_ID,
|
||||
body => jsx:encode(#{
|
||||
<<"email">> => <<"x@test.local">>,
|
||||
<<"password">> => <<"SecretPass1!">>,
|
||||
<<"role">> => <<"admin">>
|
||||
})
|
||||
}),
|
||||
?assertEqual(403, Status).
|
||||
|
||||
test_create_missing_fields() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/admins">>,
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"email">> => <<"x@test.local">>})
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_admins, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/admins">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,97 @@
|
||||
-module(admin_handler_audit_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(PLAIN_ID, <<"adm_plain_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID, role => superadmin}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_audit_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", {timeout, 60, fun test_list/0}},
|
||||
{"GET list – filter by action", {timeout, 60, fun test_list_filter/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"GET – forbidden for non-superadmin", {timeout, 60, fun test_forbidden/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
seed_audit() ->
|
||||
{ok, _} = core_admin_audit:log(
|
||||
?ADMIN_ID, <<"admin@test.local">>, superadmin,
|
||||
<<"create_admin">>, <<"admin">>, <<"adm_x">>, <<"127.0.0.1">>
|
||||
),
|
||||
{ok, _} = core_admin_audit:log(
|
||||
?ADMIN_ID, <<"admin@test.local">>, superadmin,
|
||||
<<"update_me">>, <<"admin">>, ?ADMIN_ID, <<"127.0.0.1">>
|
||||
),
|
||||
ok.
|
||||
|
||||
test_list() ->
|
||||
seed_audit(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_audit, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/audit">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, length(Result)),
|
||||
[First | _] = Result,
|
||||
?assert(is_map_key(<<"action">>, First)),
|
||||
?assert(is_map_key(<<"admin_id">>, First)).
|
||||
|
||||
test_list_filter() ->
|
||||
seed_audit(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_audit, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/audit">>,
|
||||
auth => ?ADMIN_ID,
|
||||
qs => [{<<"action">>, <<"update_me">>}]
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, length(Result)),
|
||||
[Only] = Result,
|
||||
?assertEqual(<<"update_me">>, maps:get(<<"action">>, Only)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_audit, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/audit">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_forbidden() ->
|
||||
ok = mnesia:dirty_write(eh_test_support:make_admin(#{
|
||||
id => ?PLAIN_ID,
|
||||
email => <<"plain@test.local">>,
|
||||
role => admin
|
||||
})),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_audit, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/audit">>,
|
||||
auth => ?PLAIN_ID
|
||||
}),
|
||||
?assertEqual(403, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_audit, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/audit">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -2,157 +2,154 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, banned_word, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
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),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_banned_words),
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
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}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", fun test_list/0},
|
||||
{"GET list – forbidden without admin", fun test_list_forbidden/0},
|
||||
{"POST add – success", fun test_add/0},
|
||||
{"POST add – missing field", fun test_add_missing/0},
|
||||
{"POST add – already exists", fun test_add_exists/0},
|
||||
{"DELETE – success", fun test_delete/0},
|
||||
{"DELETE – not found", fun test_delete_not_found/0},
|
||||
{"PUT – success", fun test_update/0},
|
||||
{"PUT – not found", fun test_update_not_found/0},
|
||||
{"POST batch – success", fun test_batch/0},
|
||||
{"PATCH – 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),
|
||||
{ok, _} = core_banned_words:add_banned_word(<<"badword">>, ?ADMIN_ID),
|
||||
{ok, _} = core_banned_words:add_banned_word(<<"spamword">>, ?ADMIN_ID),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/banned-words">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(RespBody, [return_maps]),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, length(Result)),
|
||||
First = hd(Result),
|
||||
?assertEqual(<<"badword">>, maps:get(<<"word">>, First)),
|
||||
?assertEqual(<<"adm1">>, maps:get(<<"added_by">>, First)).
|
||||
Words = [maps:get(<<"word">>, W) || W <- Result],
|
||||
?assert(lists:member(<<"badword">>, Words)),
|
||||
?assert(lists:member(<<"spamword">>, Words)).
|
||||
|
||||
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).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/banned-words">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, 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),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/banned-words">>,
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"word">> => <<"banned">>})
|
||||
}),
|
||||
?assertEqual(201, Status),
|
||||
#{<<"word">> := <<"banned">>, <<"added_by">> := <<"adm1">>} = jsx:decode(RespBody, [return_maps]).
|
||||
#{<<"status">> := <<"added">>} = jsx:decode(Body, [return_maps]),
|
||||
?assert(core_banned_words:is_word_banned(<<"banned">>)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
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),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/banned-words">>,
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"other">> => <<"data">>})
|
||||
}),
|
||||
?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),
|
||||
{ok, _} = core_banned_words:add_banned_word(<<"already">>, ?ADMIN_ID),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/banned-words">>,
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"word">> => <<"already">>})
|
||||
}),
|
||||
?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),
|
||||
{ok, _} = core_banned_words:add_banned_word(<<"badword">>, ?ADMIN_ID),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/banned-words/badword">>,
|
||||
bindings => #{word => <<"badword">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(RespBody, [return_maps]).
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(false, core_banned_words:is_word_banned(<<"badword">>)).
|
||||
|
||||
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),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/banned-words/unknown">>,
|
||||
bindings => #{word => <<"unknown">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?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),
|
||||
{ok, _} = core_banned_words:add_banned_word(<<"old">>, ?ADMIN_ID),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/banned-words/old">>,
|
||||
bindings => #{word => <<"old">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"word">> => <<"new">>})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Resp = jsx:decode(RespBody, [return_maps]),
|
||||
?assertEqual(<<"newword">>, maps:get(<<"word">>, Resp)),
|
||||
?assertEqual(<<"adm1">>, maps:get(<<"added_by">>, Resp)).
|
||||
#{<<"status">> := <<"updated">>} = jsx:decode(Body, [return_maps]),
|
||||
?assert(core_banned_words:is_word_banned(<<"new">>)),
|
||||
?assertEqual(false, core_banned_words:is_word_banned(<<"old">>)).
|
||||
|
||||
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),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/banned-words/missing">>,
|
||||
bindings => #{word => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"word">> => <<"x">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_batch() ->
|
||||
{ok, _} = core_banned_words:add_banned_word(<<"exists">>, ?ADMIN_ID),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/banned-words/batch">>,
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"words">> => [<<"new1">>, <<"exists">>, <<"new2">>]})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"added">> := 2, <<"skipped">> := 1} = jsx:decode(Body, [return_maps]),
|
||||
?assert(core_banned_words:is_word_banned(<<"new1">>)),
|
||||
?assert(core_banned_words:is_word_banned(<<"new2">>)).
|
||||
|
||||
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]).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_banned_words, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/banned-words">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
-module(admin_handler_calendar_by_id_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, calendar, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(OWNER_ID, <<"owner_cal_by_id">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_calendar_by_id_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – success", {timeout, 60, fun test_get/0}},
|
||||
{"GET – not found", {timeout, 60, fun test_get_not_found/0}},
|
||||
{"PUT – update title", {timeout, 60, fun test_update/0}},
|
||||
{"PUT – not found", {timeout, 60, fun test_update_not_found/0}},
|
||||
{"DELETE – soft delete", {timeout, 60, fun test_delete/0}},
|
||||
{"DELETE – not found", {timeout, 60, fun test_delete_not_found/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"PATCH – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
seed_calendar() ->
|
||||
{ok, Cal} = core_calendar:create(?OWNER_ID, <<"Target Cal">>, <<"desc">>, auto),
|
||||
Cal.
|
||||
|
||||
test_get() ->
|
||||
Cal = seed_calendar(),
|
||||
CalId = Cal#calendar.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_calendar_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/calendars/", CalId/binary>>,
|
||||
bindings => #{id => CalId},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(CalId, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"Target Cal">>, maps:get(<<"title">>, Result)).
|
||||
|
||||
test_get_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_calendar_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/calendars/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_update() ->
|
||||
Cal = seed_calendar(),
|
||||
CalId = Cal#calendar.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_calendar_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/calendars/", CalId/binary>>,
|
||||
bindings => #{id => CalId},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{
|
||||
<<"title">> => <<"Updated Cal">>,
|
||||
<<"status">> => <<"frozen">>
|
||||
})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"Updated Cal">>, maps:get(<<"title">>, Result)),
|
||||
?assertEqual(<<"frozen">>, maps:get(<<"status">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
test_update_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_calendar_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/calendars/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"title">> => <<"X">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_delete() ->
|
||||
Cal = seed_calendar(),
|
||||
CalId = Cal#calendar.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_calendar_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/calendars/", CalId/binary>>,
|
||||
bindings => #{id => CalId},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
?assertEqual(#{}, jsx:decode(Body, [return_maps])),
|
||||
{ok, Deleted} = core_calendar:get_by_id(CalId),
|
||||
?assertEqual(deleted, Deleted#calendar.status),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
test_delete_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_calendar_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/calendars/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
Cal = seed_calendar(),
|
||||
CalId = Cal#calendar.id,
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_calendar_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/calendars/", CalId/binary>>,
|
||||
bindings => #{id => CalId},
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
Cal = seed_calendar(),
|
||||
CalId = Cal#calendar.id,
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_calendar_by_id, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/calendars/", CalId/binary>>,
|
||||
bindings => #{id => CalId},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,83 @@
|
||||
-module(admin_handler_calendar_stats_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, calendar, review, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(OWNER_ID, <<"owner_cal_stats">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_calendar_stats_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET stats – empty", {timeout, 60, fun test_stats_empty/0}},
|
||||
{"GET stats – with calendars", {timeout, 60, fun test_stats_with_data/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
test_stats_empty() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_calendar_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/calendars/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(0, maps:get(<<"total_calendars">>, Result)),
|
||||
?assertEqual(#{}, maps:get(<<"calendars_by_type">>, Result)),
|
||||
?assertEqual(#{}, maps:get(<<"calendars_by_status">>, Result)),
|
||||
?assertEqual([], maps:get(<<"top_calendars_by_reviews">>, Result)),
|
||||
?assertEqual([], maps:get(<<"top_calendars_by_rating">>, Result)).
|
||||
|
||||
test_stats_with_data() ->
|
||||
{ok, C1} = core_calendar:create(?OWNER_ID, <<"Personal">>, <<"d">>, auto, personal),
|
||||
{ok, _} = core_calendar:create(?OWNER_ID, <<"Shop">>, <<"d">>, manual, commercial),
|
||||
{ok, _} = core_calendar:update(C1#calendar.id, [
|
||||
{rating_avg, 4.5},
|
||||
{rating_count, 3}
|
||||
]),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_calendar_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/calendars/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, maps:get(<<"total_calendars">>, Result)),
|
||||
ByType = maps:get(<<"calendars_by_type">>, Result),
|
||||
?assertEqual(1, maps:get(<<"personal">>, ByType)),
|
||||
?assertEqual(1, maps:get(<<"commercial">>, ByType)),
|
||||
ByStatus = maps:get(<<"calendars_by_status">>, Result),
|
||||
?assertEqual(2, maps:get(<<"active">>, ByStatus)),
|
||||
TopRating = maps:get(<<"top_calendars_by_rating">>, Result),
|
||||
?assert(length(TopRating) >= 1),
|
||||
[First | _] = TopRating,
|
||||
?assertEqual(C1#calendar.id, maps:get(<<"id">>, First)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_calendar_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/calendars/stats">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_calendar_stats, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/calendars/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,76 @@
|
||||
-module(admin_handler_calendars_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, calendar, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(OWNER_ID, <<"owner_cal_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_calendars_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", {timeout, 60, fun test_list/0}},
|
||||
{"GET list – filter by status", {timeout, 60, fun test_list_filter/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
seed_calendars() ->
|
||||
{ok, C1} = core_calendar:create(?OWNER_ID, <<"Alpha">>, <<"desc a">>, auto, personal),
|
||||
{ok, C2} = core_calendar:create(?OWNER_ID, <<"Beta">>, <<"desc b">>, manual, commercial),
|
||||
{ok, _} = core_calendar:update(C2#calendar.id, [{status, frozen}]),
|
||||
{C1, C2}.
|
||||
|
||||
test_list() ->
|
||||
seed_calendars(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_calendars, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/calendars">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, length(Result)).
|
||||
|
||||
test_list_filter() ->
|
||||
seed_calendars(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_calendars, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/calendars">>,
|
||||
auth => ?ADMIN_ID,
|
||||
qs => [{<<"status">>, <<"active">>}]
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, length(Result)),
|
||||
[Only] = Result,
|
||||
?assertEqual(<<"Alpha">>, maps:get(<<"title">>, Only)),
|
||||
?assertEqual(<<"active">>, maps:get(<<"status">>, Only)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_calendars, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/calendars">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_calendars, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/calendars">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,142 @@
|
||||
-module(admin_handler_event_by_id_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, calendar, event, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(OWNER_ID, <<"owner_evt_by_id">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_event_by_id_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – success", {timeout, 60, fun test_get/0}},
|
||||
{"GET – not found", {timeout, 60, fun test_get_not_found/0}},
|
||||
{"PUT – update title/status", {timeout, 60, fun test_update/0}},
|
||||
{"PUT – not found", {timeout, 60, fun test_update_not_found/0}},
|
||||
{"DELETE – soft delete", {timeout, 60, fun test_delete/0}},
|
||||
{"DELETE – not found", {timeout, 60, fun test_delete_not_found/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"PATCH – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
seed_event() ->
|
||||
{ok, Cal} = core_calendar:create(?OWNER_ID, <<"Cal">>, <<"">>, auto),
|
||||
{ok, Event} = core_event:create(
|
||||
Cal#calendar.id,
|
||||
<<"Target Event">>,
|
||||
eh_test_support:future_start(),
|
||||
60
|
||||
),
|
||||
Event.
|
||||
|
||||
test_get() ->
|
||||
Event = seed_event(),
|
||||
EventId = Event#event.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_event_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/events/", EventId/binary>>,
|
||||
bindings => #{id => EventId},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(EventId, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"Target Event">>, maps:get(<<"title">>, Result)).
|
||||
|
||||
test_get_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_event_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/events/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_update() ->
|
||||
Event = seed_event(),
|
||||
EventId = Event#event.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_event_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/events/", EventId/binary>>,
|
||||
bindings => #{id => EventId},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{
|
||||
<<"title">> => <<"Updated Event">>,
|
||||
<<"status">> => <<"cancelled">>
|
||||
})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"Updated Event">>, maps:get(<<"title">>, Result)),
|
||||
?assertEqual(<<"cancelled">>, maps:get(<<"status">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
test_update_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_event_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/events/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"title">> => <<"X">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_delete() ->
|
||||
Event = seed_event(),
|
||||
EventId = Event#event.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_event_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/events/", EventId/binary>>,
|
||||
bindings => #{id => EventId},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(Body, [return_maps]),
|
||||
{ok, Deleted} = core_event:get_by_id(EventId),
|
||||
?assertEqual(deleted, Deleted#event.status),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
test_delete_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_event_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/events/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
Event = seed_event(),
|
||||
EventId = Event#event.id,
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_event_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/events/", EventId/binary>>,
|
||||
bindings => #{id => EventId},
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
Event = seed_event(),
|
||||
EventId = Event#event.id,
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_event_by_id, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/events/", EventId/binary>>,
|
||||
bindings => #{id => EventId},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,84 @@
|
||||
-module(admin_handler_event_stats_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, calendar, event, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(OWNER_ID, <<"owner_evt_stats">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_event_stats_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET stats – empty", {timeout, 60, fun test_stats_empty/0}},
|
||||
{"GET stats – with events", {timeout, 60, fun test_stats_with_data/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
test_stats_empty() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_event_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/events/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(0, maps:get(<<"total_events">>, Result)),
|
||||
?assertEqual(#{}, maps:get(<<"events_by_type">>, Result)),
|
||||
?assertEqual(#{}, maps:get(<<"events_by_status">>, Result)),
|
||||
?assertEqual([], maps:get(<<"top_events_by_rating">>, Result)).
|
||||
|
||||
test_stats_with_data() ->
|
||||
{ok, Cal} = core_calendar:create(?OWNER_ID, <<"Cal">>, <<"">>, auto),
|
||||
CalId = Cal#calendar.id,
|
||||
Start = eh_test_support:future_start(),
|
||||
{ok, E1} = core_event:create(CalId, <<"Rated">>, Start, 60),
|
||||
{ok, _} = core_event:create(CalId, <<"Other">>, Start, 30),
|
||||
{ok, _} = core_event:update(E1#event.id, [
|
||||
{rating_avg, 4.8},
|
||||
{rating_count, 5}
|
||||
]),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_event_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/events/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, maps:get(<<"total_events">>, Result)),
|
||||
ByType = maps:get(<<"events_by_type">>, Result),
|
||||
?assertEqual(2, maps:get(<<"single">>, ByType)),
|
||||
ByStatus = maps:get(<<"events_by_status">>, Result),
|
||||
?assertEqual(2, maps:get(<<"active">>, ByStatus)),
|
||||
Top = maps:get(<<"top_events_by_rating">>, Result),
|
||||
?assert(length(Top) >= 1),
|
||||
[First | _] = Top,
|
||||
?assertEqual(E1#event.id, maps:get(<<"id">>, First)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_event_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/events/stats">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_event_stats, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/events/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,81 @@
|
||||
-module(admin_handler_events_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, calendar, event, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(OWNER_ID, <<"owner_evt_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_events_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", {timeout, 60, fun test_list/0}},
|
||||
{"GET list – filter by title", {timeout, 60, fun test_list_filter/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
event_start() ->
|
||||
{{Y, M, D}, _} = eh_test_support:days_from_now(7),
|
||||
{{Y, M, D}, {12, 0, 0}}.
|
||||
|
||||
seed_events() ->
|
||||
{ok, Cal} = core_calendar:create(?OWNER_ID, <<"Cal">>, <<"">>, auto),
|
||||
CalId = Cal#calendar.id,
|
||||
Start = event_start(),
|
||||
{ok, E1} = core_event:create(CalId, <<"Meetup">>, Start, 60),
|
||||
{ok, E2} = core_event:create(CalId, <<"Workshop">>, Start, 90),
|
||||
{E1, E2}.
|
||||
|
||||
test_list() ->
|
||||
seed_events(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_events, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/events">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, length(Result)).
|
||||
|
||||
test_list_filter() ->
|
||||
seed_events(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_events, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/events">>,
|
||||
auth => ?ADMIN_ID,
|
||||
qs => [{<<"title">>, <<"Meetup">>}]
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, length(Result)),
|
||||
[Only] = Result,
|
||||
?assertEqual(<<"Meetup">>, maps:get(<<"title">>, Only)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_events, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/events">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_events, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/events">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -1,49 +1,37 @@
|
||||
-module(admin_handler_health_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Фикстуры
|
||||
%% ------------------------------------------------------------------
|
||||
setup() ->
|
||||
ok = meck:new(cowboy_req, [non_strict]),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
ok.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Тесты
|
||||
%% ------------------------------------------------------------------
|
||||
admin_handler_health_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/health returns 200 with status ok",
|
||||
fun test_health_get/0},
|
||||
{"POST /admin/health returns 405 Method not allowed",
|
||||
fun test_health_post/0}
|
||||
admin_health_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET health – success", fun test_health_get/0},
|
||||
{"POST health – method not allowed", fun test_health_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% ── Успешный GET ─────────────────────────────────────────────
|
||||
test_health_get() ->
|
||||
% Мокаем method → <<"GET">>
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
% reply/4 будем перехватывать
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_health:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_health, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/admin/health">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
?assertEqual(#{<<"status">> => <<"ok">>}, jsx:decode(RespBody, [return_maps])).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"ok">>, maps:get(<<"status">>, Result)),
|
||||
?assertEqual(<<"eventhub">>, maps:get(<<"service">>, Result)),
|
||||
?assert(is_map_key(<<"version">>, Result)),
|
||||
?assert(is_map_key(<<"git_sha">>, Result)).
|
||||
|
||||
%% ── Метод не разрешён ───────────────────────────────────────
|
||||
test_health_post() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_health:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_health_wrong_method() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_health, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/admin/health">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(405, Status),
|
||||
?assertEqual(#{<<"error">> => <<"Method not allowed">>}, jsx:decode(RespBody, [return_maps])).
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(Body, [return_maps]).
|
||||
|
||||
@@ -2,122 +2,134 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(JWT_SECRET, <<"test-user-secret-key-32-byt!">>).
|
||||
-define(ADMIN_JWT_SECRET, <<"test-admin-secret-key-32-b">>).
|
||||
-define(TABLES, [admin, admin_audit, auth_session, ticket]).
|
||||
-define(EMAIL, <<"admin@test.local">>).
|
||||
-define(PASSWORD, <<"SecretPass1!">>).
|
||||
|
||||
setup() ->
|
||||
ok = meck:new(logic_auth, [non_strict]),
|
||||
ok = meck:new(cowboy_req, [non_strict]),
|
||||
ok = meck:new(core_admin, [non_strict]),
|
||||
ok = meck:new(admin_utils, [non_strict]),
|
||||
mnesia:start(),
|
||||
catch ets:new(eventhub_counters, [named_table, public, set, {write_concurrency, true}]),
|
||||
mnesia:create_table(auth_session, [
|
||||
{attributes, record_info(fields, auth_session)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:add_table_index(auth_session, #auth_session.family_id),
|
||||
mnesia:add_table_index(auth_session, #auth_session.subject_id),
|
||||
application:set_env(eventhub, jwt_secret, ?JWT_SECRET),
|
||||
application:set_env(eventhub, admin_jwt_secret, ?ADMIN_JWT_SECRET),
|
||||
{ok, _} = application:ensure_all_started(jose).
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
catch application:ensure_all_started(argon2),
|
||||
catch ets:new(eventhub_counters, [named_table, public, set, {write_concurrency, true}]),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
application:unset_env(eventhub, jwt_secret),
|
||||
application:unset_env(eventhub, admin_jwt_secret),
|
||||
application:stop(jose),
|
||||
catch mnesia:delete_table(auth_session),
|
||||
catch ets:delete(eventhub_counters),
|
||||
mnesia:stop(),
|
||||
meck:unload(admin_utils),
|
||||
meck:unload(core_admin),
|
||||
meck:unload(cowboy_req),
|
||||
meck:unload(logic_auth),
|
||||
ok.
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_handler_login_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"Valid admin login returns 200 and token", fun test_valid_admin_login/0},
|
||||
{"Invalid credentials return 401", fun test_invalid_credentials/0},
|
||||
{"Non‑admin role returns 403", fun test_insufficient_permissions/0},
|
||||
{"Malformed JSON returns 400", fun test_malformed_json/0},
|
||||
{"Missing body returns 400", fun test_missing_body/0},
|
||||
{"Wrong HTTP method returns 405", fun test_wrong_method/0}
|
||||
]}.
|
||||
admin_login_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"POST login – success", fun test_login_ok/0},
|
||||
{"POST login – bad credentials", fun test_login_bad_credentials/0},
|
||||
{"POST login – insufficient permissions", fun test_login_forbidden_role/0},
|
||||
{"POST login – missing fields", fun test_login_missing_fields/0},
|
||||
{"POST login – missing body", fun test_login_missing_body/0},
|
||||
{"POST login – invalid JSON", fun test_login_invalid_json/0},
|
||||
{"GET login – method not allowed", fun test_login_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% ── Вспомогательная функция для создания запроса и ожидания reply ──
|
||||
prepare_req(Method, HasBody, Body) ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> Method end),
|
||||
ok = meck:expect(cowboy_req, has_body, fun(_) -> HasBody end),
|
||||
case {HasBody, Body} of
|
||||
{true, undefined} -> ok;
|
||||
{true, _} ->
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, Body, Req} end);
|
||||
{false, _} -> ok
|
||||
end,
|
||||
% Устанавливаем мок на reply, который сохраняет ответ в словаре процесса
|
||||
meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, RespBody, Req) ->
|
||||
put(test_reply, {Code, Headers, RespBody}),
|
||||
Req
|
||||
end),
|
||||
req.
|
||||
%% argon2 is a Rust NIF; rebar hooks only build it on unix. Skip hash-dependent
|
||||
%% cases when the NIF is unavailable (common on Windows without cargo).
|
||||
argon2_ready() ->
|
||||
case catch argon2:hash(<<"probe">>) of
|
||||
{ok, _} -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
%% ── Тесты ────────────────────────────────────────────────────
|
||||
require_argon2(Fun) ->
|
||||
case argon2_ready() of
|
||||
true -> Fun();
|
||||
false -> {skip, "argon2 NIF not available"}
|
||||
end.
|
||||
|
||||
test_valid_admin_login() ->
|
||||
UserMap = #{id => <<"adm1">>, email => <<"admin@test.com">>, role => <<"superadmin">>},
|
||||
ok = meck:expect(logic_auth, authenticate_admin,
|
||||
fun(<<"admin@test.com">>, <<"pass">>) -> {ok, UserMap} end),
|
||||
ok = meck:expect(core_admin, update_last_login, fun(_) -> ok end),
|
||||
ok = meck:expect(admin_utils, log_admin_action, fun(_, _, _, _, _, _) -> ok end),
|
||||
Req0 = prepare_req(<<"POST">>, true, jsx:encode(#{email => <<"admin@test.com">>, password => <<"pass">>})),
|
||||
{ok, _, _} = admin_handler_login:init(Req0, []),
|
||||
{Code, Headers, Body} = get(test_reply),
|
||||
?assertEqual(200, Code),
|
||||
?assertEqual(<<"application/json">>, maps:get(<<"content-type">>, Headers)),
|
||||
test_login_ok() ->
|
||||
require_argon2(fun() ->
|
||||
{ok, Admin} = core_admin:create(?EMAIL, ?PASSWORD, admin),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_login, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/login">>,
|
||||
auth => none,
|
||||
body => jsx:encode(#{<<"email">> => ?EMAIL, <<"password">> => ?PASSWORD})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Resp = jsx:decode(Body, [return_maps]),
|
||||
?assert(is_map_key(<<"token">>, Resp)),
|
||||
?assert(is_map_key(<<"refresh_token">>, Resp)),
|
||||
?assertEqual(<<"superadmin">>, maps:get(<<"role">>, maps:get(<<"user">>, Resp))).
|
||||
User = maps:get(<<"user">>, Resp),
|
||||
?assertEqual(Admin#admin.id, maps:get(<<"id">>, User)),
|
||||
?assertEqual(?EMAIL, maps:get(<<"email">>, User)),
|
||||
?assertEqual(<<"admin">>, maps:get(<<"role">>, User)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1)
|
||||
end).
|
||||
|
||||
test_invalid_credentials() ->
|
||||
ok = meck:expect(logic_auth, authenticate_admin,
|
||||
fun(_, _) -> {error, bad_credentials} end),
|
||||
Req0 = prepare_req(<<"POST">>, true, jsx:encode(#{email => <<"bad@test.com">>, password => <<"wrong">>})),
|
||||
{ok, _, _} = admin_handler_login:init(Req0, []),
|
||||
{Code, _, Body} = get(test_reply),
|
||||
?assertEqual(401, Code),
|
||||
#{<<"error">> := <<"bad_credentials">>} = jsx:decode(Body, [return_maps]).
|
||||
test_login_bad_credentials() ->
|
||||
%% Unknown email does not need password verification.
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_login, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/login">>,
|
||||
auth => none,
|
||||
body => jsx:encode(#{<<"email">> => <<"nobody@test.local">>, <<"password">> => <<"x">>})
|
||||
}),
|
||||
?assertEqual(401, Status),
|
||||
#{<<"error">> := <<"invalid_credentials">>} = jsx:decode(Body, [return_maps]).
|
||||
|
||||
test_insufficient_permissions() ->
|
||||
UserMap = #{id => <<"user1">>, email => <<"user@test.com">>, role => <<"user">>},
|
||||
ok = meck:expect(logic_auth, authenticate_admin,
|
||||
fun(_, _) -> {ok, UserMap} end),
|
||||
Req0 = prepare_req(<<"POST">>, true, jsx:encode(#{email => <<"user@test.com">>, password => <<"pass">>})),
|
||||
{ok, _, _} = admin_handler_login:init(Req0, []),
|
||||
{Code, _, Body} = get(test_reply),
|
||||
?assertEqual(403, Code),
|
||||
#{<<"error">> := <<"insufficient_permissions">>} = jsx:decode(Body, [return_maps]).
|
||||
test_login_forbidden_role() ->
|
||||
require_argon2(fun() ->
|
||||
{ok, Hash} = logic_auth:hash_password(?PASSWORD),
|
||||
eh_test_support:seed_admin(#{
|
||||
id => <<"adm_guest">>,
|
||||
email => <<"guest@test.local">>,
|
||||
password_hash => Hash,
|
||||
role => guest
|
||||
}),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_login, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/login">>,
|
||||
auth => none,
|
||||
body => jsx:encode(#{
|
||||
<<"email">> => <<"guest@test.local">>,
|
||||
<<"password">> => ?PASSWORD
|
||||
})
|
||||
}),
|
||||
?assertEqual(403, Status),
|
||||
#{<<"error">> := <<"insufficient_permissions">>} = jsx:decode(Body, [return_maps])
|
||||
end).
|
||||
|
||||
test_malformed_json() ->
|
||||
Req0 = prepare_req(<<"POST">>, true, <<"not a json">>),
|
||||
{ok, _, _} = admin_handler_login:init(Req0, []),
|
||||
{Code, _, Body} = get(test_reply),
|
||||
?assertEqual(400, Code),
|
||||
#{<<"error">> := <<"Invalid JSON">>} = jsx:decode(Body, [return_maps]).
|
||||
test_login_missing_fields() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_login, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/login">>,
|
||||
auth => none,
|
||||
body => jsx:encode(#{<<"email">> => ?EMAIL})
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_missing_body() ->
|
||||
Req0 = prepare_req(<<"POST">>, false, undefined),
|
||||
{ok, _, _} = admin_handler_login:init(Req0, []),
|
||||
{Code, _, Body} = get(test_reply),
|
||||
?assertEqual(400, Code),
|
||||
#{<<"error">> := <<"Missing request body">>} = jsx:decode(Body, [return_maps]).
|
||||
test_login_missing_body() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_login, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/login">>,
|
||||
auth => none,
|
||||
has_body => false
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
Req0 = prepare_req(<<"GET">>, false, undefined),
|
||||
{ok, _, _} = admin_handler_login:init(Req0, []),
|
||||
{Code, _, Body} = get(test_reply),
|
||||
?assertEqual(405, Code),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(Body, [return_maps]).
|
||||
test_login_invalid_json() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_login, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/login">>,
|
||||
auth => none,
|
||||
body => <<"not-json">>
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_login_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_login, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/login">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
-module(admin_handler_me_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{
|
||||
id => ?ADMIN_ID,
|
||||
role => admin,
|
||||
nickname => <<"me-admin">>,
|
||||
email => <<"me@test.local">>
|
||||
}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_me_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – success", {timeout, 60, fun test_get/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"PUT – update profile", {timeout, 60, fun test_update/0}},
|
||||
{"PUT – unauthorized", {timeout, 60, fun test_update_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
test_get() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_me, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/me">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(?ADMIN_ID, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"me@test.local">>, maps:get(<<"email">>, Result)),
|
||||
?assertEqual(<<"me-admin">>, maps:get(<<"nickname">>, Result)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_me, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/me">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_update() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_me, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/me">>,
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{
|
||||
<<"nickname">> => <<"new-nick">>,
|
||||
<<"timezone">> => <<"Europe/Moscow">>,
|
||||
<<"language">> => <<"en">>
|
||||
})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"new-nick">>, maps:get(<<"nickname">>, Result)),
|
||||
?assertEqual(<<"Europe/Moscow">>, maps:get(<<"timezone">>, Result)),
|
||||
?assertEqual(<<"en">>, maps:get(<<"language">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
test_update_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_me, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/me">>,
|
||||
auth => none,
|
||||
body => jsx:encode(#{<<"nickname">> => <<"x">>})
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_me, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/me">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -2,188 +2,114 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, user, calendar, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(USER_ID, <<"user_mod_1">>).
|
||||
|
||||
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_calendar, [non_strict]),
|
||||
ok = meck:new(core_event, [non_strict]),
|
||||
ok = meck:new(core_review, [non_strict]),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => ?USER_ID,
|
||||
email => <<"user@test.local">>,
|
||||
status => active
|
||||
})),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_review),
|
||||
meck:unload(core_event),
|
||||
meck:unload(core_calendar),
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_moderation_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"Freeze calendar – success", fun test_freeze_calendar/0},
|
||||
{"Freeze calendar – not found", fun test_freeze_calendar_not_found/0},
|
||||
{"Unfreeze calendar – success", fun test_unfreeze_calendar/0},
|
||||
{"Freeze event – success", fun test_freeze_event/0},
|
||||
{"Unfreeze event – success", fun test_unfreeze_event/0},
|
||||
{"Hide review – success", fun test_hide_review/0},
|
||||
{"Show review – success", fun test_show_review/0},
|
||||
{"Block user – success", fun test_block_user/0},
|
||||
{"Unblock user – success", fun test_unblock_user/0},
|
||||
{"Invalid target type", fun test_invalid_target/0},
|
||||
{"Invalid action", fun test_invalid_action/0},
|
||||
{"Missing action field", fun test_missing_action/0},
|
||||
{"Forbidden – non admin", fun test_forbidden/0},
|
||||
{"Wrong method – POST", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"PUT block user – success", fun test_block_user/0},
|
||||
{"PUT block user – not found", fun test_block_user_not_found/0},
|
||||
{"PUT freeze calendar – success", fun test_freeze_calendar/0},
|
||||
{"PUT – missing action", fun test_missing_action/0},
|
||||
{"PUT – invalid target", fun test_invalid_target/0},
|
||||
{"PUT – unauthorized", fun test_unauthorized/0},
|
||||
{"GET – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% ── Вспомогательные функции ──────────────────────────────
|
||||
prepare_req(TargetType, TargetId, Action) ->
|
||||
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, binding,
|
||||
fun(target_type, _) -> TargetType;
|
||||
(id, _) -> TargetId
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"action">> => Action}), Req} end).
|
||||
|
||||
%% ── Календари ───────────────────────────────────────────
|
||||
test_freeze_calendar() ->
|
||||
prepare_req(<<"calendar">>, <<"c1">>, <<"freeze">>),
|
||||
Frozen = #calendar{id = <<"c1">>, title = <<"Test">>, status = frozen},
|
||||
ok = meck:expect(core_calendar, freeze, fun(<<"c1">>) -> {ok, Frozen} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_block_user() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_moderation, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/user/", ?USER_ID/binary>>,
|
||||
bindings => #{target_type => <<"user">>, id => ?USER_ID},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"action">> => <<"block">>, <<"reason">> => <<"spam">>})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"frozen">>} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(?USER_ID, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"blocked">>, maps:get(<<"status">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
test_freeze_calendar_not_found() ->
|
||||
prepare_req(<<"calendar">>, <<"c99">>, <<"freeze">>),
|
||||
ok = meck:expect(core_calendar, freeze, fun(_) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
test_block_user_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_moderation, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/user/missing">>,
|
||||
bindings => #{target_type => <<"user">>, id => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"action">> => <<"block">>, <<"reason">> => <<"x">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_unfreeze_calendar() ->
|
||||
prepare_req(<<"calendar">>, <<"c1">>, <<"unfreeze">>),
|
||||
Unfrozen = #calendar{id = <<"c1">>, title = <<"Test">>, status = active},
|
||||
ok = meck:expect(core_calendar, unfreeze, fun(<<"c1">>) -> {ok, Unfrozen} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_freeze_calendar() ->
|
||||
{ok, Cal} = core_calendar:create(?USER_ID, <<"Cal">>, <<"desc">>, auto),
|
||||
CalId = Cal#calendar.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_moderation, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/calendar/", CalId/binary>>,
|
||||
bindings => #{target_type => <<"calendar">>, id => CalId},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"action">> => <<"freeze">>, <<"reason">> => <<"policy">>})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"active">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
%% ── События ─────────────────────────────────────────────
|
||||
test_freeze_event() ->
|
||||
prepare_req(<<"event">>, <<"e1">>, <<"freeze">>),
|
||||
FrozenE = #event{id = <<"e1">>, title = <<"Event1">>, status = frozen},
|
||||
ok = meck:expect(core_event, freeze, fun(<<"e1">>) -> {ok, FrozenE} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"frozen">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
test_unfreeze_event() ->
|
||||
prepare_req(<<"event">>, <<"e1">>, <<"unfreeze">>),
|
||||
UnfrozenE = #event{id = <<"e1">>, title = <<"Event1">>, status = active},
|
||||
ok = meck:expect(core_event, unfreeze, fun(<<"e1">>) -> {ok, UnfrozenE} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"active">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
%% ── Отзывы ──────────────────────────────────────────────
|
||||
test_hide_review() ->
|
||||
prepare_req(<<"review">>, <<"r1">>, <<"hide">>),
|
||||
Hidden = #review{id = <<"r1">>, status = hidden},
|
||||
ok = meck:expect(core_review, hide, fun(<<"r1">>) -> {ok, Hidden} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"hidden">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
test_show_review() ->
|
||||
prepare_req(<<"review">>, <<"r1">>, <<"show">>),
|
||||
Visible = #review{id = <<"r1">>, status = active},
|
||||
ok = meck:expect(core_review, show, fun(<<"r1">>) -> {ok, Visible} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"active">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
%% ── Пользователи ────────────────────────────────────────
|
||||
test_block_user() ->
|
||||
prepare_req(<<"user">>, <<"u1">>, <<"block">>),
|
||||
Blocked = #user{id = <<"u1">>, email = <<"user@test.com">>, status = frozen},
|
||||
ok = meck:expect(core_user, block, fun(<<"u1">>) -> {ok, Blocked} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"frozen">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
test_unblock_user() ->
|
||||
prepare_req(<<"user">>, <<"u1">>, <<"unblock">>),
|
||||
Unblocked = #user{id = <<"u1">>, email = <<"user@test.com">>, status = active},
|
||||
ok = meck:expect(core_user, unblock, fun(<<"u1">>) -> {ok, Unblocked} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"active">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
%% ── Ошибки ──────────────────────────────────────────────
|
||||
test_invalid_target() ->
|
||||
prepare_req(<<"bad_type">>, <<"x">>, <<"freeze">>),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_invalid_action() ->
|
||||
prepare_req(<<"calendar">>, <<"c1">>, <<"delete">>),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(400, Status).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(CalId, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"frozen">>, maps:get(<<"status">>, Result)).
|
||||
|
||||
test_missing_action() ->
|
||||
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, binding,
|
||||
fun(target_type, _) -> <<"calendar">>;
|
||||
(id, _) -> <<"c1">>
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"other">> => <<"data">>}), Req} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_moderation, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/user/", ?USER_ID/binary>>,
|
||||
bindings => #{target_type => <<"user">>, id => ?USER_ID},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"reason">> => <<"x">>})
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_forbidden() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"PUT">> end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {error, 403, <<"Admin access required">>, Req} end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(403, Status).
|
||||
test_invalid_target() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_moderation, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/unknown/x">>,
|
||||
bindings => #{target_type => <<"unknown">>, id => <<"x">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"action">> => <<"block">>})
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_moderation, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/user/", ?USER_ID/binary>>,
|
||||
bindings => #{target_type => <<"user">>, id => ?USER_ID},
|
||||
auth => none,
|
||||
body => jsx:encode(#{<<"action">> => <<"block">>})
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_moderation:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_moderation, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/user/", ?USER_ID/binary>>,
|
||||
bindings => #{target_type => <<"user">>, id => ?USER_ID},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
-module(admin_handler_node_metrics_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, node_metric, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_node_metrics_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – empty range", {timeout, 60, fun test_empty/0}},
|
||||
{"GET – with seeded metric", {timeout, 60, fun test_with_data/0}},
|
||||
{"GET – missing from", {timeout, 60, fun test_missing_from/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
range_qs() ->
|
||||
From = handler_utils:datetime_to_iso8601(eh_test_support:days_from_now(-1)),
|
||||
To = handler_utils:datetime_to_iso8601(eh_test_support:days_from_now(1)),
|
||||
[{<<"from">>, From}, {<<"to">>, To}].
|
||||
|
||||
seed_metric() ->
|
||||
Now = calendar:universal_time(),
|
||||
Metric = #node_metric{
|
||||
timestamp = Now,
|
||||
node = node(),
|
||||
memory_total = 1000,
|
||||
memory_processes = 100,
|
||||
memory_atom = 10,
|
||||
memory_binary = 20,
|
||||
memory_ets = 30,
|
||||
process_count = 5,
|
||||
run_queue = 0,
|
||||
mnesia_commits = 1,
|
||||
mnesia_failures = 0,
|
||||
table_sizes = #{},
|
||||
active_sessions = 0,
|
||||
ws_connections = 0,
|
||||
io_in = 0,
|
||||
io_out = 0,
|
||||
memory_available = 500,
|
||||
cpu_utilization = 1.5
|
||||
},
|
||||
ok = core_node_metric:save(Metric),
|
||||
Metric.
|
||||
|
||||
test_empty() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_node_metrics, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/nodes/metrics">>,
|
||||
qs => range_qs(),
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual([], Result).
|
||||
|
||||
test_with_data() ->
|
||||
_ = seed_metric(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_node_metrics, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/nodes/metrics">>,
|
||||
qs => range_qs(),
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, length(Result)),
|
||||
[M] = Result,
|
||||
?assertEqual(1000, maps:get(<<"memory_total">>, M)),
|
||||
?assertEqual(5, maps:get(<<"process_count">>, M)).
|
||||
|
||||
test_missing_from() ->
|
||||
To = handler_utils:datetime_to_iso8601(eh_test_support:days_from_now(1)),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_node_metrics, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/nodes/metrics">>,
|
||||
qs => [{<<"to">>, To}],
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_node_metrics, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/nodes/metrics">>,
|
||||
qs => range_qs(),
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_node_metrics, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/nodes/metrics">>,
|
||||
qs => range_qs(),
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,83 @@
|
||||
-module(admin_handler_refresh_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, auth_session, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
_ = mnesia:add_table_index(auth_session, #auth_session.family_id),
|
||||
_ = mnesia:add_table_index(auth_session, #auth_session.subject_id),
|
||||
eh_test_support:ensure_jwt(),
|
||||
catch ets:new(eventhub_counters, [named_table, public, set, {write_concurrency, true}]),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID, role => admin}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_refresh_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"POST refresh – success", {timeout, 60, fun test_refresh_ok/0}},
|
||||
{"POST refresh – invalid token", {timeout, 60, fun test_refresh_invalid/0}},
|
||||
{"POST refresh – missing field", {timeout, 60, fun test_refresh_missing_field/0}},
|
||||
{"POST refresh – invalid JSON", {timeout, 60, fun test_refresh_invalid_json/0}},
|
||||
{"GET refresh – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
test_refresh_ok() ->
|
||||
{ok, _Access, Refresh} = logic_auth_session:issue_admin_tokens(?ADMIN_ID, <<"admin">>),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_refresh, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/refresh">>,
|
||||
auth => none,
|
||||
body => jsx:encode(#{<<"refresh_token">> => Refresh})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Resp = jsx:decode(Body, [return_maps]),
|
||||
?assert(is_map_key(<<"token">>, Resp)),
|
||||
?assert(is_map_key(<<"refresh_token">>, Resp)),
|
||||
NewRefresh = maps:get(<<"refresh_token">>, Resp),
|
||||
?assertNotEqual(Refresh, NewRefresh),
|
||||
{ok, _, _} = eventhub_auth:verify_admin_token(maps:get(<<"token">>, Resp)).
|
||||
|
||||
test_refresh_invalid() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_refresh, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/refresh">>,
|
||||
auth => none,
|
||||
body => jsx:encode(#{<<"refresh_token">> => <<"not-a-valid-token">>})
|
||||
}),
|
||||
?assertEqual(401, Status),
|
||||
#{<<"error">> := _} = jsx:decode(Body, [return_maps]).
|
||||
|
||||
test_refresh_missing_field() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_refresh, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/refresh">>,
|
||||
auth => none,
|
||||
body => jsx:encode(#{<<"token">> => <<"x">>})
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_refresh_invalid_json() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_refresh, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/refresh">>,
|
||||
auth => none,
|
||||
body => <<"not-json">>
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_refresh, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/refresh">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -2,144 +2,112 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, report, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
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_report, [non_strict]),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_report),
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_report_by_id_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/reports/:id – success", fun test_get_report/0},
|
||||
{"GET /admin/reports/:id – not found", fun test_get_report_not_found/0},
|
||||
{"GET /admin/reports/:id – forbidden", fun test_get_report_forbidden/0},
|
||||
{"PUT /admin/reports/:id – success", fun test_update_report/0},
|
||||
{"PUT /admin/reports/:id – not found", fun test_update_report_not_found/0},
|
||||
{"PUT /admin/reports/:id – bad JSON", fun test_update_report_bad_json/0},
|
||||
{"DELETE /admin/reports/:id – method not allowed", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – success", fun test_get/0},
|
||||
{"GET – not found", fun test_get_not_found/0},
|
||||
{"PUT – update status", fun test_update/0},
|
||||
{"PUT – missing status", fun test_update_missing/0},
|
||||
{"PUT – not found", fun test_update_not_found/0},
|
||||
{"GET – unauthorized", fun test_unauthorized/0},
|
||||
{"PATCH – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% GET – успех
|
||||
test_get_report() ->
|
||||
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),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"r1">> end),
|
||||
Report = #report{
|
||||
id = <<"r1">>,
|
||||
reporter_id = <<"u1">>,
|
||||
target_type = <<"event">>,
|
||||
target_id = <<"e1">>,
|
||||
reason = <<"spam">>,
|
||||
status = pending,
|
||||
created_at = {{2026,4,26},{12,0,0}},
|
||||
resolved_at = undefined
|
||||
},
|
||||
ok = meck:expect(core_report, get_by_id,
|
||||
fun(<<"r1">>) -> {ok, Report} end),
|
||||
{ok, _, _} = admin_handler_report_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"id">> := <<"r1">>, <<"status">> := <<"pending">>} = jsx:decode(RespBody, [return_maps]).
|
||||
seed_report() ->
|
||||
{ok, Report} = core_report:create(<<"u1">>, calendar, <<"cal1">>, <<"spam">>),
|
||||
Report.
|
||||
|
||||
%% GET – не найдено
|
||||
test_get_report_not_found() ->
|
||||
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),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"r99">> end),
|
||||
ok = meck:expect(core_report, get_by_id,
|
||||
fun(_) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_report_by_id:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
test_get() ->
|
||||
Report = seed_report(),
|
||||
Id = Report#report.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_report_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reports/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(Id, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"spam">>, maps:get(<<"reason">>, Result)).
|
||||
|
||||
test_get_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_report_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reports/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% GET – запрещён
|
||||
test_get_report_forbidden() ->
|
||||
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_report_by_id:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(403, Status).
|
||||
|
||||
%% PUT – успех
|
||||
test_update_report() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"r1">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"status">> => <<"reviewed">>}), Req} end),
|
||||
Updated = #report{id = <<"r1">>, status = reviewed},
|
||||
ok = meck:expect(core_report, update_status,
|
||||
fun(<<"r1">>, reviewed, <<"adm1">>) -> {ok, Updated} end),
|
||||
{ok, _, _} = admin_handler_report_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_update() ->
|
||||
Report = seed_report(),
|
||||
Id = Report#report.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_report_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/reports/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"status">> => <<"reviewed">>})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"reviewed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"reviewed">>, maps:get(<<"status">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
%% PUT – не найдено
|
||||
test_update_report_not_found() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"r99">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"status">> => <<"reviewed">>}), Req} end),
|
||||
ok = meck:expect(core_report, update_status,
|
||||
fun(<<"r99">>, reviewed, <<"adm1">>) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_report_by_id:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% PUT – невалидный JSON
|
||||
test_update_report_bad_json() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"r1">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, <<"bad json">>, Req} end),
|
||||
{ok, _, _} = admin_handler_report_by_id:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
test_update_missing() ->
|
||||
Report = seed_report(),
|
||||
Id = Report#report.id,
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_report_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/reports/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"other">> => <<"x">>})
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
%% Неверный метод
|
||||
test_update_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_report_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/reports/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"status">> => <<"dismissed">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_report_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reports/x">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"DELETE">> end),
|
||||
{ok, _, _} = admin_handler_report_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_report_by_id, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/reports/x">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
-module(admin_handler_report_stats_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, report, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_report_stats_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET stats – empty", {timeout, 60, fun test_stats_empty/0}},
|
||||
{"GET stats – with reports", {timeout, 60, fun test_stats_with_data/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
test_stats_empty() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_report_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reports/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(0, maps:get(<<"total_reports">>, Result)),
|
||||
?assertEqual(#{}, maps:get(<<"reports_by_target_type">>, Result)),
|
||||
?assertEqual(#{}, maps:get(<<"reports_by_status">>, Result)),
|
||||
?assertEqual([], maps:get(<<"top_targets_by_reports">>, Result)).
|
||||
|
||||
test_stats_with_data() ->
|
||||
{ok, _} = core_report:create(<<"u1">>, calendar, <<"cal1">>, <<"spam">>),
|
||||
{ok, _} = core_report:create(<<"u2">>, event, <<"ev1">>, <<"abuse">>),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_report_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reports/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, maps:get(<<"total_reports">>, Result)),
|
||||
ByType = maps:get(<<"reports_by_target_type">>, Result),
|
||||
?assertEqual(1, maps:get(<<"calendar">>, ByType)),
|
||||
?assertEqual(1, maps:get(<<"event">>, ByType)),
|
||||
ByStatus = maps:get(<<"reports_by_status">>, Result),
|
||||
?assertEqual(2, maps:get(<<"pending">>, ByStatus)),
|
||||
?assert(length(maps:get(<<"top_targets_by_reports">>, Result)) >= 1).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_report_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reports/stats">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_report_stats, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/reports/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -2,127 +2,70 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, report, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
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_report, [non_strict]),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_report),
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_reports_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/reports – success", fun test_list_reports/0},
|
||||
{"GET /admin/reports – forbidden", fun test_list_reports_forbidden/0},
|
||||
{"PUT /admin/reports/:id – success", fun test_update_report/0},
|
||||
{"PUT /admin/reports/:id – missing status", fun test_update_report_bad_json/0},
|
||||
{"PUT /admin/reports/:id – not found", fun test_update_report_not_found/0},
|
||||
{"POST /admin/reports – method not allowed", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", fun test_list/0},
|
||||
{"GET list – filter by status", fun test_list_filter/0},
|
||||
{"GET list – unauthorized", fun test_list_unauthorized/0},
|
||||
{"POST – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% GET – успех
|
||||
test_list_reports() ->
|
||||
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),
|
||||
Report = #report{
|
||||
id = <<"r1">>,
|
||||
reporter_id = <<"u1">>,
|
||||
target_type = <<"event">>,
|
||||
target_id = <<"e1">>,
|
||||
reason = <<"spam">>,
|
||||
status = pending,
|
||||
created_at = {{2026,4,26},{12,0,0}},
|
||||
resolved_at = undefined
|
||||
},
|
||||
% list_all возвращает {ok, List}
|
||||
ok = meck:expect(core_report, list_all, fun() -> {ok, [Report]} end),
|
||||
{ok, _, _} = admin_handler_reports:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_list() ->
|
||||
{ok, _} = core_report:create(<<"u1">>, calendar, <<"cal1">>, <<"spam">>),
|
||||
{ok, _} = core_report:create(<<"u2">>, event, <<"ev1">>, <<"abuse">>),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_reports, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reports">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
[#{<<"id">> := <<"r1">>, <<"status">> := <<"pending">>}] = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, length(Result)).
|
||||
|
||||
%% GET – запрещён
|
||||
test_list_reports_forbidden() ->
|
||||
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_reports:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(403, Status),
|
||||
#{<<"error">> := <<"Admin access required">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
%% PUT – успех
|
||||
test_update_report() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"r1">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"status">> => <<"reviewed">>}), Req} end),
|
||||
Updated = #report{id = <<"r1">>, status = reviewed},
|
||||
% обработчик передаёт бинарный статус, поэтому мок ожидает строку
|
||||
ok = meck:expect(core_report, update_status,
|
||||
fun(<<"r1">>, <<"reviewed">>, <<"adm1">>) -> {ok, Updated} end),
|
||||
{ok, _, _} = admin_handler_reports:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_list_filter() ->
|
||||
{ok, _} = core_report:create(<<"u1">>, calendar, <<"cal1">>, <<"spam">>),
|
||||
{ok, R} = core_report:create(<<"u2">>, event, <<"ev1">>, <<"abuse">>),
|
||||
{ok, _} = core_report:update_status(R#report.id, reviewed, ?ADMIN_ID),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_reports, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reports">>,
|
||||
auth => ?ADMIN_ID,
|
||||
qs => [{<<"status">>, <<"pending">>}]
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"reviewed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, length(Result)),
|
||||
[Only] = Result,
|
||||
?assertEqual(<<"pending">>, maps:get(<<"status">>, Only)).
|
||||
|
||||
%% PUT – невалидный JSON
|
||||
test_update_report_bad_json() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"r1">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, <<"bad json">>, Req} end),
|
||||
{ok, _, _} = admin_handler_reports:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(400, Status).
|
||||
test_list_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reports, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reports">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
%% PUT – не найдено
|
||||
test_update_report_not_found() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"r99">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"status">> => <<"reviewed">>}), Req} end),
|
||||
ok = meck:expect(core_report, update_status,
|
||||
fun(<<"r99">>, <<"reviewed">>, <<"adm1">>) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_reports:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% Неправильный метод
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
{ok, _, _} = admin_handler_reports:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reports, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/reports">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
-module(admin_handler_reviews_by_id_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, review, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_reviews_by_id_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – success", {timeout, 60, fun test_get/0}},
|
||||
{"GET – not found", {timeout, 60, fun test_get_not_found/0}},
|
||||
{"PUT – update status", {timeout, 60, fun test_update/0}},
|
||||
{"PUT – not found", {timeout, 60, fun test_update_not_found/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"PATCH – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
seed_review() ->
|
||||
{ok, Review} = core_review:create(<<"u1">>, calendar, <<"cal1">>, 5, <<"great">>),
|
||||
Review.
|
||||
|
||||
test_get() ->
|
||||
Review = seed_review(),
|
||||
Id = Review#review.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_reviews_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reviews/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(Id, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"great">>, maps:get(<<"comment">>, Result)).
|
||||
|
||||
test_get_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reviews_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reviews/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_update() ->
|
||||
Review = seed_review(),
|
||||
Id = Review#review.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_reviews_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/reviews/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{
|
||||
<<"status">> => <<"hidden">>,
|
||||
<<"reason">> => <<"moderation">>
|
||||
})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"hidden">>, maps:get(<<"status">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
test_update_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reviews_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/reviews/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"status">> => <<"hidden">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reviews_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reviews/x">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reviews_by_id, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/reviews/x">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,76 @@
|
||||
-module(admin_handler_reviews_stats_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, review, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_reviews_stats_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET stats – empty", {timeout, 60, fun test_stats_empty/0}},
|
||||
{"GET stats – with reviews", {timeout, 60, fun test_stats_with_data/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
test_stats_empty() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_reviews_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reviews/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(0, maps:get(<<"total_reviews">>, Result)),
|
||||
?assertEqual(#{}, maps:get(<<"reviews_by_target_type">>, Result)),
|
||||
?assertEqual(#{}, maps:get(<<"reviews_by_status">>, Result)),
|
||||
?assertEqual([], maps:get(<<"top_targets_by_reviews">>, Result)).
|
||||
|
||||
test_stats_with_data() ->
|
||||
{ok, _} = core_review:create(<<"u1">>, calendar, <<"cal1">>, 5, <<"great">>),
|
||||
{ok, _} = core_review:create(<<"u2">>, event, <<"ev1">>, 1, <<"bad">>),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_reviews_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reviews/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, maps:get(<<"total_reviews">>, Result)),
|
||||
ByType = maps:get(<<"reviews_by_target_type">>, Result),
|
||||
?assertEqual(1, maps:get(<<"calendar">>, ByType)),
|
||||
?assertEqual(1, maps:get(<<"event">>, ByType)),
|
||||
ByStatus = maps:get(<<"reviews_by_status">>, Result),
|
||||
?assertEqual(2, maps:get(<<"visible">>, ByStatus)),
|
||||
?assert(length(maps:get(<<"top_targets_by_reviews">>, Result)) >= 1),
|
||||
?assert(length(maps:get(<<"top_targets_by_positive_reviews">>, Result)) >= 1),
|
||||
?assert(length(maps:get(<<"top_targets_by_negative_reviews">>, Result)) >= 1).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reviews_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reviews/stats">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reviews_stats, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/reviews/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -2,145 +2,85 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, review, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
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_review, [non_strict]),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_review),
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_reviews_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/reviews/:id – success", fun test_get_review/0},
|
||||
{"GET /admin/reviews/:id – not found", fun test_get_review_not_found/0},
|
||||
{"GET /admin/reviews/:id – forbidden", fun test_get_review_forbidden/0},
|
||||
{"PUT /admin/reviews/:id – success", fun test_update_review/0},
|
||||
{"PUT /admin/reviews/:id – not found", fun test_update_review_not_found/0},
|
||||
{"PUT /admin/reviews/:id – bad JSON", fun test_update_review_bad_json/0},
|
||||
{"DELETE /admin/reviews/:id – method not allowed", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", fun test_list/0},
|
||||
{"PATCH bulk – success", fun test_bulk/0},
|
||||
{"PATCH bulk – missing reason", fun test_bulk_missing/0},
|
||||
{"GET – unauthorized", fun test_unauthorized/0},
|
||||
{"POST – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% GET – успех
|
||||
test_get_review() ->
|
||||
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),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"rv1">> end),
|
||||
Review = #review{
|
||||
id = <<"rv1">>,
|
||||
user_id = <<"u1">>,
|
||||
target_type = <<"event">>,
|
||||
target_id = <<"e1">>,
|
||||
rating = 5,
|
||||
comment = <<"Great!">>,
|
||||
status = <<"active">>,
|
||||
created_at = {{2026,4,26},{12,0,0}},
|
||||
updated_at = {{2026,4,26},{12,0,0}}
|
||||
},
|
||||
ok = meck:expect(core_review, get_by_id,
|
||||
fun(<<"rv1">>) -> {ok, Review} end),
|
||||
{ok, _, _} = admin_handler_reviews:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
seed_review() ->
|
||||
{ok, Review} = core_review:create(<<"u1">>, calendar, <<"cal1">>, 5, <<"great">>),
|
||||
Review.
|
||||
|
||||
test_list() ->
|
||||
_ = seed_review(),
|
||||
{ok, _} = core_review:create(<<"u2">>, event, <<"ev1">>, 3, <<"ok">>),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_reviews, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reviews">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"id">> := <<"rv1">>, <<"comment">> := <<"Great!">>, <<"rating">> := 5} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, length(Result)).
|
||||
|
||||
%% GET – не найдено
|
||||
test_get_review_not_found() ->
|
||||
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),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"rv99">> end),
|
||||
ok = meck:expect(core_review, get_by_id,
|
||||
fun(_) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_reviews:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% GET – запрещён
|
||||
test_get_review_forbidden() ->
|
||||
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_reviews:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(403, Status).
|
||||
|
||||
%% PUT – успех
|
||||
test_update_review() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"rv1">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"status">> => <<"hidden">>}), Req} end),
|
||||
Updated = #review{id = <<"rv1">>, status = <<"hidden">>},
|
||||
ok = meck:expect(core_review, update_status,
|
||||
fun(<<"rv1">>, <<"hidden">>) -> {ok, Updated} end),
|
||||
{ok, _, _} = admin_handler_reviews:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_bulk() ->
|
||||
Review = seed_review(),
|
||||
Id = Review#review.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_reviews, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/reviews">>,
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{
|
||||
<<"operations">> => [#{<<"id">> => Id, <<"status">> => <<"hidden">>}],
|
||||
<<"reason">> => <<"moderation">>
|
||||
})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"hidden">>} = jsx:decode(RespBody, [return_maps]).
|
||||
#{<<"updated_count">> := 1} = jsx:decode(Body, [return_maps]),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
%% PUT – не найдено
|
||||
test_update_review_not_found() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"rv99">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"status">> => <<"hidden">>}), Req} end),
|
||||
ok = meck:expect(core_review, update_status,
|
||||
fun(_, _) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_reviews:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% PUT – невалидный JSON
|
||||
test_update_review_bad_json() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"rv1">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, <<"bad json">>, Req} end),
|
||||
{ok, _, _} = admin_handler_reviews:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
test_bulk_missing() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reviews, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/reviews">>,
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"operations">> => []})
|
||||
}),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
%% Неверный метод
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reviews, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/reviews">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"DELETE">> end),
|
||||
{ok, _, _} = admin_handler_reviews:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_reviews, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/reviews">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -2,101 +2,75 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, user, event, calendar, review, report,
|
||||
ticket, subscription]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
ok = meck:new(cowboy_req, [non_strict]),
|
||||
ok = meck:new(handler_auth, [non_strict]),
|
||||
ok = meck:new(admin_utils, [non_strict]),
|
||||
ok = meck:new(core_admin, [non_strict]),
|
||||
ok = meck:new(logic_stats, [non_strict]),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, _, _, _) -> put(test_reply, Code) end),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID, role => admin}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(logic_stats),
|
||||
meck:unload(core_admin),
|
||||
meck:unload(admin_utils),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_stats_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/stats as superadmin returns 200 with system metrics", fun test_superadmin/0},
|
||||
{"GET /admin/stats as superadmin with date filter", fun test_superadmin_dates/0},
|
||||
{"GET /admin/stats as moderator returns 200 with own metrics", fun test_moderator/0},
|
||||
{"GET /admin/stats as support returns 200 with assigned tickets", fun test_support/0},
|
||||
{"GET /admin/stats with non‑admin token returns 403", fun test_forbidden/0},
|
||||
{"POST /admin/stats returns 405", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET stats – success empty", fun test_stats_empty/0},
|
||||
{"GET stats – with seeded data", fun test_stats_with_data/0},
|
||||
{"GET – unauthorized", fun test_unauthorized/0},
|
||||
{"POST – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% --- Суперадмин (без дат) ---
|
||||
test_superadmin() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(cowboy_req, parse_qs, fun(_) -> [] end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"adm1">>, Req} end),
|
||||
ok = meck:expect(admin_utils, is_admin, fun(_) -> true end),
|
||||
ok = meck:expect(core_admin, get_by_id,
|
||||
fun(<<"adm1">>) -> {ok, #admin{id = <<"adm1">>, role = superadmin}} end),
|
||||
ok = meck:expect(logic_stats, get_stats,
|
||||
fun(superadmin, _) -> #{users => 10, events => 25} end),
|
||||
{ok, _, _} = admin_handler_stats:init(req, []),
|
||||
?assertEqual(200, erase(test_reply)).
|
||||
test_stats_empty() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(0, maps:get(<<"users_total">>, Result)),
|
||||
?assertEqual(0, maps:get(<<"events_total">>, Result)),
|
||||
?assertEqual(0, maps:get(<<"tickets_total">>, Result)),
|
||||
?assert(is_map_key(<<"registrations_by_day">>, Result)).
|
||||
|
||||
%% --- Суперадмин (с датами) ---
|
||||
test_superadmin_dates() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(cowboy_req, parse_qs, fun(_) ->
|
||||
[{<<"from">>, <<"2026-01-01T00:00:00">>}, {<<"to">>, <<"2026-06-01T00:00:00">>}]
|
||||
end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"adm1">>, Req} end),
|
||||
ok = meck:expect(admin_utils, is_admin, fun(_) -> true end),
|
||||
ok = meck:expect(core_admin, get_by_id,
|
||||
fun(<<"adm1">>) -> {ok, #admin{id = <<"adm1">>, role = superadmin}} end),
|
||||
ok = meck:expect(logic_stats, get_stats,
|
||||
fun(superadmin, _, _, _) -> #{users => 10} end),
|
||||
{ok, _, _} = admin_handler_stats:init(req, []),
|
||||
?assertEqual(200, erase(test_reply)).
|
||||
test_stats_with_data() ->
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => <<"u1">>,
|
||||
email => <<"u1@test.local">>,
|
||||
status => active
|
||||
})),
|
||||
{ok, _} = core_report:create(<<"u1">>, calendar, <<"c1">>, <<"spam">>),
|
||||
{ok, _} = core_ticket:create(<<"u1">>, <<"hash1">>, <<"boom">>, <<"stack">>),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, maps:get(<<"users_total">>, Result)),
|
||||
?assertEqual(1, maps:get(<<"reports_total">>, Result)),
|
||||
?assertEqual(1, maps:get(<<"tickets_total">>, Result)).
|
||||
|
||||
%% --- Модератор ---
|
||||
test_moderator() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(cowboy_req, parse_qs, fun(_) -> [] end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"mod1">>, Req} end),
|
||||
ok = meck:expect(admin_utils, is_admin, fun(_) -> true end),
|
||||
ok = meck:expect(core_admin, get_by_id,
|
||||
fun(<<"mod1">>) -> {ok, #admin{id = <<"mod1">>, role = moderator}} end),
|
||||
ok = meck:expect(logic_stats, get_stats,
|
||||
fun(moderator, _) -> #{reports_reviewed => 5} end),
|
||||
{ok, _, _} = admin_handler_stats:init(req, []),
|
||||
?assertEqual(200, erase(test_reply)).
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/stats">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
%% --- Поддержка ---
|
||||
test_support() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(cowboy_req, parse_qs, fun(_) -> [] end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"sup1">>, Req} end),
|
||||
ok = meck:expect(admin_utils, is_admin, fun(_) -> true end),
|
||||
ok = meck:expect(core_admin, get_by_id,
|
||||
fun(<<"sup1">>) -> {ok, #admin{id = <<"sup1">>, role = support}} end),
|
||||
ok = meck:expect(logic_stats, get_stats,
|
||||
fun(support, _) -> #{tickets_assigned => 3} end),
|
||||
{ok, _, _} = admin_handler_stats:init(req, []),
|
||||
?assertEqual(200, erase(test_reply)).
|
||||
|
||||
%% --- Не админ ---
|
||||
test_forbidden() ->
|
||||
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_stats:init(req, []),
|
||||
?assertEqual(403, erase(test_reply)).
|
||||
|
||||
%% --- Неверный метод ---
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
{ok, _, _} = admin_handler_stats:init(req, []),
|
||||
?assertEqual(405, erase(test_reply)).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_stats, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
-module(admin_handler_subscription_stats_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, subscription, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_subscription_stats_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET stats – empty", {timeout, 60, fun test_stats_empty/0}},
|
||||
{"GET stats – with subscriptions", {timeout, 60, fun test_stats_with_data/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
test_stats_empty() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_subscription_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/subscriptions/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(0, maps:get(<<"total_subscriptions">>, Result)),
|
||||
?assertEqual(0, maps:get(<<"trial_subscriptions">>, Result)),
|
||||
?assertEqual([], maps:get(<<"ending_paid_subscriptions">>, Result)).
|
||||
|
||||
test_stats_with_data() ->
|
||||
{ok, _} = core_subscription:create(<<"u1">>, monthly, false),
|
||||
Now = calendar:universal_time(),
|
||||
Expires = eh_test_support:days_from_now(15),
|
||||
ok = mnesia:dirty_write(#subscription{
|
||||
id = <<"sub_paid">>,
|
||||
user_id = <<"u2">>,
|
||||
plan = annual,
|
||||
status = active,
|
||||
trial_used = true,
|
||||
started_at = Now,
|
||||
expires_at = Expires,
|
||||
created_at = Now,
|
||||
updated_at = Now
|
||||
}),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_subscription_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/subscriptions/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, maps:get(<<"total_subscriptions">>, Result)),
|
||||
?assertEqual(1, maps:get(<<"trial_subscriptions">>, Result)),
|
||||
ByPlan = maps:get(<<"subscriptions_by_plan">>, Result),
|
||||
?assertEqual(1, maps:get(<<"monthly">>, ByPlan)),
|
||||
?assertEqual(1, maps:get(<<"annual">>, ByPlan)),
|
||||
Ending = maps:get(<<"ending_paid_subscriptions">>, Result),
|
||||
?assertEqual(1, length(Ending)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_subscription_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/subscriptions/stats">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_subscription_stats, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/subscriptions/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,124 @@
|
||||
-module(admin_handler_subscriptions_by_id_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, subscription, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_subscriptions_by_id_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – success", {timeout, 60, fun test_get/0}},
|
||||
{"GET – not found", {timeout, 60, fun test_get_not_found/0}},
|
||||
{"PUT – update status", {timeout, 60, fun test_update/0}},
|
||||
{"PUT – not found", {timeout, 60, fun test_update_not_found/0}},
|
||||
{"DELETE – success", {timeout, 60, fun test_delete/0}},
|
||||
{"DELETE – not found", {timeout, 60, fun test_delete_not_found/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"PATCH – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
seed_sub() ->
|
||||
{ok, Sub} = core_subscription:create(<<"u1">>, monthly, true),
|
||||
Sub.
|
||||
|
||||
test_get() ->
|
||||
Sub = seed_sub(),
|
||||
Id = Sub#subscription.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_subscriptions_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/subscriptions/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(Id, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"monthly">>, maps:get(<<"plan">>, Result)).
|
||||
|
||||
test_get_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_subscriptions_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/subscriptions/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_update() ->
|
||||
Sub = seed_sub(),
|
||||
Id = Sub#subscription.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_subscriptions_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/subscriptions/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"status">> => <<"cancelled">>})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"cancelled">>, maps:get(<<"status">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
test_update_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_subscriptions_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/subscriptions/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"status">> => <<"cancelled">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_delete() ->
|
||||
Sub = seed_sub(),
|
||||
Id = Sub#subscription.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_subscriptions_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/subscriptions/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual({error, not_found}, core_subscription:get_by_id(Id)).
|
||||
|
||||
test_delete_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_subscriptions_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/subscriptions/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_subscriptions_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/subscriptions/x">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_subscriptions_by_id, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/subscriptions/x">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -2,218 +2,69 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, subscription, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
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_subscription, [non_strict]),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_subscription),
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_subscriptions_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/subscriptions – success", fun test_list/0},
|
||||
{"GET /admin/subscriptions – forbidden", fun test_list_forbidden/0},
|
||||
{"POST /admin/subscriptions – success", fun test_create/0},
|
||||
{"POST /admin/subscriptions – missing user_id", fun test_create_missing/0},
|
||||
{"GET /admin/subscriptions/:id – success", fun test_get/0},
|
||||
{"GET /admin/subscriptions/:id – not found", fun test_get_not_found/0},
|
||||
{"PUT /admin/subscriptions/:id – success", fun test_update/0},
|
||||
{"PUT /admin/subscriptions/:id – not found", fun test_update_not_found/0},
|
||||
{"DELETE /admin/subscriptions/:id – success", fun test_delete/0},
|
||||
{"DELETE /admin/subscriptions/:id – not found", fun test_delete_not_found/0},
|
||||
{"PATCH /admin/subscriptions – method not allowed", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", fun test_list/0},
|
||||
{"GET list – filter by plan", fun test_list_filter/0},
|
||||
{"GET – unauthorized", fun test_unauthorized/0},
|
||||
{"POST – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% GET список
|
||||
test_list() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> 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),
|
||||
Sub1 = #subscription{
|
||||
id = <<"s1">>,
|
||||
user_id = <<"u1">>,
|
||||
plan = monthly,
|
||||
status = active,
|
||||
trial_used = false,
|
||||
started_at = {{2026,4,27},{12,0,0}},
|
||||
expires_at = {{2026,5,27},{12,0,0}},
|
||||
created_at = {{2026,4,27},{12,0,0}},
|
||||
updated_at = {{2026,4,27},{12,0,0}}
|
||||
},
|
||||
ok = meck:expect(core_subscription, list_subscriptions, fun() -> [Sub1] end),
|
||||
{ok, _, _} = admin_handler_subscriptions:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
{ok, _} = core_subscription:create(<<"u1">>, monthly, true),
|
||||
{ok, _} = core_subscription:create(<<"u2">>, annual, false),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_subscriptions, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/subscriptions">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
[#{<<"id">> := <<"s1">>, <<"plan">> := <<"monthly">>, <<"status">> := <<"active">>}] =
|
||||
jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, length(Result)).
|
||||
|
||||
test_list_forbidden() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> 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_subscriptions:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(403, Status).
|
||||
|
||||
%% POST создание
|
||||
test_create() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> 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),
|
||||
BodyMap = #{<<"user_id">> => <<"u1">>, <<"plan">> => <<"yearly">>},
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(BodyMap), Req} end),
|
||||
Created = #subscription{
|
||||
id = <<"s_new">>, user_id = <<"u1">>, plan = yearly, status = active,
|
||||
trial_used = false,
|
||||
started_at = {{2026,4,27},{14,0,0}}, expires_at = {{2027,4,27},{14,0,0}},
|
||||
created_at = {{2026,4,27},{14,0,0}}, updated_at = {{2026,4,27},{14,0,0}}
|
||||
},
|
||||
ok = meck:expect(core_subscription, create_subscription, fun(_) -> {ok, Created} end),
|
||||
{ok, _, _} = admin_handler_subscriptions:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(201, Status),
|
||||
#{<<"plan">> := <<"yearly">>, <<"status">> := <<"active">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
test_create_missing() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> 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(#{<<"plan">> => <<"monthly">>}), Req} end),
|
||||
{ok, _, _} = admin_handler_subscriptions:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
%% GET по ID
|
||||
test_get() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"s1">> 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),
|
||||
Sub = #subscription{
|
||||
id = <<"s1">>, user_id = <<"u1">>, plan = monthly, status = active,
|
||||
trial_used = false,
|
||||
started_at = {{2026,4,27},{12,0,0}}, expires_at = {{2026,5,27},{12,0,0}},
|
||||
created_at = {{2026,4,27},{12,0,0}}, updated_at = {{2026,4,27},{12,0,0}}
|
||||
},
|
||||
ok = meck:expect(core_subscription, get_by_id,
|
||||
fun(<<"s1">>) -> {ok, Sub} end),
|
||||
{ok, _, _} = admin_handler_subscriptions:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_list_filter() ->
|
||||
{ok, _} = core_subscription:create(<<"u1">>, monthly, true),
|
||||
{ok, _} = core_subscription:create(<<"u2">>, annual, false),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_subscriptions, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/subscriptions">>,
|
||||
auth => ?ADMIN_ID,
|
||||
qs => [{<<"plan">>, <<"monthly">>}]
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"id">> := <<"s1">>, <<"plan">> := <<"monthly">>} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, length(Result)),
|
||||
[Only] = Result,
|
||||
?assertEqual(<<"monthly">>, maps:get(<<"plan">>, Only)).
|
||||
|
||||
test_get_not_found() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"s99">> 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),
|
||||
ok = meck:expect(core_subscription, get_by_id,
|
||||
fun(_) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_subscriptions:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_subscriptions, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/subscriptions">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
%% PUT обновление
|
||||
test_update() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"s1">> 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(#{<<"status">> => <<"cancelled">>}), Req} end),
|
||||
Updated = #subscription{id = <<"s1">>, status = cancelled},
|
||||
ok = meck:expect(core_subscription, update_subscription,
|
||||
fun(<<"s1">>, _) -> {ok, Updated} end),
|
||||
{ok, _, _} = admin_handler_subscriptions:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"cancelled">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
test_update_not_found() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"s99">> 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(#{<<"status">> => <<"cancelled">>}), Req} end),
|
||||
ok = meck:expect(core_subscription, update_subscription,
|
||||
fun(_, _) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_subscriptions:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% DELETE
|
||||
test_delete() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"s1">> 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_subscription, delete_subscription,
|
||||
fun(<<"s1">>) -> {ok, deleted} end),
|
||||
{ok, _, _} = admin_handler_subscriptions: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(id, _) -> <<"s99">> 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_subscription, delete_subscription,
|
||||
fun(_) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_subscriptions:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% Неверный метод
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> undefined end),
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"PATCH">> end),
|
||||
{ok, _, _} = admin_handler_subscriptions:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_subscriptions, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/subscriptions">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -2,186 +2,114 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
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_ticket, [non_strict]),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_ticket),
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_ticket_by_id_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/tickets/:id – success", fun test_get/0},
|
||||
{"GET /admin/tickets/:id – not found", fun test_get_not_found/0},
|
||||
{"GET /admin/tickets/:id – forbidden", fun test_get_forbidden/0},
|
||||
{"PUT /admin/tickets/:id – success", fun test_update/0},
|
||||
{"PUT /admin/tickets/:id – not found", fun test_update_not_found/0},
|
||||
{"PUT /admin/tickets/:id – bad JSON", fun test_update_bad_json/0},
|
||||
{"DELETE /admin/tickets/:id – success", fun test_delete/0},
|
||||
{"DELETE /admin/tickets/:id – not found", fun test_delete_not_found/0},
|
||||
{"POST /admin/tickets/:id – method not allowed", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – success", fun test_get/0},
|
||||
{"GET – not found", fun test_get_not_found/0},
|
||||
{"PUT – resolve", fun test_resolve/0},
|
||||
{"DELETE – success", fun test_delete/0},
|
||||
{"DELETE – not found", fun test_delete_not_found/0},
|
||||
{"GET – unauthorized", fun test_unauthorized/0},
|
||||
{"PATCH – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% GET – успех
|
||||
seed_ticket() ->
|
||||
{ok, Ticket} = core_ticket:create(<<"u1">>, <<"hash_x">>, <<"boom">>, <<"stack">>),
|
||||
Ticket.
|
||||
|
||||
test_get() ->
|
||||
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),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t1">> end),
|
||||
Ticket = #ticket{
|
||||
id = <<"t1">>,
|
||||
error_hash = <<"hash">>,
|
||||
error_message = <<"msg">>,
|
||||
stacktrace = <<"trace">>,
|
||||
context = <<"ctx">>,
|
||||
count = 5,
|
||||
first_seen = {{2026,4,27},{12,0,0}},
|
||||
last_seen = {{2026,4,27},{13,0,0}},
|
||||
status = open,
|
||||
assigned_to = <<"dev1">>,
|
||||
resolution_note = undefined
|
||||
},
|
||||
ok = meck:expect(core_ticket, get_by_id,
|
||||
fun(<<"t1">>) -> {ok, Ticket} end),
|
||||
{ok, _, _} = admin_handler_ticket_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
Ticket = seed_ticket(),
|
||||
Id = Ticket#ticket.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_ticket_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/tickets/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"id">> := <<"t1">>, <<"error_message">> := <<"msg">>, <<"count">> := 5} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(Id, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"boom">>, maps:get(<<"error_message">>, Result)).
|
||||
|
||||
%% GET – не найдено
|
||||
test_get_not_found() ->
|
||||
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),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t99">> end),
|
||||
ok = meck:expect(core_ticket, get_by_id,
|
||||
fun(_) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_ticket_by_id:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_ticket_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/tickets/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% GET – запрещён
|
||||
test_get_forbidden() ->
|
||||
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_ticket_by_id:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(403, Status).
|
||||
|
||||
%% PUT – успех
|
||||
test_update() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"t1">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"status">> => <<"closed">>, <<"assigned_to">> => <<"adm2">>}), Req} end),
|
||||
Updated = #ticket{id = <<"t1">>, status = closed, assigned_to = <<"adm2">>},
|
||||
ok = meck:expect(core_ticket, update_ticket,
|
||||
fun(<<"t1">>, _) -> {ok, Updated} end),
|
||||
{ok, _, _} = admin_handler_ticket_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_resolve() ->
|
||||
Ticket = seed_ticket(),
|
||||
Id = Ticket#ticket.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_ticket_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/tickets/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{
|
||||
<<"status">> => <<"resolved">>,
|
||||
<<"resolution_note">> => <<"fixed">>
|
||||
})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"closed">>, <<"assigned_to">> := <<"adm2">>} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"resolved">>, maps:get(<<"status">>, Result)),
|
||||
?assertEqual(<<"fixed">>, maps:get(<<"resolution_note">>, Result)).
|
||||
|
||||
%% PUT – не найдено
|
||||
test_update_not_found() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"t99">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(#{<<"status">> => <<"closed">>}), Req} end),
|
||||
ok = meck:expect(core_ticket, update_ticket,
|
||||
fun(_, _) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_ticket_by_id:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% PUT – невалидный JSON
|
||||
test_update_bad_json() ->
|
||||
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, binding,
|
||||
fun(id, _) -> <<"t1">> end),
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, <<"not json">>, Req} end),
|
||||
{ok, _, _} = admin_handler_ticket_by_id:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
%% DELETE – успех
|
||||
test_delete() ->
|
||||
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(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t1">> end),
|
||||
ok = meck:expect(core_ticket, delete_ticket,
|
||||
fun(<<"t1">>) -> {ok, deleted} end),
|
||||
{ok, _, _} = admin_handler_ticket_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
Ticket = seed_ticket(),
|
||||
Id = Ticket#ticket.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_ticket_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/tickets/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(RespBody, [return_maps]).
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual({error, not_found}, core_ticket:get_by_id(Id)).
|
||||
|
||||
%% DELETE – не найдено
|
||||
test_delete_not_found() ->
|
||||
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(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t99">> end),
|
||||
ok = meck:expect(core_ticket, delete_ticket,
|
||||
fun(_) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_ticket_by_id:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_ticket_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/tickets/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% Неверный метод
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_ticket_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/tickets/x">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_ticket_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_ticket_by_id, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/tickets/x">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -2,60 +2,70 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
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_ticket, [non_strict]),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_ticket),
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_ticket_stats_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/tickets/stats – success", fun test_stats/0},
|
||||
{"GET /admin/tickets/stats – forbidden", fun test_stats_forbidden/0},
|
||||
{"POST /admin/tickets/stats – method not allowed", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET stats – empty", fun test_stats_empty/0},
|
||||
{"GET stats – with tickets", fun test_stats_with_data/0},
|
||||
{"GET – unauthorized", fun test_unauthorized/0},
|
||||
{"POST – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
test_stats() ->
|
||||
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),
|
||||
StatsData = #{
|
||||
open => 5,
|
||||
in_progress => 3,
|
||||
resolved => 12,
|
||||
closed => 20
|
||||
},
|
||||
ok = meck:expect(core_ticket, stats, fun() -> StatsData end),
|
||||
{ok, _, _} = admin_handler_ticket_stats:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_stats_empty() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_ticket_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/tickets/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"open">> := 5, <<"resolved">> := 12} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(0, maps:get(<<"total_tickets">>, Result)),
|
||||
?assertEqual(0, maps:get(<<"open">>, Result)),
|
||||
?assertEqual(0, maps:get(<<"total_errors">>, Result)).
|
||||
|
||||
test_stats_forbidden() ->
|
||||
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_ticket_stats:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(403, Status).
|
||||
test_stats_with_data() ->
|
||||
{ok, T1} = core_ticket:create(<<"u1">>, <<"h1">>, <<"err1">>, <<"s">>),
|
||||
{ok, _} = core_ticket:create(<<"u2">>, <<"h2">>, <<"err2">>, <<"s">>),
|
||||
{ok, _} = logic_ticket:update_status(?ADMIN_ID, T1#ticket.id, in_progress),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_ticket_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/tickets/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, maps:get(<<"total_tickets">>, Result)),
|
||||
?assertEqual(1, maps:get(<<"open">>, Result)),
|
||||
?assertEqual(1, maps:get(<<"in_progress">>, Result)),
|
||||
?assertEqual(2, maps:get(<<"total_errors">>, Result)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_ticket_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/tickets/stats">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
{ok, _, _} = admin_handler_ticket_stats:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_ticket_stats, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/tickets/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -2,230 +2,102 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
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_ticket, [non_strict]),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_ticket),
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_tickets_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/tickets – success", fun test_list/0},
|
||||
{"GET /admin/tickets – forbidden", fun test_list_forbidden/0},
|
||||
{"POST /admin/tickets – success", fun test_create/0},
|
||||
{"POST /admin/tickets – missing error_message", fun test_create_missing/0},
|
||||
{"GET /admin/tickets/:id – success", fun test_get/0},
|
||||
{"GET /admin/tickets/:id – not found", fun test_get_not_found/0},
|
||||
{"PUT /admin/tickets/:id – success", fun test_update/0},
|
||||
{"PUT /admin/tickets/:id – not found", fun test_update_not_found/0},
|
||||
{"DELETE /admin/tickets/:id – success", fun test_delete/0},
|
||||
{"DELETE /admin/tickets/:id – not found", fun test_delete_not_found/0},
|
||||
{"PATCH /admin/tickets – method not allowed", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", fun test_list/0},
|
||||
{"GET list – filter by status", fun test_list_filter/0},
|
||||
{"PUT update – success", fun test_update/0},
|
||||
{"PUT update – not found", fun test_update_not_found/0},
|
||||
{"GET – unauthorized", fun test_unauthorized/0},
|
||||
{"POST – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% GET – список тикетов (успех)
|
||||
seed_ticket() ->
|
||||
{ok, Ticket} = core_ticket:create(<<"u1">>, <<"hash_a">>, <<"error A">>, <<"stack">>),
|
||||
Ticket.
|
||||
|
||||
test_list() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> undefined end), % для маршрута без id
|
||||
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),
|
||||
Ticket = #ticket{
|
||||
id = <<"t1">>,
|
||||
error_hash = <<"hash1">>,
|
||||
error_message = <<"Error message">>,
|
||||
stacktrace = <<"stack">>,
|
||||
context = <<"ctx">>,
|
||||
count = 1,
|
||||
first_seen = {{2026,4,28},{12,0,0}},
|
||||
last_seen = {{2026,4,28},{12,0,0}},
|
||||
status = open,
|
||||
assigned_to = undefined,
|
||||
resolution_note = undefined
|
||||
},
|
||||
ok = meck:expect(core_ticket, list_all, fun() -> [Ticket] end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
_ = seed_ticket(),
|
||||
{ok, _} = core_ticket:create(<<"u2">>, <<"hash_b">>, <<"error B">>, <<"stack">>),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_tickets, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/tickets">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
[#{<<"id">> := <<"t1">>, <<"error_message">> := <<"Error message">>}] =
|
||||
jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, length(Result)).
|
||||
|
||||
%% GET – запрещён
|
||||
test_list_forbidden() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> undefined end), % для маршрута без id
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {error, 403, <<"Admin access required">>, Req} end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(403, Status).
|
||||
|
||||
%% POST – создание тикета
|
||||
test_create() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> undefined end), % для маршрута без id
|
||||
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),
|
||||
BodyMap = #{<<"error_message">> => <<"Bug">>, <<"stacktrace">> => <<"trace">>},
|
||||
ok = meck:expect(cowboy_req, read_body,
|
||||
fun(Req) -> {ok, jsx:encode(BodyMap), Req} end),
|
||||
Created = #ticket{id = <<"t_new">>, error_message = <<"Bug">>, status = open},
|
||||
ok = meck:expect(core_ticket, create_ticket, fun(_) -> {ok, Created} end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(201, Status),
|
||||
#{<<"error_message">> := <<"Bug">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
%% POST – отсутствует поле error_message
|
||||
test_create_missing() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> undefined end), % для маршрута без id
|
||||
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(#{<<"title">> => <<"No msg">>}), Req} end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(400, Status).
|
||||
|
||||
%% GET – один тикет по ID
|
||||
test_get() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t1">> end), % для маршрута с id
|
||||
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),
|
||||
Ticket = #ticket{id = <<"t1">>, error_message = <<"Test">>, status = open},
|
||||
ok = meck:expect(core_ticket, get_by_id,
|
||||
fun(<<"t1">>) -> {ok, Ticket} end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_list_filter() ->
|
||||
Ticket = seed_ticket(),
|
||||
{ok, _} = logic_ticket:update_status(?ADMIN_ID, Ticket#ticket.id, in_progress),
|
||||
{ok, _} = core_ticket:create(<<"u2">>, <<"hash_b">>, <<"error B">>, <<"stack">>),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_tickets, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/tickets">>,
|
||||
auth => ?ADMIN_ID,
|
||||
qs => [{<<"status">>, <<"open">>}]
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"id">> := <<"t1">>} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, length(Result)),
|
||||
[Only] = Result,
|
||||
?assertEqual(<<"open">>, maps:get(<<"status">>, Only)).
|
||||
|
||||
%% GET – тикет не найден
|
||||
test_get_not_found() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t99">> end), % для маршрута с id
|
||||
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_ticket, get_by_id,
|
||||
fun(_) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% PUT – обновление тикета
|
||||
test_update() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"PUT">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t1">> end), % для маршрута с id
|
||||
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(#{<<"status">> => <<"closed">>}), Req} end),
|
||||
Updated = #ticket{id = <<"t1">>, status = closed},
|
||||
ok = meck:expect(core_ticket, update_ticket,
|
||||
fun(<<"t1">>, _) -> {ok, Updated} end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
Ticket = seed_ticket(),
|
||||
Id = Ticket#ticket.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_tickets, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/tickets/", Id/binary>>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"status">> => <<"in_progress">>})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"closed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"in_progress">>, maps:get(<<"status">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
%% PUT – тикет не найден
|
||||
test_update_not_found() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"PUT">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t99">> end), % для маршрута с id
|
||||
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(#{<<"status">> => <<"closed">>}), Req} end),
|
||||
ok = meck:expect(core_ticket, update_ticket,
|
||||
fun(_, _) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_tickets, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/tickets/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"status">> => <<"closed">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% DELETE – удаление тикета
|
||||
test_delete() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"DELETE">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t1">> end), % для маршрута с id
|
||||
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_ticket, delete_ticket,
|
||||
fun(<<"t1">>) -> {ok, deleted} end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(RespBody, [return_maps]).
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_tickets, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/tickets">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
%% DELETE – тикет не найден
|
||||
test_delete_not_found() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"DELETE">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"t99">> end), % для маршрута с id
|
||||
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_ticket, delete_ticket,
|
||||
fun(_) -> {error, not_found} end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, _, _} = erase(test_reply),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% Неправильный метод
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"PATCH">> end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> undefined end), % для маршрута без id
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_tickets:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_tickets, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/tickets">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -2,189 +2,125 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, user, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(USER_ID, <<"user_by_id_1">>).
|
||||
|
||||
setup() ->
|
||||
ok = meck:new(cowboy_req, [non_strict]),
|
||||
ok = meck:new(handler_auth, [non_strict]),
|
||||
ok = meck:new(core_user, [non_strict]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => ?USER_ID,
|
||||
email => <<"target@test.local">>,
|
||||
nickname => <<"target">>,
|
||||
status => active
|
||||
})),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_user_by_id_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/users/:id – success", fun test_get_user/0},
|
||||
{"GET /admin/users/:id – not found", fun test_get_user_not_found/0},
|
||||
{"GET /admin/users/:id – forbidden", fun test_get_user_forbidden/0},
|
||||
{"PUT /admin/users/:id – success", fun test_update_user/0},
|
||||
{"PUT /admin/users/:id – not found", fun test_update_user_not_found/0},
|
||||
{"DELETE /admin/users/:id – success", fun test_delete_user/0},
|
||||
{"DELETE /admin/users/:id – not found", fun test_delete_user_not_found/0},
|
||||
{"POST /admin/users/:id – method not allowed", fun test_wrong_method/0},
|
||||
{"convert_updates/1", fun test_convert_updates/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – success", fun test_get/0},
|
||||
{"GET – not found", fun test_get_not_found/0},
|
||||
{"PUT – update status", fun test_update/0},
|
||||
{"PUT – not found", fun test_update_not_found/0},
|
||||
{"DELETE – soft delete", fun test_delete/0},
|
||||
{"DELETE – not found", fun test_delete_not_found/0},
|
||||
{"GET – unauthorized", fun test_unauthorized/0},
|
||||
{"PATCH – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
%% ── GET – success ─────────────────────────────────────────
|
||||
test_get_user() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"admin1">>, Req} end),
|
||||
AdminUser = #user{id = <<"admin1">>, role = admin},
|
||||
ok = meck:expect(core_user, get_by_id,
|
||||
fun(<<"admin1">>) -> {ok, AdminUser};
|
||||
(<<"user1">>) -> {ok, #user{id = <<"user1">>, email = <<"u@t.com">>,
|
||||
role = user, status = active,
|
||||
created_at = {{2026,4,27},{12,0,0}},
|
||||
updated_at = {{2026,4,27},{12,0,0}}}}
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, binding,
|
||||
fun(id, _) -> <<"user1">> end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_user_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_get() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_user_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/", ?USER_ID/binary>>,
|
||||
bindings => #{id => ?USER_ID},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"id">> := <<"user1">>, <<"email">> := <<"u@t.com">>, <<"role">> := <<"user">>}
|
||||
= jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(?USER_ID, maps:get(<<"id">>, Result)),
|
||||
?assertEqual(<<"target@test.local">>, maps:get(<<"email">>, Result)).
|
||||
|
||||
%% ── GET – not found ───────────────────────────────────────
|
||||
test_get_user_not_found() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"admin1">>, Req} end),
|
||||
AdminUser = #user{id = <<"admin1">>, role = admin},
|
||||
ok = meck:expect(core_user, get_by_id,
|
||||
fun(<<"admin1">>) -> {ok, AdminUser};
|
||||
(_) -> {error, not_found}
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"missing">> end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_user_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(404, Status),
|
||||
#{<<"error">> := <<"User not found">>} = jsx:decode(RespBody, [return_maps]).
|
||||
test_get_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% ── GET – forbidden ───────────────────────────────────────
|
||||
test_get_user_forbidden() ->
|
||||
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 = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_user_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(403, Status),
|
||||
#{<<"error">> := <<"Admin access required">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
%% ── PUT – success ─────────────────────────────────────────
|
||||
test_update_user() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"PUT">> end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"admin1">>, Req} end),
|
||||
AdminUser = #user{id = <<"admin1">>, role = admin},
|
||||
ok = meck:expect(core_user, get_by_id,
|
||||
fun(<<"admin1">>) -> {ok, AdminUser} end),
|
||||
User = #user{id = <<"user1">>, email = <<"u@t.com">>, role = user, status = frozen,
|
||||
created_at = {{2026,4,27},{12,0,0}}, updated_at = {{2026,4,27},{13,0,0}}},
|
||||
ok = meck:expect(core_user, update, fun(<<"user1">>, _) -> {ok, User} end),
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"user1">> end),
|
||||
ok = meck:expect(cowboy_req, read_body, fun(Req) -> {ok, jsx:encode(#{status => <<"frozen">>}), Req} end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_user_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_update() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_user_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/users/", ?USER_ID/binary>>,
|
||||
bindings => #{id => ?USER_ID},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{
|
||||
<<"status">> => <<"frozen">>,
|
||||
<<"reason">> => <<"abuse">>
|
||||
})
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"frozen">>} = jsx:decode(RespBody, [return_maps]).
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(<<"frozen">>, maps:get(<<"status">>, Result)),
|
||||
?assertEqual(<<"abuse">>, maps:get(<<"reason">>, Result)),
|
||||
Audits = core_admin_audit:list(),
|
||||
?assert(length(Audits) >= 1).
|
||||
|
||||
%% ── PUT – not found ──────────────────────────────────────
|
||||
test_update_user_not_found() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"PUT">> end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"admin1">>, Req} end),
|
||||
AdminUser = #user{id = <<"admin1">>, role = admin},
|
||||
ok = meck:expect(core_user, get_by_id,
|
||||
fun(<<"admin1">>) -> {ok, AdminUser} end),
|
||||
ok = meck:expect(core_user, update, fun(_, _) -> {error, not_found} end),
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"missing">> end),
|
||||
ok = meck:expect(cowboy_req, read_body, fun(Req) -> {ok, <<"{}">>, Req} end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_user_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(404, Status),
|
||||
#{<<"error">> := <<"User not found">>} = jsx:decode(RespBody, [return_maps]).
|
||||
test_update_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_by_id, #{
|
||||
method => <<"PUT">>,
|
||||
path => <<"/v1/admin/users/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID,
|
||||
body => jsx:encode(#{<<"status">> => <<"frozen">>})
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
%% ── DELETE – success ─────────────────────────────────────
|
||||
test_delete_user() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"DELETE">> end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"admin1">>, Req} end),
|
||||
AdminUser = #user{id = <<"admin1">>, role = admin},
|
||||
ok = meck:expect(core_user, get_by_id,
|
||||
fun(<<"admin1">>) -> {ok, AdminUser} end),
|
||||
ok = meck:expect(core_user, delete, fun(<<"user1">>) -> {ok, deleted} end),
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"user1">> end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_user_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
test_delete() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_user_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/users/", ?USER_ID/binary>>,
|
||||
bindings => #{id => ?USER_ID},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(RespBody, [return_maps]).
|
||||
#{<<"status">> := <<"deleted">>} = jsx:decode(Body, [return_maps]),
|
||||
{ok, User} = core_user:get_by_id(?USER_ID),
|
||||
?assertEqual(deleted, User#user.status).
|
||||
|
||||
%% ── DELETE – not found ───────────────────────────────────
|
||||
test_delete_user_not_found() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"DELETE">> end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"admin1">>, Req} end),
|
||||
AdminUser = #user{id = <<"admin1">>, role = admin},
|
||||
ok = meck:expect(core_user, get_by_id,
|
||||
fun(<<"admin1">>) -> {ok, AdminUser} end),
|
||||
ok = meck:expect(core_user, delete, fun(_) -> {error, not_found} end),
|
||||
ok = meck:expect(cowboy_req, binding, fun(id, _) -> <<"missing">> end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_user_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(404, Status),
|
||||
#{<<"error">> := <<"User not found">>} = jsx:decode(RespBody, [return_maps]).
|
||||
test_delete_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_by_id, #{
|
||||
method => <<"DELETE">>,
|
||||
path => <<"/v1/admin/users/missing">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_by_id, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/", ?USER_ID/binary>>,
|
||||
bindings => #{id => ?USER_ID},
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
%% ── Wrong method ─────────────────────────────────────────
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
ok = meck:expect(cowboy_req, reply,
|
||||
fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_user_by_id:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
#{<<"error">> := <<"Method not allowed">>} = jsx:decode(RespBody, [return_maps]).
|
||||
|
||||
%% ── convert_updates/1 ────────────────────────────────────
|
||||
test_convert_updates() ->
|
||||
Updates = [
|
||||
{<<"status">>, <<"frozen">>},
|
||||
{<<"role">>, <<"admin">>},
|
||||
{<<"email">>, <<"test@test.com">>}
|
||||
],
|
||||
Converted = admin_handler_user_by_id:convert_updates(Updates),
|
||||
?assertEqual({status, frozen}, lists:keyfind(status, 1, Converted)),
|
||||
?assertEqual({role, admin}, lists:keyfind(role, 1, Converted)),
|
||||
?assertEqual({<<"email">>, <<"test@test.com">>}, lists:keyfind(<<"email">>, 1, Converted)).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_by_id, #{
|
||||
method => <<"PATCH">>,
|
||||
path => <<"/v1/admin/users/", ?USER_ID/binary>>,
|
||||
bindings => #{id => ?USER_ID},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
-module(admin_handler_user_stats_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, user, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_user_stats_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET stats – empty", {timeout, 60, fun test_stats_empty/0}},
|
||||
{"GET stats – with users", {timeout, 60, fun test_stats_with_data/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
test_stats_empty() ->
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_user_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(0, maps:get(<<"total_users">>, Result)),
|
||||
?assertEqual(0, maps:get(<<"pending_users">>, Result)).
|
||||
|
||||
test_stats_with_data() ->
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => <<"u1">>,
|
||||
email => <<"alice@test.local">>,
|
||||
nickname => <<"alice">>,
|
||||
status => active,
|
||||
role => user
|
||||
})),
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => <<"u2">>,
|
||||
email => <<"bob@test.local">>,
|
||||
nickname => <<"bob">>,
|
||||
status => pending,
|
||||
role => user
|
||||
})),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_user_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, maps:get(<<"total_users">>, Result)),
|
||||
?assertEqual(1, maps:get(<<"pending_users">>, Result)),
|
||||
ByRole = maps:get(<<"users_by_role">>, Result),
|
||||
?assertEqual(2, maps:get(<<"user">>, ByRole)),
|
||||
ByStatus = maps:get(<<"users_by_status">>, Result),
|
||||
?assertEqual(1, maps:get(<<"active">>, ByStatus)),
|
||||
?assertEqual(1, maps:get(<<"pending">>, ByStatus)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_stats, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/stats">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_stats, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/users/stats">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -0,0 +1,93 @@
|
||||
-module(admin_handler_user_verification_token_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, user, verification, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_user_verification_token_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET – create token", {timeout, 60, fun test_create/0}},
|
||||
{"GET – reuse existing token", {timeout, 60, fun test_reuse/0}},
|
||||
{"GET – user not found", {timeout, 60, fun test_not_found/0}},
|
||||
{"GET – unauthorized", {timeout, 60, fun test_unauthorized/0}},
|
||||
{"POST – method not allowed", {timeout, 60, fun test_wrong_method/0}}
|
||||
]}.
|
||||
|
||||
seed_user() ->
|
||||
User = eh_test_support:make_user(#{
|
||||
id => <<"u_verify">>,
|
||||
email => <<"verify@test.local">>,
|
||||
nickname => <<"verify">>,
|
||||
status => pending
|
||||
}),
|
||||
ok = mnesia:dirty_write(User),
|
||||
User.
|
||||
|
||||
test_create() ->
|
||||
User = seed_user(),
|
||||
Id = User#user.id,
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_user_verification_token, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/", Id/binary, "/verification-token">>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assert(is_binary(maps:get(<<"token">>, Result))),
|
||||
?assert(is_binary(maps:get(<<"expires_at">>, Result))).
|
||||
|
||||
test_reuse() ->
|
||||
User = seed_user(),
|
||||
Id = User#user.id,
|
||||
{ok, Token1, _} = core_verification:create_token(Id),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_user_verification_token, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/", Id/binary, "/verification-token">>,
|
||||
bindings => #{id => Id},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(Token1, maps:get(<<"token">>, Result)).
|
||||
|
||||
test_not_found() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_verification_token, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/missing/verification-token">>,
|
||||
bindings => #{id => <<"missing">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(404, Status).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_verification_token, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users/x/verification-token">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_user_verification_token, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/users/x/verification-token">>,
|
||||
bindings => #{id => <<"x">>},
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
@@ -1,65 +1,83 @@
|
||||
-module(admin_handler_users_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin, admin_audit, user, ticket]).
|
||||
-define(ADMIN_ID, <<"adm_test_1">>).
|
||||
|
||||
setup() ->
|
||||
ok = meck:new(cowboy_req, [non_strict]),
|
||||
ok = meck:new(handler_auth, [non_strict]), % вместо auth
|
||||
ok = meck:new(core_user, [non_strict]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:ensure_jwt(),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID}),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
meck:unload(core_user),
|
||||
meck:unload(handler_auth),
|
||||
meck:unload(cowboy_req).
|
||||
eh_test_support:unload_cowboy(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
admin_users_test_() ->
|
||||
{setup, fun setup/0, fun cleanup/1, [
|
||||
{"GET /admin/users with valid admin token returns 200 and list of users", fun test_list_users/0},
|
||||
{"GET /admin/users with non-admin token returns 403", fun test_list_users_forbidden/0},
|
||||
{"POST /admin/users returns 405", fun test_wrong_method/0}
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"GET list – success", fun test_list/0},
|
||||
{"GET list – filter by status", fun test_list_filter/0},
|
||||
{"GET – unauthorized", fun test_unauthorized/0},
|
||||
{"POST – method not allowed", fun test_wrong_method/0}
|
||||
]}.
|
||||
|
||||
test_list_users() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"GET">> end),
|
||||
ok = meck:expect(handler_auth, authenticate,
|
||||
fun(Req) -> {ok, <<"admin1">>, Req} end),
|
||||
User = #{
|
||||
id => <<"user1">>,
|
||||
email => <<"user@test.com">>,
|
||||
role => <<"user">>,
|
||||
status => <<"active">>,
|
||||
created_at => {{2025,4,27},{12,0,0}},
|
||||
updated_at => {{2025,4,27},{12,30,0}}
|
||||
},
|
||||
ok = meck:expect(core_user, list_users, fun() -> {ok, [User]} end),
|
||||
ok = meck:expect(cowboy_req, reply, fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_users:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(200, Status),
|
||||
Users = jsx:decode(RespBody, [return_maps]),
|
||||
?assertEqual(1, length(Users)),
|
||||
?assertEqual(<<"user1">>, maps:get(<<"id">>, hd(Users))).
|
||||
seed_users() ->
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => <<"u1">>,
|
||||
email => <<"alice@test.local">>,
|
||||
nickname => <<"alice">>,
|
||||
status => active
|
||||
})),
|
||||
ok = mnesia:dirty_write(eh_test_support:make_user(#{
|
||||
id => <<"u2">>,
|
||||
email => <<"bob@test.local">>,
|
||||
nickname => <<"bob">>,
|
||||
status => frozen
|
||||
})),
|
||||
ok.
|
||||
|
||||
test_list_users_forbidden() ->
|
||||
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 = meck:expect(cowboy_req, reply, fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_users:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(403, Status),
|
||||
?assertEqual(#{<<"error">> => <<"Admin access required">>}, jsx:decode(RespBody, [return_maps])).
|
||||
test_list() ->
|
||||
seed_users(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_users, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(2, length(Result)).
|
||||
|
||||
test_list_filter() ->
|
||||
seed_users(),
|
||||
{Status, _, Body} = eh_test_support:call(admin_handler_users, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users">>,
|
||||
auth => ?ADMIN_ID,
|
||||
qs => [{<<"status">>, <<"active">>}]
|
||||
}),
|
||||
?assertEqual(200, Status),
|
||||
Result = jsx:decode(Body, [return_maps]),
|
||||
?assertEqual(1, length(Result)),
|
||||
[Only] = Result,
|
||||
?assertEqual(<<"alice@test.local">>, maps:get(<<"email">>, Only)).
|
||||
|
||||
test_unauthorized() ->
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_users, #{
|
||||
method => <<"GET">>,
|
||||
path => <<"/v1/admin/users">>,
|
||||
auth => none
|
||||
}),
|
||||
?assertEqual(401, Status).
|
||||
|
||||
test_wrong_method() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) -> <<"POST">> end),
|
||||
ok = meck:expect(cowboy_req, reply, fun(Code, Headers, Body, Req) ->
|
||||
put(test_reply, {Code, Headers, Body, Req})
|
||||
end),
|
||||
{ok, _, _} = admin_handler_users:init(req, []),
|
||||
{Status, _, RespBody, _} = erase(test_reply),
|
||||
?assertEqual(405, Status),
|
||||
?assertEqual(#{<<"error">> => <<"Method not allowed">>}, jsx:decode(RespBody, [return_maps])).
|
||||
{Status, _, _} = eh_test_support:call(admin_handler_users, #{
|
||||
method => <<"POST">>,
|
||||
path => <<"/v1/admin/users">>,
|
||||
auth => ?ADMIN_ID
|
||||
}),
|
||||
?assertEqual(405, Status).
|
||||
|
||||
@@ -1,37 +1,110 @@
|
||||
-module(admin_utils_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin]).
|
||||
-define(ADMIN_ID, <<"adm_utils_1">>).
|
||||
-define(SUPER_ID, <<"adm_super_1">>).
|
||||
|
||||
%%%===================================================================
|
||||
%%% client_ip / ip_to_binary (cowboy via eh_test_support)
|
||||
%%%===================================================================
|
||||
|
||||
client_ip_setup() ->
|
||||
ok.
|
||||
|
||||
client_ip_cleanup(_) ->
|
||||
eh_test_support:unload_cowboy(),
|
||||
ok.
|
||||
|
||||
client_ip_test_() ->
|
||||
{setup,
|
||||
fun() -> ok = meck:new(cowboy_req, [non_strict]) end,
|
||||
fun(_) -> meck:unload(cowboy_req) end,
|
||||
[
|
||||
{"Uses X-Forwarded-For when present", fun test_forwarded_for/0},
|
||||
{"Falls back to X-Real-IP", fun test_real_ip/0},
|
||||
{"Falls back to peer address", fun test_peer_fallback/0},
|
||||
{"ip_to_binary handles IPv4 tuple", fun test_ipv4_tuple/0}
|
||||
]}.
|
||||
{foreach, fun client_ip_setup/0, fun client_ip_cleanup/1, [
|
||||
{"Uses X-Forwarded-For when present", fun test_forwarded_for/0},
|
||||
{"Falls back to X-Real-IP", fun test_real_ip/0},
|
||||
{"Falls back to peer address", fun test_peer_fallback/0},
|
||||
{"ip_to_binary handles IPv4 tuple", fun test_ipv4_tuple/0}
|
||||
]}.
|
||||
|
||||
test_forwarded_for() ->
|
||||
ok = meck:expect(cowboy_req, header, fun
|
||||
(<<"x-forwarded-for">>, _) -> <<"203.0.113.10, 10.0.0.1">>;
|
||||
(_, _) -> undefined
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, peer, fun(_) -> {10, 0, 0, 2} end),
|
||||
?assertEqual(<<"203.0.113.10">>, admin_utils:client_ip(req)).
|
||||
Req = eh_test_support:with_cowboy(#{
|
||||
headers => #{<<"x-forwarded-for">> => <<"203.0.113.10, 10.0.0.1">>},
|
||||
peer => {10, 0, 0, 2}
|
||||
}),
|
||||
?assertEqual(<<"203.0.113.10">>, admin_utils:client_ip(Req)).
|
||||
|
||||
test_real_ip() ->
|
||||
ok = meck:expect(cowboy_req, header, fun
|
||||
(<<"x-real-ip">>, _) -> <<"198.51.100.5">>;
|
||||
(_, _) -> undefined
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, peer, fun(_) -> {10, 0, 0, 2} end),
|
||||
?assertEqual(<<"198.51.100.5">>, admin_utils:client_ip(req)).
|
||||
Req = eh_test_support:with_cowboy(#{
|
||||
headers => #{<<"x-real-ip">> => <<"198.51.100.5">>},
|
||||
peer => {10, 0, 0, 2}
|
||||
}),
|
||||
?assertEqual(<<"198.51.100.5">>, admin_utils:client_ip(Req)).
|
||||
|
||||
test_peer_fallback() ->
|
||||
ok = meck:expect(cowboy_req, header, fun(_, _) -> undefined end),
|
||||
ok = meck:expect(cowboy_req, peer, fun(_) -> {127, 0, 0, 1} end),
|
||||
?assertEqual(<<"127.0.0.1">>, admin_utils:client_ip(req)).
|
||||
Req = eh_test_support:with_cowboy(#{
|
||||
headers => #{},
|
||||
peer => {127, 0, 0, 1}
|
||||
}),
|
||||
?assertEqual(<<"127.0.0.1">>, admin_utils:client_ip(Req)).
|
||||
|
||||
test_ipv4_tuple() ->
|
||||
?assertEqual(<<"192.168.1.1">>, admin_utils:ip_to_binary({192, 168, 1, 1})).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Role / permission helpers (real admin table, no core_admin mock)
|
||||
%%%===================================================================
|
||||
|
||||
role_setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
eh_test_support:seed_admin(#{id => ?ADMIN_ID, role => admin}),
|
||||
eh_test_support:seed_admin(#{
|
||||
id => ?SUPER_ID,
|
||||
email => <<"super@test.local">>,
|
||||
role => superadmin,
|
||||
nickname => <<"super">>
|
||||
}),
|
||||
ok.
|
||||
|
||||
role_cleanup(_) ->
|
||||
eh_test_support:clear_tables(?TABLES),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
role_utils_test_() ->
|
||||
{foreach, fun role_setup/0, fun role_cleanup/1, [
|
||||
{"is_admin / is_superadmin", fun test_is_admin_roles/0},
|
||||
{"check_role atom and list", fun test_check_role/0},
|
||||
{"get_permissions by role", fun test_get_permissions/0}
|
||||
]}.
|
||||
|
||||
test_is_admin_roles() ->
|
||||
?assertEqual(true, admin_utils:is_admin(?ADMIN_ID)),
|
||||
?assertEqual(true, admin_utils:is_admin(?SUPER_ID)),
|
||||
?assertEqual(false, admin_utils:is_admin(<<"missing">>)),
|
||||
?assertEqual(true, admin_utils:is_superadmin(?SUPER_ID)),
|
||||
?assertEqual(false, admin_utils:is_superadmin(?ADMIN_ID)),
|
||||
?assertEqual(false, admin_utils:is_superadmin(<<"missing">>)).
|
||||
|
||||
test_check_role() ->
|
||||
?assertEqual(true, admin_utils:check_role(?ADMIN_ID, admin)),
|
||||
?assertEqual(false, admin_utils:check_role(?ADMIN_ID, superadmin)),
|
||||
?assertEqual(true, admin_utils:check_role(?ADMIN_ID, [admin, moderator])),
|
||||
?assertEqual(false, admin_utils:check_role(?ADMIN_ID, [superadmin, support])),
|
||||
?assertEqual(true, admin_utils:check_role(?SUPER_ID, [admin, superadmin])),
|
||||
?assertEqual(false, admin_utils:check_role(<<"missing">>, admin)),
|
||||
?assertEqual(false, admin_utils:check_role(<<"missing">>, [admin])).
|
||||
|
||||
test_get_permissions() ->
|
||||
Super = admin_utils:get_permissions(superadmin),
|
||||
Admin = admin_utils:get_permissions(admin),
|
||||
Mod = admin_utils:get_permissions(moderator),
|
||||
Support = admin_utils:get_permissions(support),
|
||||
?assert(lists:member(<<"manage_admins">>, Super)),
|
||||
?assert(lists:member(<<"view_audit">>, Super)),
|
||||
?assertEqual(false, lists:member(<<"manage_admins">>, Admin)),
|
||||
?assert(lists:member(<<"manage_users">>, Admin)),
|
||||
?assert(lists:member(<<"manage_tickets">>, Mod)),
|
||||
?assertEqual(false, lists:member(<<"view_audit">>, Mod)),
|
||||
?assertEqual([<<"manage_tickets">>, <<"view_stats">>], Support),
|
||||
?assertEqual([], admin_utils:get_permissions(unknown)).
|
||||
|
||||
@@ -2,32 +2,16 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [user, calendar, event, booking]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(user, [
|
||||
{attributes, record_info(fields, user)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(calendar, [
|
||||
{attributes, record_info(fields, calendar)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(event, [
|
||||
{attributes, record_info(fields, event)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(booking, [
|
||||
{attributes, record_info(fields, booking)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(booking),
|
||||
mnesia:delete_table(event),
|
||||
mnesia:delete_table(calendar),
|
||||
mnesia:delete_table(user),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
booking_integration_test_() ->
|
||||
@@ -35,7 +19,7 @@ booking_integration_test_() ->
|
||||
fun setup/0,
|
||||
fun cleanup/1,
|
||||
[
|
||||
{"Full booking flow with auto confirmation", fun test_auto_booking_flow/0},
|
||||
{"Booking create stays pending", fun test_pending_booking_flow/0},
|
||||
{"Full booking flow with manual confirmation", fun test_manual_booking_flow/0},
|
||||
{"Capacity management test", fun test_capacity_management/0},
|
||||
{"Multiple bookings test", fun test_multiple_bookings/0}
|
||||
@@ -55,22 +39,26 @@ create_user() ->
|
||||
mnesia:dirty_write(User),
|
||||
UserId.
|
||||
|
||||
test_auto_booking_flow() ->
|
||||
add_days(DateTime, Days) ->
|
||||
Sec = calendar:datetime_to_gregorian_seconds(DateTime) + Days * 86400,
|
||||
calendar:gregorian_seconds_to_datetime(Sec).
|
||||
|
||||
test_pending_booking_flow() ->
|
||||
OwnerId = create_user(),
|
||||
ParticipantId = create_user(),
|
||||
|
||||
{ok, Calendar} = core_calendar:create(OwnerId, <<"Auto">>, <<"">>, auto),
|
||||
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, Event} = core_event:create(Calendar#calendar.id, <<"Event">>, StartTime, 60),
|
||||
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, Event#event.id),
|
||||
timer:sleep(100),
|
||||
?assertEqual(pending, Booking#booking.status),
|
||||
|
||||
{ok, Updated} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(confirmed, Updated#booking.status),
|
||||
{ok, Stored} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(pending, Stored#booking.status),
|
||||
|
||||
{ok, EventBookings} = logic_booking:list_event_bookings(OwnerId, Event#event.id),
|
||||
{ok, EventBookings} = logic_booking:list_event_bookings(Event#event.id),
|
||||
?assertEqual(1, length(EventBookings)),
|
||||
|
||||
{ok, UserBookings} = logic_booking:list_user_bookings(ParticipantId),
|
||||
@@ -82,7 +70,7 @@ test_manual_booking_flow() ->
|
||||
|
||||
{ok, Calendar} = core_calendar:create(OwnerId, <<"Manual">>, <<"">>, manual),
|
||||
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, Event} = core_event:create(Calendar#calendar.id, <<"Event">>, StartTime, 60),
|
||||
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, Event#event.id),
|
||||
@@ -91,7 +79,7 @@ test_manual_booking_flow() ->
|
||||
{ok, Confirmed} = logic_booking:confirm_booking(OwnerId, Booking#booking.id, confirm),
|
||||
?assertEqual(confirmed, Confirmed#booking.status),
|
||||
|
||||
{ok, Cancelled} = logic_booking:cancel_booking(ParticipantId, Booking#booking.id),
|
||||
{ok, Cancelled} = logic_booking:cancel_booking(ParticipantId, Booking#booking.id, cancel),
|
||||
?assertEqual(cancelled, Cancelled#booking.status).
|
||||
|
||||
test_capacity_management() ->
|
||||
@@ -100,21 +88,21 @@ test_capacity_management() ->
|
||||
Participant2Id = create_user(),
|
||||
Participant3Id = create_user(),
|
||||
|
||||
{ok, Calendar} = core_calendar:create(OwnerId, <<"Test">>, <<"">>, auto),
|
||||
{ok, Calendar} = core_calendar:create(OwnerId, <<"Test">>, <<"">>, manual),
|
||||
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, Event} = core_event:create(Calendar#calendar.id, <<"Event">>, StartTime, 60),
|
||||
{ok, _} = core_event:update(Event#event.id, [{capacity, 2}]),
|
||||
|
||||
{ok, Booking1} = logic_booking:create_booking(Participant1Id, Event#event.id),
|
||||
{ok, _Booking2} = logic_booking:create_booking(Participant2Id, Event#event.id),
|
||||
{ok, Booking2} = logic_booking:create_booking(Participant2Id, Event#event.id),
|
||||
{ok, _} = logic_booking:confirm_booking(OwnerId, Booking1#booking.id, confirm),
|
||||
{ok, _} = logic_booking:confirm_booking(OwnerId, Booking2#booking.id, confirm),
|
||||
|
||||
{error, event_full} = logic_booking:create_booking(Participant3Id, Event#event.id),
|
||||
{error, full} = logic_booking:create_booking(Participant3Id, Event#event.id),
|
||||
|
||||
% Участник 1 отменяет своё бронирование
|
||||
{ok, _} = logic_booking:cancel_booking(Participant1Id, Booking1#booking.id),
|
||||
{ok, _} = logic_booking:cancel_booking(Participant1Id, Booking1#booking.id, cancel),
|
||||
|
||||
% Теперь третий может записаться
|
||||
{ok, _} = logic_booking:create_booking(Participant3Id, Event#event.id).
|
||||
|
||||
test_multiple_bookings() ->
|
||||
@@ -123,9 +111,10 @@ test_multiple_bookings() ->
|
||||
|
||||
{ok, Calendar} = core_calendar:create(OwnerId, <<"Test">>, <<"">>, manual),
|
||||
|
||||
StartTime1 = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime2 = {{2026, 6, 2}, {10, 0, 0}},
|
||||
StartTime3 = {{2026, 6, 3}, {10, 0, 0}},
|
||||
Base = eh_test_support:future_start(),
|
||||
StartTime1 = Base,
|
||||
StartTime2 = add_days(Base, 1),
|
||||
StartTime3 = add_days(Base, 2),
|
||||
|
||||
{ok, Event1} = core_event:create(Calendar#calendar.id, <<"Event1">>, StartTime1, 60),
|
||||
{ok, Event2} = core_event:create(Calendar#calendar.id, <<"Event2">>, StartTime2, 60),
|
||||
@@ -145,4 +134,4 @@ test_multiple_bookings() ->
|
||||
?assertEqual(2, ConfirmedCount),
|
||||
|
||||
PendingCount = length([B || B <- UserBookings, B#booking.status =:= pending]),
|
||||
?assertEqual(1, PendingCount).
|
||||
?assertEqual(1, PendingCount).
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
-module(core_admin_audit_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin_audit]).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:clear_tables(?TABLES),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
core_admin_audit_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"Log/7 without reason", fun test_log7/0},
|
||||
{"Log/8 with reason", fun test_log8/0},
|
||||
{"List all", fun test_list_all/0},
|
||||
{"List with filters", fun test_list_filters/0},
|
||||
{"Count actions by admin", fun test_count_actions/0}
|
||||
]}.
|
||||
|
||||
test_log7() ->
|
||||
{ok, Entry} = core_admin_audit:log(
|
||||
<<"adm1">>, <<"a@test.local">>, admin,
|
||||
<<"create">>, <<"user">>, <<"u1">>, <<"127.0.0.1">>
|
||||
),
|
||||
?assertEqual(<<"adm1">>, Entry#admin_audit.admin_id),
|
||||
?assertEqual(<<"a@test.local">>, Entry#admin_audit.email),
|
||||
?assertEqual(admin, Entry#admin_audit.role),
|
||||
?assertEqual(<<"create">>, Entry#admin_audit.action),
|
||||
?assertEqual(<<"user">>, Entry#admin_audit.entity_type),
|
||||
?assertEqual(<<"u1">>, Entry#admin_audit.entity_id),
|
||||
?assertEqual(<<"127.0.0.1">>, Entry#admin_audit.ip),
|
||||
?assertEqual(<<>>, Entry#admin_audit.reason),
|
||||
?assert(is_binary(Entry#admin_audit.id)).
|
||||
|
||||
test_log8() ->
|
||||
{ok, Entry} = core_admin_audit:log(
|
||||
<<"adm1">>, <<"a@test.local">>, moderator,
|
||||
<<"block">>, <<"user">>, <<"u2">>, <<"10.0.0.1">>, <<"abuse">>
|
||||
),
|
||||
?assertEqual(<<"block">>, Entry#admin_audit.action),
|
||||
?assertEqual(<<"abuse">>, Entry#admin_audit.reason),
|
||||
?assertEqual(moderator, Entry#admin_audit.role).
|
||||
|
||||
test_list_all() ->
|
||||
?assertEqual([], core_admin_audit:list()),
|
||||
{ok, _} = core_admin_audit:log(
|
||||
<<"adm1">>, <<"a@test.local">>, admin,
|
||||
<<"create">>, <<"user">>, <<"u1">>, <<"127.0.0.1">>
|
||||
),
|
||||
{ok, _} = core_admin_audit:log(
|
||||
<<"adm2">>, <<"b@test.local">>, admin,
|
||||
<<"update">>, <<"event">>, <<"e1">>, <<"127.0.0.1">>, <<"fix">>
|
||||
),
|
||||
?assertEqual(2, length(core_admin_audit:list())).
|
||||
|
||||
test_list_filters() ->
|
||||
{ok, _} = core_admin_audit:log(
|
||||
<<"adm1">>, <<"a@test.local">>, admin,
|
||||
<<"create">>, <<"user">>, <<"u1">>, <<"127.0.0.1">>
|
||||
),
|
||||
{ok, _} = core_admin_audit:log(
|
||||
<<"adm1">>, <<"a@test.local">>, admin,
|
||||
<<"block">>, <<"user">>, <<"u2">>, <<"127.0.0.1">>, <<"spam">>
|
||||
),
|
||||
{ok, _} = core_admin_audit:log(
|
||||
<<"adm2">>, <<"b@test.local">>, support,
|
||||
<<"create">>, <<"ticket">>, <<"t1">>, <<"10.0.0.1">>
|
||||
),
|
||||
|
||||
ByAdmin = core_admin_audit:list([{admin_id, <<"adm1">>}]),
|
||||
?assertEqual(2, length(ByAdmin)),
|
||||
?assert(lists:all(fun(E) -> E#admin_audit.admin_id =:= <<"adm1">> end, ByAdmin)),
|
||||
|
||||
ByAction = core_admin_audit:list([{action, <<"create">>}]),
|
||||
?assertEqual(2, length(ByAction)),
|
||||
|
||||
Combined = core_admin_audit:list([
|
||||
{admin_id, <<"adm1">>},
|
||||
{action, <<"block">>}
|
||||
]),
|
||||
?assertEqual(1, length(Combined)),
|
||||
[Only] = Combined,
|
||||
?assertEqual(<<"block">>, Only#admin_audit.action),
|
||||
|
||||
FarFuture = {{2099, 1, 1}, {0, 0, 0}},
|
||||
?assertEqual([], core_admin_audit:list([{date_from, FarFuture}])),
|
||||
|
||||
FarPast = {{1970, 1, 1}, {0, 0, 0}},
|
||||
?assertEqual(3, length(core_admin_audit:list([{date_from, FarPast}]))),
|
||||
|
||||
BeforeAll = {{1970, 1, 1}, {0, 0, 0}},
|
||||
?assertEqual([], core_admin_audit:list([{date_to, BeforeAll}])).
|
||||
|
||||
test_count_actions() ->
|
||||
{ok, _} = core_admin_audit:log(
|
||||
<<"adm1">>, <<"a@test.local">>, admin,
|
||||
<<"create">>, <<"user">>, <<"u1">>, <<"127.0.0.1">>
|
||||
),
|
||||
{ok, _} = core_admin_audit:log(
|
||||
<<"adm1">>, <<"a@test.local">>, admin,
|
||||
<<"create">>, <<"event">>, <<"e1">>, <<"127.0.0.1">>
|
||||
),
|
||||
{ok, _} = core_admin_audit:log(
|
||||
<<"adm1">>, <<"a@test.local">>, admin,
|
||||
<<"block">>, <<"user">>, <<"u2">>, <<"127.0.0.1">>
|
||||
),
|
||||
{ok, _} = core_admin_audit:log(
|
||||
<<"adm2">>, <<"b@test.local">>, admin,
|
||||
<<"create">>, <<"user">>, <<"u3">>, <<"127.0.0.1">>
|
||||
),
|
||||
?assertEqual(2, core_admin_audit:count_actions_by_admin(<<"adm1">>, <<"create">>)),
|
||||
?assertEqual(1, core_admin_audit:count_actions_by_admin(<<"adm1">>, <<"block">>)),
|
||||
?assertEqual(0, core_admin_audit:count_actions_by_admin(<<"adm1">>, <<"delete">>)),
|
||||
?assertEqual(1, core_admin_audit:count_actions_by_admin(<<"adm2">>, <<"create">>)).
|
||||
@@ -0,0 +1,109 @@
|
||||
-module(core_admin_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [admin]).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:clear_tables(?TABLES),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
core_admin_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"Create admin", fun test_create/0},
|
||||
{"Create admin – email exists", fun test_create_email_exists/0},
|
||||
{"Get by id", fun test_get_by_id/0},
|
||||
{"Get by email", fun test_get_by_email/0},
|
||||
{"List all", fun test_list_all/0},
|
||||
{"Update fields", fun test_update/0},
|
||||
{"Update role", fun test_update_role/0},
|
||||
{"Block and unblock", fun test_block_unblock/0},
|
||||
{"Update last login", fun test_update_last_login/0},
|
||||
{"Delete", fun test_delete/0}
|
||||
]}.
|
||||
|
||||
test_create() ->
|
||||
Email = <<"admin1@test.local">>,
|
||||
{ok, Admin} = core_admin:create(Email, <<"secret123">>, admin),
|
||||
?assertEqual(Email, Admin#admin.email),
|
||||
?assertEqual(admin, Admin#admin.role),
|
||||
?assertEqual(active, Admin#admin.status),
|
||||
?assertEqual(<<"admin1">>, Admin#admin.nickname),
|
||||
?assert(is_binary(Admin#admin.id)),
|
||||
?assert(is_binary(Admin#admin.password_hash)),
|
||||
?assert(Admin#admin.password_hash =/= <<"secret123">>).
|
||||
|
||||
test_create_email_exists() ->
|
||||
Email = <<"dup@test.local">>,
|
||||
{ok, _} = core_admin:create(Email, <<"pass1">>, admin),
|
||||
?assertEqual({error, email_exists}, core_admin:create(Email, <<"pass2">>, moderator)).
|
||||
|
||||
test_get_by_id() ->
|
||||
{ok, Admin} = core_admin:create(<<"byid@test.local">>, <<"pass">>, admin),
|
||||
{ok, Found} = core_admin:get_by_id(Admin#admin.id),
|
||||
?assertEqual(Admin#admin.id, Found#admin.id),
|
||||
?assertEqual(Admin#admin.email, Found#admin.email),
|
||||
?assertEqual({error, not_found}, core_admin:get_by_id(<<"missing">>)).
|
||||
|
||||
test_get_by_email() ->
|
||||
Email = <<"byemail@test.local">>,
|
||||
{ok, Admin} = core_admin:create(Email, <<"pass">>, support),
|
||||
{ok, Found} = core_admin:get_by_email(Email),
|
||||
?assertEqual(Admin#admin.id, Found#admin.id),
|
||||
?assertEqual({error, not_found}, core_admin:get_by_email(<<"nope@test.local">>)).
|
||||
|
||||
test_list_all() ->
|
||||
?assertEqual([], core_admin:list_all()),
|
||||
{ok, _} = core_admin:create(<<"a1@test.local">>, <<"pass">>, admin),
|
||||
{ok, _} = core_admin:create(<<"a2@test.local">>, <<"pass">>, moderator),
|
||||
List = core_admin:list_all(),
|
||||
?assertEqual(2, length(List)).
|
||||
|
||||
test_update() ->
|
||||
{ok, Admin} = core_admin:create(<<"upd@test.local">>, <<"pass">>, admin),
|
||||
{ok, Updated} = core_admin:update(Admin#admin.id, [
|
||||
{nickname, <<"nick">>},
|
||||
{phone, <<"+7999">>},
|
||||
{timezone, <<"Europe/Moscow">>}
|
||||
]),
|
||||
?assertEqual(<<"nick">>, Updated#admin.nickname),
|
||||
?assertEqual(<<"+7999">>, Updated#admin.phone),
|
||||
?assertEqual(<<"Europe/Moscow">>, Updated#admin.timezone),
|
||||
?assertEqual({error, not_found}, core_admin:update(<<"missing">>, [{nickname, <<"x">>}])).
|
||||
|
||||
test_update_role() ->
|
||||
{ok, Admin} = core_admin:create(<<"role@test.local">>, <<"pass">>, admin),
|
||||
{ok, Updated} = core_admin:update_role(Admin#admin.id, superadmin),
|
||||
?assertEqual(superadmin, Updated#admin.role),
|
||||
{ok, Loaded} = core_admin:get_by_id(Admin#admin.id),
|
||||
?assertEqual(superadmin, Loaded#admin.role),
|
||||
?assertEqual({error, not_found}, core_admin:update_role(<<"missing">>, admin)).
|
||||
|
||||
test_block_unblock() ->
|
||||
{ok, Admin} = core_admin:create(<<"block@test.local">>, <<"pass">>, admin),
|
||||
{ok, Blocked} = core_admin:block(Admin#admin.id),
|
||||
?assertEqual(blocked, Blocked#admin.status),
|
||||
{ok, Active} = core_admin:unblock(Admin#admin.id),
|
||||
?assertEqual(active, Active#admin.status),
|
||||
?assertEqual({error, not_found}, core_admin:block(<<"missing">>)),
|
||||
?assertEqual({error, not_found}, core_admin:unblock(<<"missing">>)).
|
||||
|
||||
test_update_last_login() ->
|
||||
{ok, Admin} = core_admin:create(<<"login@test.local">>, <<"pass">>, admin),
|
||||
OldLogin = Admin#admin.last_login,
|
||||
{ok, Updated} = core_admin:update_last_login(Admin#admin.id),
|
||||
?assert(Updated#admin.last_login >= OldLogin),
|
||||
?assertEqual({error, not_found}, core_admin:update_last_login(<<"missing">>)).
|
||||
|
||||
test_delete() ->
|
||||
{ok, Admin} = core_admin:create(<<"del@test.local">>, <<"pass">>, admin),
|
||||
?assertEqual({ok, deleted}, core_admin:delete(Admin#admin.id)),
|
||||
?assertEqual({error, not_found}, core_admin:get_by_id(Admin#admin.id)),
|
||||
?assertEqual({error, not_found}, core_admin:delete(<<"missing">>)).
|
||||
@@ -2,125 +2,84 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [booking]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(booking, [
|
||||
{attributes, record_info(fields, booking)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(booking),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
core_booking_test_() ->
|
||||
{foreach,
|
||||
fun setup/0,
|
||||
fun cleanup/1,
|
||||
[
|
||||
{"Create booking test", fun test_create_booking/0},
|
||||
{"Get booking by id test", fun test_get_by_id/0},
|
||||
{"Get booking by event and user test", fun test_get_by_event_and_user/0},
|
||||
{"List bookings by event test", fun test_list_by_event/0},
|
||||
{"List bookings by user test", fun test_list_by_user/0},
|
||||
{"Update booking status test", fun test_update_status/0},
|
||||
{"Delete booking test", fun test_delete_booking/0}
|
||||
]}.
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"Create booking test", fun test_create_booking/0},
|
||||
{"Get booking by id test", fun test_get_by_id/0},
|
||||
{"Get booking by event and user test", fun test_get_by_event_and_user/0},
|
||||
{"List bookings by event test", fun test_list_by_event/0},
|
||||
{"List bookings by user test", fun test_list_by_user/0},
|
||||
{"Update booking status test", fun test_update_status/0},
|
||||
{"Delete booking test", fun test_delete_booking/0}
|
||||
]}.
|
||||
|
||||
test_create_booking() ->
|
||||
EventId = <<"event123">>,
|
||||
UserId = <<"user123">>,
|
||||
|
||||
{ok, Booking} = core_booking:create(EventId, UserId),
|
||||
|
||||
{ok, Booking} = core_booking:create(EventId, UserId, pending),
|
||||
?assertEqual(EventId, Booking#booking.event_id),
|
||||
?assertEqual(UserId, Booking#booking.user_id),
|
||||
?assertEqual(pending, Booking#booking.status),
|
||||
?assertEqual(undefined, Booking#booking.confirmed_at),
|
||||
?assert(is_binary(Booking#booking.id)),
|
||||
?assert(Booking#booking.created_at =/= undefined),
|
||||
?assert(Booking#booking.updated_at =/= undefined).
|
||||
|
||||
test_get_by_id() ->
|
||||
EventId = <<"event123">>,
|
||||
UserId = <<"user123">>,
|
||||
{ok, Booking} = core_booking:create(EventId, UserId),
|
||||
|
||||
{ok, Booking} = core_booking:create(<<"event123">>, <<"user123">>, pending),
|
||||
{ok, Found} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(Booking#booking.id, Found#booking.id),
|
||||
|
||||
{error, not_found} = core_booking:get_by_id(<<"nonexistent">>).
|
||||
|
||||
test_get_by_event_and_user() ->
|
||||
EventId = <<"event123">>,
|
||||
UserId1 = <<"user1">>,
|
||||
UserId2 = <<"user2">>,
|
||||
|
||||
{ok, Booking1} = core_booking:create(EventId, UserId1),
|
||||
{ok, _Booking2} = core_booking:create(EventId, UserId2),
|
||||
|
||||
{ok, Found} = core_booking:get_by_event_and_user(EventId, UserId1),
|
||||
{ok, Booking1} = core_booking:create(EventId, <<"user1">>, pending),
|
||||
{ok, _} = core_booking:create(EventId, <<"user2">>, pending),
|
||||
{ok, Found} = core_booking:get_by_event_and_user(EventId, <<"user1">>),
|
||||
?assertEqual(Booking1#booking.id, Found#booking.id),
|
||||
|
||||
{error, not_found} = core_booking:get_by_event_and_user(EventId, <<"user3">>).
|
||||
|
||||
test_list_by_event() ->
|
||||
EventId1 = <<"event1">>,
|
||||
EventId2 = <<"event2">>,
|
||||
UserId = <<"user123">>,
|
||||
|
||||
{ok, _} = core_booking:create(EventId1, UserId),
|
||||
{ok, _} = core_booking:create(EventId1, <<"user2">>),
|
||||
{ok, _} = core_booking:create(EventId2, UserId),
|
||||
|
||||
{ok, Bookings1} = core_booking:list_by_event(EventId1),
|
||||
{ok, _} = core_booking:create(<<"event1">>, <<"user123">>, pending),
|
||||
{ok, _} = core_booking:create(<<"event1">>, <<"user2">>, pending),
|
||||
{ok, _} = core_booking:create(<<"event2">>, <<"user123">>, pending),
|
||||
{ok, Bookings1} = core_booking:list_by_event(<<"event1">>),
|
||||
?assertEqual(2, length(Bookings1)),
|
||||
|
||||
{ok, Bookings2} = core_booking:list_by_event(EventId2),
|
||||
{ok, Bookings2} = core_booking:list_by_event(<<"event2">>),
|
||||
?assertEqual(1, length(Bookings2)).
|
||||
|
||||
test_list_by_user() ->
|
||||
EventId = <<"event123">>,
|
||||
UserId1 = <<"user1">>,
|
||||
UserId2 = <<"user2">>,
|
||||
|
||||
{ok, _} = core_booking:create(EventId, UserId1),
|
||||
{ok, _} = core_booking:create(EventId, UserId1),
|
||||
{ok, _} = core_booking:create(EventId, UserId2),
|
||||
|
||||
{ok, Bookings1} = core_booking:list_by_user(UserId1),
|
||||
{ok, _} = core_booking:create(<<"event123">>, <<"user1">>, pending),
|
||||
{ok, _} = core_booking:create(<<"event456">>, <<"user1">>, pending),
|
||||
{ok, _} = core_booking:create(<<"event123">>, <<"user2">>, pending),
|
||||
{ok, Bookings1} = core_booking:list_by_user(<<"user1">>),
|
||||
?assertEqual(2, length(Bookings1)),
|
||||
|
||||
{ok, Bookings2} = core_booking:list_by_user(UserId2),
|
||||
{ok, Bookings2} = core_booking:list_by_user(<<"user2">>),
|
||||
?assertEqual(1, length(Bookings2)).
|
||||
|
||||
test_update_status() ->
|
||||
EventId = <<"event123">>,
|
||||
UserId = <<"user123">>,
|
||||
{ok, Booking} = core_booking:create(EventId, UserId),
|
||||
|
||||
timer:sleep(2000), % 2 секунды
|
||||
|
||||
{ok, Confirmed} = core_booking:update_status(Booking#booking.id, confirmed),
|
||||
{ok, Booking} = core_booking:create(<<"event123">>, <<"user123">>, pending),
|
||||
Now = calendar:universal_time(),
|
||||
{ok, Confirmed} = core_booking:update(Booking#booking.id,
|
||||
[{status, confirmed}, {confirmed_at, Now}]),
|
||||
?assertEqual(confirmed, Confirmed#booking.status),
|
||||
?assert(Confirmed#booking.confirmed_at =/= undefined),
|
||||
?assert(Confirmed#booking.updated_at > Booking#booking.updated_at),
|
||||
|
||||
timer:sleep(2000), % 2 секунды
|
||||
|
||||
{ok, Cancelled} = core_booking:update_status(Booking#booking.id, cancelled),
|
||||
?assertEqual(Now, Confirmed#booking.confirmed_at),
|
||||
{ok, Cancelled} = core_booking:update(Booking#booking.id, [{status, cancelled}]),
|
||||
?assertEqual(cancelled, Cancelled#booking.status),
|
||||
?assert(Cancelled#booking.updated_at > Confirmed#booking.updated_at),
|
||||
|
||||
{error, not_found} = core_booking:update_status(<<"nonexistent">>, confirmed).
|
||||
{error, not_found} = core_booking:update(<<"nonexistent">>, [{status, confirmed}]).
|
||||
|
||||
test_delete_booking() ->
|
||||
EventId = <<"event123">>,
|
||||
UserId = <<"user123">>,
|
||||
{ok, Booking} = core_booking:create(EventId, UserId),
|
||||
|
||||
{ok, deleted} = core_booking:delete(Booking#booking.id),
|
||||
|
||||
{error, not_found} = core_booking:get_by_id(Booking#booking.id).
|
||||
{ok, Booking} = core_booking:create(<<"event123">>, <<"user123">>, pending),
|
||||
ok = core_booking:delete(Booking#booking.id),
|
||||
{error, not_found} = core_booking:get_by_id(Booking#booking.id).
|
||||
|
||||
@@ -2,22 +2,16 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [event, recurrence_exception]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(event, [
|
||||
{attributes, record_info(fields, event)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(recurrence_exception, [
|
||||
{attributes, record_info(fields, recurrence_exception)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(recurrence_exception),
|
||||
mnesia:delete_table(event),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
core_event_recurring_test_() ->
|
||||
@@ -31,10 +25,14 @@ core_event_recurring_test_() ->
|
||||
{"Materialize non-recurring test", fun test_materialize_non_recurring/0}
|
||||
]}.
|
||||
|
||||
add_days(DateTime, Days) ->
|
||||
Sec = calendar:datetime_to_gregorian_seconds(DateTime) + Days * 86400,
|
||||
calendar:gregorian_seconds_to_datetime(Sec).
|
||||
|
||||
test_create_recurring() ->
|
||||
CalendarId = <<"calendar123">>,
|
||||
Title = <<"Weekly Meeting">>,
|
||||
StartTime = {{2026, 4, 20}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
Duration = 60,
|
||||
RRule = #{<<"freq">> => <<"WEEKLY">>, <<"interval">> => 1},
|
||||
|
||||
@@ -46,7 +44,6 @@ test_create_recurring() ->
|
||||
?assertEqual(false, Event#event.is_instance),
|
||||
?assert(is_binary(Event#event.recurrence_rule)),
|
||||
|
||||
% Проверяем, что правило сохранилось
|
||||
Decoded = jsx:decode(Event#event.recurrence_rule, [return_maps]),
|
||||
RRuleMap = case Decoded of
|
||||
Map when is_map(Map) -> Map;
|
||||
@@ -56,8 +53,8 @@ test_create_recurring() ->
|
||||
|
||||
test_materialize_occurrence() ->
|
||||
CalendarId = <<"calendar123">>,
|
||||
MasterId = create_recurring_event(CalendarId),
|
||||
OccurrenceStart = {{2026, 4, 27}, {10, 0, 0}},
|
||||
{MasterId, StartTime} = create_recurring_event(CalendarId),
|
||||
OccurrenceStart = add_days(StartTime, 7),
|
||||
SpecialistId = <<"specialist123">>,
|
||||
|
||||
{ok, Instance} = core_event:materialize_occurrence(MasterId, OccurrenceStart, SpecialistId),
|
||||
@@ -71,15 +68,13 @@ test_materialize_occurrence() ->
|
||||
|
||||
test_materialize_existing() ->
|
||||
CalendarId = <<"calendar123">>,
|
||||
MasterId = create_recurring_event(CalendarId),
|
||||
OccurrenceStart = {{2026, 4, 27}, {10, 0, 0}},
|
||||
{MasterId, StartTime} = create_recurring_event(CalendarId),
|
||||
OccurrenceStart = add_days(StartTime, 7),
|
||||
SpecialistId1 = <<"specialist1">>,
|
||||
SpecialistId2 = <<"specialist2">>,
|
||||
|
||||
% Первая материализация
|
||||
{ok, Instance1} = core_event:materialize_occurrence(MasterId, OccurrenceStart, SpecialistId1),
|
||||
|
||||
% Вторая материализация - должна вернуть существующий экземпляр
|
||||
{ok, Instance2} = core_event:materialize_occurrence(MasterId, OccurrenceStart, SpecialistId2),
|
||||
|
||||
?assertEqual(Instance1#event.id, Instance2#event.id),
|
||||
@@ -87,18 +82,20 @@ test_materialize_existing() ->
|
||||
|
||||
test_materialize_non_recurring() ->
|
||||
CalendarId = <<"calendar123">>,
|
||||
{ok, SingleEvent} = core_event:create(CalendarId, <<"Single">>, {{2026, 4, 20}, {10, 0, 0}}, 60),
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, SingleEvent} = core_event:create(CalendarId, <<"Single">>, StartTime, 60),
|
||||
|
||||
{error, not_recurring} = core_event:materialize_occurrence(
|
||||
SingleEvent#event.id, {{2026, 4, 27}, {10, 0, 0}}, <<"spec">>
|
||||
SingleEvent#event.id, add_days(StartTime, 7), <<"spec">>
|
||||
).
|
||||
|
||||
create_recurring_event(CalendarId) ->
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, Event} = core_event:create_recurring(
|
||||
CalendarId,
|
||||
<<"Weekly Meeting">>,
|
||||
{{2026, 4, 20}, {10, 0, 0}},
|
||||
StartTime,
|
||||
60,
|
||||
#{<<"freq">> => <<"WEEKLY">>, <<"interval">> => 1}
|
||||
),
|
||||
Event#event.id.
|
||||
{Event#event.id, StartTime}.
|
||||
|
||||
@@ -2,17 +2,16 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [event]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(event, [
|
||||
{attributes, record_info(fields, event)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(event),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
core_event_test_() ->
|
||||
@@ -27,10 +26,14 @@ core_event_test_() ->
|
||||
{"Delete event test", fun test_delete_event/0}
|
||||
]}.
|
||||
|
||||
add_days(DateTime, Days) ->
|
||||
Sec = calendar:datetime_to_gregorian_seconds(DateTime) + Days * 86400,
|
||||
calendar:gregorian_seconds_to_datetime(Sec).
|
||||
|
||||
test_create_event() ->
|
||||
CalendarId = <<"calendar123">>,
|
||||
Title = <<"Test Event">>,
|
||||
StartTime = {{2026, 4, 25}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
Duration = 60,
|
||||
|
||||
{ok, Event} = core_event:create(CalendarId, Title, StartTime, Duration),
|
||||
@@ -48,7 +51,7 @@ test_create_event() ->
|
||||
|
||||
test_get_by_id() ->
|
||||
CalendarId = <<"calendar123">>,
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Test">>, {{2026, 4, 25}, {10, 0, 0}}, 60),
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Test">>, eh_test_support:future_start(), 60),
|
||||
|
||||
{ok, Found} = core_event:get_by_id(Event#event.id),
|
||||
?assertEqual(Event#event.id, Found#event.id),
|
||||
@@ -58,10 +61,11 @@ test_get_by_id() ->
|
||||
test_list_by_calendar() ->
|
||||
CalendarId1 = <<"calendar1">>,
|
||||
CalendarId2 = <<"calendar2">>,
|
||||
Base = eh_test_support:future_start(),
|
||||
|
||||
{ok, _} = core_event:create(CalendarId1, <<"Event 1">>, {{2026, 4, 25}, {10, 0, 0}}, 60),
|
||||
{ok, _} = core_event:create(CalendarId1, <<"Event 2">>, {{2026, 4, 26}, {11, 0, 0}}, 90),
|
||||
{ok, _} = core_event:create(CalendarId2, <<"Event 3">>, {{2026, 4, 27}, {12, 0, 0}}, 30),
|
||||
{ok, _} = core_event:create(CalendarId1, <<"Event 1">>, Base, 60),
|
||||
{ok, _} = core_event:create(CalendarId1, <<"Event 2">>, add_days(Base, 1), 90),
|
||||
{ok, _} = core_event:create(CalendarId2, <<"Event 3">>, add_days(Base, 2), 30),
|
||||
|
||||
{ok, Events1} = core_event:list_by_calendar(CalendarId1),
|
||||
?assertEqual(2, length(Events1)),
|
||||
@@ -74,7 +78,7 @@ test_list_by_calendar() ->
|
||||
|
||||
test_update_event() ->
|
||||
CalendarId = <<"calendar123">>,
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Original">>, {{2026, 4, 25}, {10, 0, 0}}, 60),
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Original">>, eh_test_support:future_start(), 60),
|
||||
timer:sleep(2000),
|
||||
Updates = [{title, <<"Updated">>}, {capacity, 100}, {description, <<"New desc">>}],
|
||||
{ok, Updated} = core_event:update(Event#event.id, Updates),
|
||||
@@ -88,11 +92,10 @@ test_update_event() ->
|
||||
|
||||
test_delete_event() ->
|
||||
CalendarId = <<"calendar123">>,
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Test">>, {{2026, 4, 25}, {10, 0, 0}}, 60),
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Test">>, eh_test_support:future_start(), 60),
|
||||
|
||||
{ok, Deleted} = core_event:delete(Event#event.id),
|
||||
?assertEqual(deleted, Deleted#event.status),
|
||||
|
||||
% Удалённое событие не возвращается в списке активных
|
||||
{ok, ActiveEvents} = core_event:list_by_calendar(CalendarId),
|
||||
?assertEqual(0, length(ActiveEvents)).
|
||||
?assertEqual(0, length(ActiveEvents)).
|
||||
|
||||
@@ -2,100 +2,70 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [report]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(report, [
|
||||
{attributes, record_info(fields, report)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(report),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
core_report_test_() ->
|
||||
{foreach,
|
||||
fun setup/0,
|
||||
fun cleanup/1,
|
||||
[
|
||||
{"Create report test", fun test_create_report/0},
|
||||
{"Get report by id test", fun test_get_by_id/0},
|
||||
{"List reports by target test", fun test_list_by_target/0},
|
||||
{"List reports by reporter test", fun test_list_by_reporter/0},
|
||||
{"Update report status test", fun test_update_status/0},
|
||||
{"Get count by target test", fun test_get_count_by_target/0}
|
||||
]}.
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"Create report test", fun test_create_report/0},
|
||||
{"Get report by id test", fun test_get_by_id/0},
|
||||
{"List reports by target test", fun test_list_by_target/0},
|
||||
{"List reports by reporter test", fun test_list_by_reporter/0},
|
||||
{"Update report status test", fun test_update_status/0},
|
||||
{"Get count by target test", fun test_get_count_by_target/0}
|
||||
]}.
|
||||
|
||||
test_create_report() ->
|
||||
ReporterId = <<"user123">>,
|
||||
TargetType = event,
|
||||
TargetId = <<"event123">>,
|
||||
Reason = <<"Inappropriate content">>,
|
||||
|
||||
{ok, Report} = core_report:create(ReporterId, TargetType, TargetId, Reason),
|
||||
|
||||
?assertEqual(ReporterId, Report#report.reporter_id),
|
||||
?assertEqual(TargetType, Report#report.target_type),
|
||||
?assertEqual(TargetId, Report#report.target_id),
|
||||
?assertEqual(Reason, Report#report.reason),
|
||||
{ok, Report} = core_report:create(<<"user123">>, event, <<"event123">>,
|
||||
<<"Inappropriate content">>),
|
||||
?assertEqual(<<"user123">>, Report#report.reporter_id),
|
||||
?assertEqual(event, Report#report.target_type),
|
||||
?assertEqual(<<"event123">>, Report#report.target_id),
|
||||
?assertEqual(<<"Inappropriate content">>, Report#report.reason),
|
||||
?assertEqual(pending, Report#report.status),
|
||||
?assertEqual(undefined, Report#report.resolved_at),
|
||||
?assertEqual(undefined, Report#report.resolved_by),
|
||||
?assert(is_binary(Report#report.id)).
|
||||
|
||||
test_get_by_id() ->
|
||||
ReporterId = <<"user123">>,
|
||||
{ok, Report} = core_report:create(ReporterId, event, <<"ev1">>, <<"Bad">>),
|
||||
|
||||
{ok, Report} = core_report:create(<<"user123">>, event, <<"ev1">>, <<"Bad">>),
|
||||
{ok, Found} = core_report:get_by_id(Report#report.id),
|
||||
?assertEqual(Report#report.id, Found#report.id),
|
||||
|
||||
{error, not_found} = core_report:get_by_id(<<"nonexistent">>).
|
||||
|
||||
test_list_by_target() ->
|
||||
User1 = <<"user1">>,
|
||||
User2 = <<"user2">>,
|
||||
EventId = <<"event123">>,
|
||||
|
||||
{ok, _} = core_report:create(User1, event, EventId, <<"Reason 1">>),
|
||||
{ok, _} = core_report:create(User2, event, EventId, <<"Reason 2">>),
|
||||
{ok, _} = core_report:create(User1, calendar, <<"cal1">>, <<"Reason 3">>),
|
||||
|
||||
{ok, Reports} = core_report:list_by_target(event, EventId),
|
||||
{ok, _} = core_report:create(<<"user1">>, event, EventId, <<"Reason 1">>),
|
||||
{ok, _} = core_report:create(<<"user2">>, event, EventId, <<"Reason 2">>),
|
||||
{ok, _} = core_report:create(<<"user1">>, calendar, <<"cal1">>, <<"Reason 3">>),
|
||||
Reports = core_report:list_by_target(event, EventId),
|
||||
?assertEqual(2, length(Reports)).
|
||||
|
||||
test_list_by_reporter() ->
|
||||
User1 = <<"user1">>,
|
||||
User2 = <<"user2">>,
|
||||
|
||||
{ok, _} = core_report:create(User1, event, <<"ev1">>, <<"">>),
|
||||
{ok, _} = core_report:create(User1, event, <<"ev2">>, <<"">>),
|
||||
{ok, _} = core_report:create(User2, event, <<"ev3">>, <<"">>),
|
||||
|
||||
{ok, Reports} = core_report:list_by_reporter(User1),
|
||||
{ok, _} = core_report:create(<<"user1">>, event, <<"ev1">>, <<"">>),
|
||||
{ok, _} = core_report:create(<<"user1">>, event, <<"ev2">>, <<"">>),
|
||||
{ok, _} = core_report:create(<<"user2">>, event, <<"ev3">>, <<"">>),
|
||||
Reports = core_report:list_by_reporter(<<"user1">>),
|
||||
?assertEqual(2, length(Reports)).
|
||||
|
||||
test_update_status() ->
|
||||
ReporterId = <<"user123">>,
|
||||
AdminId = <<"admin123">>,
|
||||
{ok, Report} = core_report:create(ReporterId, event, <<"ev1">>, <<"">>),
|
||||
|
||||
{ok, Updated} = core_report:update_status(Report#report.id, reviewed, AdminId),
|
||||
{ok, Report} = core_report:create(<<"user123">>, event, <<"ev1">>, <<"">>),
|
||||
{ok, Updated} = core_report:update_status(Report#report.id, reviewed, <<"admin123">>),
|
||||
?assertEqual(reviewed, Updated#report.status),
|
||||
?assertEqual(AdminId, Updated#report.resolved_by),
|
||||
?assertEqual(<<"admin123">>, Updated#report.resolved_by),
|
||||
?assert(Updated#report.resolved_at =/= undefined).
|
||||
|
||||
test_get_count_by_target() ->
|
||||
User1 = <<"user1">>,
|
||||
User2 = <<"user2">>,
|
||||
EventId = <<"event123">>,
|
||||
|
||||
?assertEqual(0, core_report:get_count_by_target(event, EventId)),
|
||||
|
||||
{ok, _} = core_report:create(User1, event, EventId, <<"">>),
|
||||
{ok, _} = core_report:create(<<"user1">>, event, EventId, <<"">>),
|
||||
?assertEqual(1, core_report:get_count_by_target(event, EventId)),
|
||||
|
||||
{ok, _} = core_report:create(User2, event, EventId, <<"">>),
|
||||
?assertEqual(2, core_report:get_count_by_target(event, EventId)).
|
||||
{ok, _} = core_report:create(<<"user2">>, event, EventId, <<"">>),
|
||||
?assertEqual(2, core_report:get_count_by_target(event, EventId)).
|
||||
|
||||
@@ -2,17 +2,16 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [review]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(review, [
|
||||
{attributes, record_info(fields, review)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(review),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
core_review_test_() ->
|
||||
@@ -103,11 +102,13 @@ test_hide_unhide() ->
|
||||
UserId = <<"user123">>,
|
||||
{ok, Review} = core_review:create(UserId, event, <<"ev1">>, 4, <<"">>),
|
||||
|
||||
{ok, Hidden} = core_review:hide(Review#review.id),
|
||||
{ok, Hidden} = core_review:hide(Review#review.id, <<"spam">>),
|
||||
?assertEqual(hidden, Hidden#review.status),
|
||||
?assertEqual(<<"spam">>, Hidden#review.reason),
|
||||
|
||||
{ok, Unhidden} = core_review:unhide(Review#review.id),
|
||||
?assertEqual(visible, Unhidden#review.status).
|
||||
{ok, Unhidden} = core_review:unhide(Review#review.id, <<"restored">>),
|
||||
?assertEqual(visible, Unhidden#review.status),
|
||||
?assertEqual(<<"restored">>, Unhidden#review.reason).
|
||||
|
||||
test_average_rating() ->
|
||||
UserId1 = <<"user1">>,
|
||||
@@ -128,4 +129,4 @@ test_has_user_reviewed() ->
|
||||
?assertNot(core_review:has_user_reviewed(UserId, event, EventId)),
|
||||
|
||||
{ok, _} = core_review:create(UserId, event, EventId, 5, <<"">>),
|
||||
?assert(core_review:has_user_reviewed(UserId, event, EventId)).
|
||||
?assert(core_review:has_user_reviewed(UserId, event, EventId)).
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
-module(core_user_tests).
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [user]).
|
||||
|
||||
setup() ->
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
case mnesia:add_table_index(user, email) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, {already_exists, user, email}} -> ok;
|
||||
{aborted, Reason} -> error({add_index_failed, Reason})
|
||||
end,
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
eh_test_support:clear_tables(?TABLES),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
core_user_test_() ->
|
||||
{foreach, fun setup/0, fun cleanup/1, [
|
||||
{"Create user", fun test_create/0},
|
||||
{"Create user – email exists", fun test_create_email_exists/0},
|
||||
{"Get by id", fun test_get_by_id/0},
|
||||
{"Get by email / email_exists", fun test_get_by_email/0},
|
||||
{"List all and list_users", fun test_list/0},
|
||||
{"Update fields", fun test_update/0},
|
||||
{"Update status", fun test_update_status/0},
|
||||
{"Update last login", fun test_update_last_login/0},
|
||||
{"Block and unblock", fun test_block_unblock/0},
|
||||
{"Soft delete", fun test_delete/0},
|
||||
{"Count helpers", fun test_counts/0},
|
||||
{"Create and delete bot", fun test_bot/0}
|
||||
]}.
|
||||
|
||||
test_create() ->
|
||||
Email = <<"user1@test.local">>,
|
||||
{ok, User} = core_user:create(Email, <<"secret123">>),
|
||||
?assertEqual(Email, User#user.email),
|
||||
?assertEqual(user, User#user.role),
|
||||
?assertEqual(pending, User#user.status),
|
||||
?assertEqual(<<"user1">>, User#user.nickname),
|
||||
?assert(is_binary(User#user.id)),
|
||||
?assert(is_binary(User#user.password_hash)),
|
||||
?assert(User#user.password_hash =/= <<"secret123">>).
|
||||
|
||||
test_create_email_exists() ->
|
||||
Email = <<"dup@test.local">>,
|
||||
{ok, _} = core_user:create(Email, <<"pass1">>),
|
||||
?assertEqual({error, email_exists}, core_user:create(Email, <<"pass2">>)).
|
||||
|
||||
test_get_by_id() ->
|
||||
{ok, User} = core_user:create(<<"byid@test.local">>, <<"pass">>),
|
||||
{ok, Found} = core_user:get_by_id(User#user.id),
|
||||
?assertEqual(User#user.id, Found#user.id),
|
||||
?assertEqual({error, not_found}, core_user:get_by_id(<<"missing">>)).
|
||||
|
||||
test_get_by_email() ->
|
||||
Email = <<"byemail@test.local">>,
|
||||
{ok, User} = core_user:create(Email, <<"pass">>),
|
||||
{ok, Found} = core_user:get_by_email(Email),
|
||||
?assertEqual(User#user.id, Found#user.id),
|
||||
?assertEqual(true, core_user:email_exists(Email)),
|
||||
?assertEqual(false, core_user:email_exists(<<"nope@test.local">>)),
|
||||
?assertEqual({error, not_found}, core_user:get_by_email(<<"nope@test.local">>)).
|
||||
|
||||
test_list() ->
|
||||
?assertEqual([], core_user:list_all()),
|
||||
{ok, U1} = core_user:create(<<"a@test.local">>, <<"pass">>),
|
||||
{ok, U2} = core_user:create(<<"b@test.local">>, <<"pass">>),
|
||||
?assertEqual(2, length(core_user:list_all())),
|
||||
{ok, _} = core_user:delete(U1#user.id),
|
||||
All = core_user:list_all(),
|
||||
?assertEqual(2, length(All)),
|
||||
{ok, ActiveMaps} = core_user:list_users(),
|
||||
?assertEqual(1, length(ActiveMaps)),
|
||||
[Map] = ActiveMaps,
|
||||
?assertEqual(U2#user.id, maps:get(id, Map)),
|
||||
?assertEqual(<<"b@test.local">>, maps:get(email, Map)).
|
||||
|
||||
test_update() ->
|
||||
{ok, User} = core_user:create(<<"upd@test.local">>, <<"pass">>),
|
||||
{ok, Updated} = core_user:update(User#user.id, [
|
||||
{nickname, <<"nick">>},
|
||||
{phone, <<"+7000">>},
|
||||
{timezone, <<"UTC">>},
|
||||
{status, active}
|
||||
]),
|
||||
?assertEqual(<<"nick">>, Updated#user.nickname),
|
||||
?assertEqual(<<"+7000">>, Updated#user.phone),
|
||||
?assertEqual(<<"UTC">>, Updated#user.timezone),
|
||||
?assertEqual(active, Updated#user.status),
|
||||
?assertEqual({error, not_found}, core_user:update(<<"missing">>, [{nickname, <<"x">>}])).
|
||||
|
||||
test_update_status() ->
|
||||
{ok, User} = core_user:create(<<"st@test.local">>, <<"pass">>),
|
||||
{ok, Updated} = core_user:update_status(User#user.id, frozen, <<"review">>),
|
||||
?assertEqual(frozen, Updated#user.status),
|
||||
?assertEqual(<<"review">>, Updated#user.reason),
|
||||
?assertEqual({error, not_found},
|
||||
core_user:update_status(<<"missing">>, active, <<>>)).
|
||||
|
||||
test_update_last_login() ->
|
||||
{ok, User} = core_user:create(<<"login@test.local">>, <<"pass">>),
|
||||
OldLogin = User#user.last_login,
|
||||
{ok, Updated} = core_user:update_last_login(User#user.id),
|
||||
?assert(Updated#user.last_login >= OldLogin),
|
||||
?assertEqual({error, not_found}, core_user:update_last_login(<<"missing">>)).
|
||||
|
||||
test_block_unblock() ->
|
||||
{ok, User} = core_user:create(<<"block@test.local">>, <<"pass">>),
|
||||
{ok, Blocked} = core_user:block(User#user.id, <<"spam">>),
|
||||
?assertEqual(blocked, Blocked#user.status),
|
||||
?assertEqual(<<"spam">>, Blocked#user.reason),
|
||||
{ok, Active} = core_user:unblock(User#user.id, <<"ok">>),
|
||||
?assertEqual(active, Active#user.status),
|
||||
?assertEqual(<<"ok">>, Active#user.reason),
|
||||
?assertEqual({error, not_found}, core_user:block(<<"missing">>, <<"x">>)),
|
||||
?assertEqual({error, not_found}, core_user:unblock(<<"missing">>, <<"x">>)).
|
||||
|
||||
test_delete() ->
|
||||
{ok, User} = core_user:create(<<"del@test.local">>, <<"pass">>),
|
||||
{ok, Deleted} = core_user:delete(User#user.id),
|
||||
?assertEqual(deleted, Deleted#user.status),
|
||||
{ok, StillThere} = core_user:get_by_id(User#user.id),
|
||||
?assertEqual(deleted, StillThere#user.status),
|
||||
?assertEqual({error, not_found}, core_user:delete(<<"missing">>)).
|
||||
|
||||
test_counts() ->
|
||||
{ok, U1} = core_user:create(<<"c1@test.local">>, <<"pass">>),
|
||||
{ok, _} = core_user:create(<<"c2@test.local">>, <<"pass">>),
|
||||
{ok, _} = core_user:update_status(U1#user.id, active, <<>>),
|
||||
?assertEqual(2, core_user:count_users()),
|
||||
?assertEqual(1, core_user:count_pending_users()),
|
||||
ByRole = core_user:count_users_by_role(),
|
||||
?assertEqual(2, proplists:get_value(user, ByRole)),
|
||||
ByStatus = core_user:count_users_by_status(),
|
||||
?assertEqual(1, proplists:get_value(pending, ByStatus)),
|
||||
?assertEqual(1, proplists:get_value(active, ByStatus)),
|
||||
Now = calendar:universal_time(),
|
||||
{{Y, M, D}, _} = Now,
|
||||
From = {{Y, M, D}, {0, 0, 0}},
|
||||
To = {{Y, M, D}, {23, 59, 59}},
|
||||
ByDate = core_user:count_users_by_date(From, To),
|
||||
?assert(lists:keyfind({Y, M, D}, 1, ByDate) =/= false),
|
||||
PendingByDate = core_user:count_pending_users_by_date(From, To),
|
||||
{_, PendingCount} = lists:keyfind({Y, M, D}, 1, PendingByDate),
|
||||
?assertEqual(1, PendingCount).
|
||||
|
||||
test_bot() ->
|
||||
Email = <<"bot@test.local">>,
|
||||
{ok, Bot} = core_user:create_bot(Email, <<"botpass">>),
|
||||
?assertEqual(bot, Bot#user.role),
|
||||
?assertEqual(active, Bot#user.status),
|
||||
?assertEqual({error, email_exists}, core_user:create_bot(Email, <<"other">>)),
|
||||
?assertEqual(ok, core_user:delete_bot(Bot#user.id)),
|
||||
?assertEqual({error, not_found}, core_user:get_by_id(Bot#user.id)),
|
||||
?assertEqual({error, not_found}, core_user:delete_bot(<<"missing">>)).
|
||||
@@ -0,0 +1,356 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Shared fixtures for EventHub EUnit tests.
|
||||
%%% - In-memory Mnesia tables (ram_copies), isolated per suite
|
||||
%%% - Thin cowboy_req fake via meck (HTTP boundary only)
|
||||
%%% - Real JWT + admin seed for auth_admin without mocking domain
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(eh_test_support).
|
||||
|
||||
-include("records.hrl").
|
||||
|
||||
-export([
|
||||
start_mnesia/0,
|
||||
stop_mnesia/0,
|
||||
ensure_tables/1,
|
||||
clear_tables/1,
|
||||
delete_tables/1,
|
||||
seed_admin/0,
|
||||
seed_admin/1,
|
||||
make_admin/1,
|
||||
make_user/1,
|
||||
ensure_jwt/0,
|
||||
admin_token/1,
|
||||
with_cowboy/1,
|
||||
update_cowboy/1,
|
||||
unload_cowboy/0,
|
||||
last_reply/0,
|
||||
last_status/0,
|
||||
last_body/0,
|
||||
last_json/0,
|
||||
call/2,
|
||||
days_from_now/1,
|
||||
future_start/0
|
||||
]).
|
||||
|
||||
-define(JWT_SECRET, <<"test-user-secret-key-32-byt!">>).
|
||||
-define(ADMIN_JWT_SECRET, <<"test-admin-secret-key-32-b">>).
|
||||
-define(DEFAULT_ADMIN_ID, <<"adm_test_1">>).
|
||||
-define(REQ_KEY, eh_test_req).
|
||||
-define(REPLY_KEY, eh_test_reply).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Mnesia
|
||||
%%%===================================================================
|
||||
|
||||
start_mnesia() ->
|
||||
application:load(mnesia),
|
||||
catch mnesia:stop(),
|
||||
Base = case os:getenv("TMPDIR") of
|
||||
false ->
|
||||
case os:getenv("TEMP") of
|
||||
false -> "/tmp";
|
||||
Temp -> Temp
|
||||
end;
|
||||
TmpDir -> TmpDir
|
||||
end,
|
||||
Tmp = filename:join(Base, "eh_eunit_" ++ integer_to_list(erlang:unique_integer([positive]))),
|
||||
ok = filelib:ensure_dir(filename:join(Tmp, "dummy")),
|
||||
application:set_env(mnesia, dir, Tmp),
|
||||
_ = mnesia:delete_schema([node()]),
|
||||
ok = mnesia:create_schema([node()]),
|
||||
case mnesia:start() of
|
||||
ok -> ok;
|
||||
{error, {already_started, mnesia}} -> ok;
|
||||
Other -> error({mnesia_start_failed, Other})
|
||||
end,
|
||||
ok.
|
||||
|
||||
stop_mnesia() ->
|
||||
catch mnesia:stop(),
|
||||
catch mnesia:delete_schema([node()]),
|
||||
ok.
|
||||
|
||||
ensure_tables(Tables) when is_list(Tables) ->
|
||||
lists:foreach(fun ensure_table/1, Tables),
|
||||
ok.
|
||||
|
||||
ensure_table(Table) ->
|
||||
case lists:member(Table, mnesia:system_info(tables)) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
case mnesia:create_table(Table, table_opts(Table)) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, {already_exists, Table}} -> ok;
|
||||
{aborted, Reason} -> error({create_table_failed, Table, Reason})
|
||||
end
|
||||
end.
|
||||
|
||||
clear_tables(Tables) when is_list(Tables) ->
|
||||
lists:foreach(fun(T) ->
|
||||
case lists:member(T, mnesia:system_info(tables)) of
|
||||
true -> {atomic, ok} = mnesia:clear_table(T);
|
||||
false -> ok
|
||||
end
|
||||
end, Tables),
|
||||
ok.
|
||||
|
||||
delete_tables(Tables) when is_list(Tables) ->
|
||||
lists:foreach(fun(T) -> catch mnesia:delete_table(T) end, Tables),
|
||||
ok.
|
||||
|
||||
table_opts(user) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, user)}];
|
||||
table_opts(admin) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, admin)}];
|
||||
table_opts(calendar) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, calendar)}];
|
||||
table_opts(calendar_share) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, calendar_share)}];
|
||||
table_opts(calendar_specialist) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, calendar_specialist)}];
|
||||
table_opts(event) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, event)}];
|
||||
table_opts(recurrence_exception) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, recurrence_exception)}];
|
||||
table_opts(booking) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, booking)}];
|
||||
table_opts(review) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, review)}];
|
||||
table_opts(report) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, report)}];
|
||||
table_opts(banned_word) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, banned_word)}];
|
||||
table_opts(ticket) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, ticket)}];
|
||||
table_opts(subscription) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, subscription)}];
|
||||
table_opts(admin_audit) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, admin_audit)}];
|
||||
table_opts(notification) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, notification)}];
|
||||
table_opts(stats) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, stats)}];
|
||||
table_opts(session) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, session)}];
|
||||
table_opts(verification) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, verification)}];
|
||||
table_opts(admin_session) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, admin_session)}];
|
||||
table_opts(auth_session) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, auth_session)}];
|
||||
table_opts(node_metric) ->
|
||||
[{ram_copies, [node()]}, {local_content, true},
|
||||
{attributes, record_info(fields, node_metric)}];
|
||||
table_opts(schema_migration) ->
|
||||
[{ram_copies, [node()]}, {attributes, record_info(fields, schema_migration)}].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Domain seeds
|
||||
%%%===================================================================
|
||||
|
||||
seed_admin() ->
|
||||
seed_admin(#{}).
|
||||
|
||||
seed_admin(Opts) when is_map(Opts) ->
|
||||
Admin = make_admin(Opts),
|
||||
ok = mnesia:dirty_write(Admin),
|
||||
Admin.
|
||||
|
||||
make_admin(Opts) when is_map(Opts) ->
|
||||
Now = calendar:universal_time(),
|
||||
#admin{
|
||||
id = maps:get(id, Opts, ?DEFAULT_ADMIN_ID),
|
||||
email = maps:get(email, Opts, <<"admin@test.local">>),
|
||||
password_hash = maps:get(password_hash, Opts, <<"hash">>),
|
||||
role = maps:get(role, Opts, admin),
|
||||
status = maps:get(status, Opts, active),
|
||||
nickname = maps:get(nickname, Opts, <<"admin">>),
|
||||
avatar_url = maps:get(avatar_url, Opts, default),
|
||||
timezone = maps:get(timezone, Opts, <<"UTC">>),
|
||||
language = maps:get(language, Opts, <<"ru">>),
|
||||
phone = maps:get(phone, Opts, <<>>),
|
||||
preferences = maps:get(preferences, Opts, #{}),
|
||||
last_login = maps:get(last_login, Opts, Now),
|
||||
created_at = maps:get(created_at, Opts, Now),
|
||||
updated_at = maps:get(updated_at, Opts, Now)
|
||||
}.
|
||||
|
||||
make_user(Opts) when is_map(Opts) ->
|
||||
Now = calendar:universal_time(),
|
||||
#user{
|
||||
id = maps:get(id, Opts, <<"user_test_1">>),
|
||||
email = maps:get(email, Opts, <<"user@test.local">>),
|
||||
password_hash = maps:get(password_hash, Opts, <<"hash">>),
|
||||
role = maps:get(role, Opts, user),
|
||||
status = maps:get(status, Opts, active),
|
||||
reason = maps:get(reason, Opts, <<>>),
|
||||
nickname = maps:get(nickname, Opts, <<"user">>),
|
||||
avatar_url = maps:get(avatar_url, Opts, default),
|
||||
timezone = maps:get(timezone, Opts, <<"UTC">>),
|
||||
language = maps:get(language, Opts, <<"ru">>),
|
||||
social_links = maps:get(social_links, Opts, []),
|
||||
phone = maps:get(phone, Opts, <<>>),
|
||||
preferences = maps:get(preferences, Opts, #{}),
|
||||
last_login = maps:get(last_login, Opts, Now),
|
||||
created_at = maps:get(created_at, Opts, Now),
|
||||
updated_at = maps:get(updated_at, Opts, Now)
|
||||
}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% JWT
|
||||
%%%===================================================================
|
||||
|
||||
ensure_jwt() ->
|
||||
application:set_env(eventhub, jwt_secret, ?JWT_SECRET),
|
||||
application:set_env(eventhub, admin_jwt_secret, ?ADMIN_JWT_SECRET),
|
||||
{ok, _} = application:ensure_all_started(jose),
|
||||
ok.
|
||||
|
||||
admin_token(AdminId) when is_binary(AdminId) ->
|
||||
ensure_jwt(),
|
||||
eventhub_auth:generate_admin_token(AdminId, <<"admin">>).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Cowboy request fake (meck at HTTP boundary only)
|
||||
%%%===================================================================
|
||||
|
||||
%% Opts:
|
||||
%% method :: binary()
|
||||
%% path :: binary()
|
||||
%% bindings :: #{atom() => binary() | undefined}
|
||||
%% qs :: [{binary(), binary()}]
|
||||
%% body :: binary()
|
||||
%% has_body :: boolean()
|
||||
%% headers :: #{binary() => binary()}
|
||||
%% peer :: inet:ip_address() | {inet:ip_address(), inet:port_number()}
|
||||
%% auth :: binary() AdminId | {bearer, Token} | none
|
||||
with_cowboy(Opts) when is_map(Opts) ->
|
||||
unload_cowboy(),
|
||||
ok = meck:new(cowboy_req, [non_strict]),
|
||||
State = normalize_req(Opts),
|
||||
put(?REQ_KEY, State),
|
||||
erase(?REPLY_KEY),
|
||||
install_cowboy_expects(),
|
||||
req.
|
||||
|
||||
update_cowboy(Opts) when is_map(Opts) ->
|
||||
Cur = case get(?REQ_KEY) of
|
||||
undefined -> #{};
|
||||
M when is_map(M) -> M
|
||||
end,
|
||||
put(?REQ_KEY, maps:merge(Cur, normalize_req(Opts))),
|
||||
erase(?REPLY_KEY),
|
||||
req.
|
||||
|
||||
unload_cowboy() ->
|
||||
catch meck:unload(cowboy_req),
|
||||
erase(?REQ_KEY),
|
||||
erase(?REPLY_KEY),
|
||||
ok.
|
||||
|
||||
last_reply() ->
|
||||
case get(?REPLY_KEY) of
|
||||
undefined -> error(no_reply);
|
||||
Reply -> Reply
|
||||
end.
|
||||
|
||||
last_status() ->
|
||||
{Status, _, _} = last_reply(),
|
||||
Status.
|
||||
|
||||
last_body() ->
|
||||
{_, _, Body} = last_reply(),
|
||||
Body.
|
||||
|
||||
last_json() ->
|
||||
jsx:decode(last_body(), [return_maps]).
|
||||
|
||||
call(Module, Opts) when is_atom(Module), is_map(Opts) ->
|
||||
Req = with_cowboy(Opts),
|
||||
{ok, _, _} = Module:init(Req, []),
|
||||
last_reply().
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal
|
||||
%%%===================================================================
|
||||
|
||||
normalize_req(Opts) ->
|
||||
Auth = maps:get(auth, Opts, none),
|
||||
Headers0 = maps:get(headers, Opts, #{<<"x-real-ip">> => <<"127.0.0.1">>}),
|
||||
{AuthHdr, Token} = case Auth of
|
||||
none ->
|
||||
{undefined, undefined};
|
||||
{bearer, T} when is_binary(T) ->
|
||||
{{bearer, T}, T};
|
||||
AdminId when is_binary(AdminId) ->
|
||||
T = admin_token(AdminId),
|
||||
{{bearer, T}, T}
|
||||
end,
|
||||
Headers = case AuthHdr of
|
||||
undefined -> Headers0;
|
||||
_ -> Headers0#{<<"authorization">> => <<"Bearer ", Token/binary>>}
|
||||
end,
|
||||
#{
|
||||
method => maps:get(method, Opts, <<"GET">>),
|
||||
path => maps:get(path, Opts, <<"/v1/admin">>),
|
||||
bindings => maps:get(bindings, Opts, #{}),
|
||||
qs => maps:get(qs, Opts, []),
|
||||
body => maps:get(body, Opts, <<>>),
|
||||
has_body => maps:get(has_body, Opts, maps:is_key(body, Opts)),
|
||||
headers => Headers,
|
||||
peer => maps:get(peer, Opts, {127, 0, 0, 1}),
|
||||
auth_parsed => AuthHdr
|
||||
}.
|
||||
|
||||
install_cowboy_expects() ->
|
||||
ok = meck:expect(cowboy_req, method, fun(_) ->
|
||||
maps:get(method, get(?REQ_KEY))
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, path, fun(_) ->
|
||||
maps:get(path, get(?REQ_KEY))
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, binding, fun(Key, _) ->
|
||||
maps:get(Key, maps:get(bindings, get(?REQ_KEY)), undefined)
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, parse_qs, fun(_) ->
|
||||
maps:get(qs, get(?REQ_KEY))
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, read_body, fun(Req) ->
|
||||
{ok, maps:get(body, get(?REQ_KEY)), Req}
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, has_body, fun(_) ->
|
||||
maps:get(has_body, get(?REQ_KEY))
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, header, fun(Name, _) ->
|
||||
maps:get(Name, maps:get(headers, get(?REQ_KEY)), undefined)
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, parse_header, fun
|
||||
(<<"authorization">>, _) ->
|
||||
maps:get(auth_parsed, get(?REQ_KEY), undefined);
|
||||
(_, _) ->
|
||||
undefined
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, peer, fun(_) ->
|
||||
maps:get(peer, get(?REQ_KEY))
|
||||
end),
|
||||
ok = meck:expect(cowboy_req, reply, fun(Code, Headers, Body, Req) ->
|
||||
put(?REPLY_KEY, {Code, Headers, Body}),
|
||||
Req
|
||||
end),
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Time helpers
|
||||
%%%===================================================================
|
||||
|
||||
%% @doc Calendar datetime N days from now (UTC).
|
||||
days_from_now(Days) when is_integer(Days) ->
|
||||
Sec = calendar:datetime_to_gregorian_seconds(calendar:universal_time())
|
||||
+ (Days * 86400),
|
||||
calendar:gregorian_seconds_to_datetime(Sec).
|
||||
|
||||
%% @doc Convenient future start (~30 days ahead, noon UTC).
|
||||
future_start() ->
|
||||
{{Y, M, D}, _} = days_from_now(30),
|
||||
{{Y, M, D}, {12, 0, 0}}.
|
||||
@@ -2,34 +2,16 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TEST_EMAIL, <<"test@example.com">>).
|
||||
-define(TABLES, [user, calendar, event, booking]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(user, [
|
||||
{attributes, record_info(fields, user)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(calendar, [
|
||||
{attributes, record_info(fields, calendar)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(event, [
|
||||
{attributes, record_info(fields, event)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(booking, [
|
||||
{attributes, record_info(fields, booking)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(booking),
|
||||
mnesia:delete_table(event),
|
||||
mnesia:delete_table(calendar),
|
||||
mnesia:delete_table(user),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
logic_booking_test_() ->
|
||||
@@ -37,16 +19,15 @@ logic_booking_test_() ->
|
||||
fun setup/0,
|
||||
fun cleanup/1,
|
||||
[
|
||||
{"Create booking with auto confirmation", fun test_create_booking_auto/0},
|
||||
{"Create booking with manual confirmation", fun test_create_booking_manual/0},
|
||||
{"Create booking with timeout confirmation", fun test_create_booking_timeout/0},
|
||||
{"Create booking returns pending", fun test_create_booking_pending/0},
|
||||
{"Create duplicate booking", fun test_create_duplicate_booking/0},
|
||||
{"Create booking for inactive event", fun test_booking_inactive_event/0},
|
||||
{"Create booking for missing event", fun test_booking_missing_event/0},
|
||||
{"Create booking when event is full", fun test_booking_event_full/0},
|
||||
{"Confirm booking by owner", fun test_confirm_booking/0},
|
||||
{"Decline booking by owner", fun test_decline_booking/0},
|
||||
{"Pending bookings do not fill capacity", fun test_pending_does_not_fill/0},
|
||||
{"Confirm booking", fun test_confirm_booking/0},
|
||||
{"Confirm non-pending booking denied", fun test_confirm_non_pending/0},
|
||||
{"Cancel booking by participant", fun test_cancel_booking/0},
|
||||
{"Unauthorized confirm attempt", fun test_unauthorized_confirm/0},
|
||||
{"Cancel booking access denied", fun test_cancel_access_denied/0},
|
||||
{"List event bookings", fun test_list_event_bookings/0},
|
||||
{"List user bookings", fun test_list_user_bookings/0}
|
||||
]}.
|
||||
@@ -71,50 +52,28 @@ create_test_calendar(OwnerId, Confirmation) ->
|
||||
Calendar#calendar.id.
|
||||
|
||||
create_test_event(CalendarId) ->
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Test Event">>, StartTime, 60),
|
||||
Event#event.id.
|
||||
|
||||
create_test_event_with_capacity(CalendarId, Capacity) ->
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Test Event">>, StartTime, 60),
|
||||
{ok, Updated} = core_event:update(Event#event.id, [{capacity, Capacity}]),
|
||||
Updated#event.id.
|
||||
|
||||
%% Тесты
|
||||
test_create_booking_auto() ->
|
||||
test_create_booking_pending() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, auto),
|
||||
EventId = create_test_event(CalendarId),
|
||||
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
|
||||
timer:sleep(100),
|
||||
{ok, Updated} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(confirmed, Updated#booking.status).
|
||||
|
||||
test_create_booking_manual() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, manual),
|
||||
EventId = create_test_event(CalendarId),
|
||||
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
?assertEqual(pending, Booking#booking.status).
|
||||
|
||||
test_create_booking_timeout() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, {timeout, 1}),
|
||||
EventId = create_test_event(CalendarId),
|
||||
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
?assertEqual(pending, Booking#booking.status),
|
||||
|
||||
timer:sleep(1500),
|
||||
{ok, Updated} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(confirmed, Updated#booking.status).
|
||||
{ok, Stored} = core_booking:get_by_id(Booking#booking.id),
|
||||
?assertEqual(pending, Stored#booking.status).
|
||||
|
||||
test_create_duplicate_booking() ->
|
||||
OwnerId = create_test_user(user),
|
||||
@@ -125,25 +84,31 @@ test_create_duplicate_booking() ->
|
||||
{ok, _} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
{error, already_booked} = logic_booking:create_booking(ParticipantId, EventId).
|
||||
|
||||
test_booking_inactive_event() ->
|
||||
OwnerId = create_test_user(user),
|
||||
test_booking_missing_event() ->
|
||||
ParticipantId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, manual),
|
||||
EventId = create_test_event(CalendarId),
|
||||
|
||||
{ok, _} = core_event:update(EventId, [{status, cancelled}]),
|
||||
|
||||
{error, event_not_active} = logic_booking:create_booking(ParticipantId, EventId).
|
||||
{error, not_found} = logic_booking:create_booking(ParticipantId, <<"nonexistent">>).
|
||||
|
||||
test_booking_event_full() ->
|
||||
OwnerId = create_test_user(user),
|
||||
Participant1Id = create_test_user(user),
|
||||
Participant2Id = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, auto),
|
||||
CalendarId = create_test_calendar(OwnerId, manual),
|
||||
EventId = create_test_event_with_capacity(CalendarId, 1),
|
||||
|
||||
{ok, B1} = logic_booking:create_booking(Participant1Id, EventId),
|
||||
{ok, _} = logic_booking:confirm_booking(Participant1Id, B1#booking.id, confirm),
|
||||
{error, full} = logic_booking:create_booking(Participant2Id, EventId).
|
||||
|
||||
test_pending_does_not_fill() ->
|
||||
OwnerId = create_test_user(user),
|
||||
Participant1Id = create_test_user(user),
|
||||
Participant2Id = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, manual),
|
||||
EventId = create_test_event_with_capacity(CalendarId, 1),
|
||||
|
||||
{ok, _} = logic_booking:create_booking(Participant1Id, EventId),
|
||||
{error, event_full} = logic_booking:create_booking(Participant2Id, EventId).
|
||||
{ok, B2} = logic_booking:create_booking(Participant2Id, EventId),
|
||||
?assertEqual(pending, B2#booking.status).
|
||||
|
||||
test_confirm_booking() ->
|
||||
OwnerId = create_test_user(user),
|
||||
@@ -155,15 +120,15 @@ test_confirm_booking() ->
|
||||
{ok, Confirmed} = logic_booking:confirm_booking(OwnerId, Booking#booking.id, confirm),
|
||||
?assertEqual(confirmed, Confirmed#booking.status).
|
||||
|
||||
test_decline_booking() ->
|
||||
test_confirm_non_pending() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, manual),
|
||||
EventId = create_test_event(CalendarId),
|
||||
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
{ok, Declined} = logic_booking:confirm_booking(OwnerId, Booking#booking.id, decline),
|
||||
?assertEqual(cancelled, Declined#booking.status).
|
||||
{ok, _} = logic_booking:confirm_booking(OwnerId, Booking#booking.id, confirm),
|
||||
{error, access_denied} = logic_booking:confirm_booking(OwnerId, Booking#booking.id, confirm).
|
||||
|
||||
test_cancel_booking() ->
|
||||
OwnerId = create_test_user(user),
|
||||
@@ -172,10 +137,10 @@ test_cancel_booking() ->
|
||||
EventId = create_test_event(CalendarId),
|
||||
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
{ok, Cancelled} = logic_booking:cancel_booking(ParticipantId, Booking#booking.id),
|
||||
{ok, Cancelled} = logic_booking:cancel_booking(ParticipantId, Booking#booking.id, cancel),
|
||||
?assertEqual(cancelled, Cancelled#booking.status).
|
||||
|
||||
test_unauthorized_confirm() ->
|
||||
test_cancel_access_denied() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
OtherId = create_test_user(user),
|
||||
@@ -183,7 +148,7 @@ test_unauthorized_confirm() ->
|
||||
EventId = create_test_event(CalendarId),
|
||||
|
||||
{ok, Booking} = logic_booking:create_booking(ParticipantId, EventId),
|
||||
{error, access_denied} = logic_booking:confirm_booking(OtherId, Booking#booking.id, confirm).
|
||||
{error, access_denied} = logic_booking:cancel_booking(OtherId, Booking#booking.id, cancel).
|
||||
|
||||
test_list_event_bookings() ->
|
||||
OwnerId = create_test_user(user),
|
||||
@@ -195,7 +160,7 @@ test_list_event_bookings() ->
|
||||
{ok, _} = logic_booking:create_booking(Participant1Id, EventId),
|
||||
{ok, _} = logic_booking:create_booking(Participant2Id, EventId),
|
||||
|
||||
{ok, Bookings} = logic_booking:list_event_bookings(OwnerId, EventId),
|
||||
{ok, Bookings} = logic_booking:list_event_bookings(EventId),
|
||||
?assertEqual(2, length(Bookings)).
|
||||
|
||||
test_list_user_bookings() ->
|
||||
@@ -209,4 +174,4 @@ test_list_user_bookings() ->
|
||||
{ok, _} = logic_booking:create_booking(ParticipantId, EventId2),
|
||||
|
||||
{ok, Bookings} = logic_booking:list_user_bookings(ParticipantId),
|
||||
?assertEqual(2, length(Bookings)).
|
||||
?assertEqual(2, length(Bookings)).
|
||||
|
||||
@@ -2,32 +2,16 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [user, calendar, event, recurrence_exception]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(user, [
|
||||
{attributes, record_info(fields, user)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(calendar, [
|
||||
{attributes, record_info(fields, calendar)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(event, [
|
||||
{attributes, record_info(fields, event)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(recurrence_exception, [
|
||||
{attributes, record_info(fields, recurrence_exception)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(recurrence_exception),
|
||||
mnesia:delete_table(event),
|
||||
mnesia:delete_table(calendar),
|
||||
mnesia:delete_table(user),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
logic_event_recurring_test_() ->
|
||||
@@ -59,10 +43,14 @@ create_test_user_and_calendar() ->
|
||||
{ok, Calendar} = logic_calendar:create_calendar(UserId, <<"Test Calendar">>, <<"">>, manual),
|
||||
{UserId, Calendar#calendar.id}.
|
||||
|
||||
add_days(DateTime, Days) ->
|
||||
Sec = calendar:datetime_to_gregorian_seconds(DateTime) + Days * 86400,
|
||||
calendar:gregorian_seconds_to_datetime(Sec).
|
||||
|
||||
test_create_recurring_event() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
Title = <<"Weekly Meeting">>,
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
Duration = 60,
|
||||
RRule = #{<<"freq">> => <<"WEEKLY">>, <<"interval">> => 1},
|
||||
|
||||
@@ -75,7 +63,7 @@ test_create_recurring_event() ->
|
||||
test_create_recurring_invalid() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
Title = <<"Invalid">>,
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
Duration = 60,
|
||||
InvalidRRule = #{<<"freq">> => <<"YEARLY">>, <<"interval">> => 1},
|
||||
|
||||
@@ -85,43 +73,43 @@ test_create_recurring_invalid() ->
|
||||
|
||||
test_get_occurrences() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
RRule = #{<<"freq">> => <<"WEEKLY">>, <<"interval">> => 1},
|
||||
|
||||
{ok, Event} = logic_event:create_recurring_event(
|
||||
UserId, CalendarId, <<"Weekly">>, StartTime, 60, RRule
|
||||
),
|
||||
|
||||
RangeEnd = {{2026, 6, 29}, {10, 0, 0}},
|
||||
RangeEnd = add_days(StartTime, 28),
|
||||
{ok, Occurrences} = logic_event:get_occurrences(UserId, Event#event.id, RangeEnd),
|
||||
|
||||
?assertEqual(5, length(Occurrences)).
|
||||
|
||||
test_cancel_occurrence() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
RRule = #{<<"freq">> => <<"WEEKLY">>, <<"interval">> => 1},
|
||||
|
||||
{ok, Event} = logic_event:create_recurring_event(
|
||||
UserId, CalendarId, <<"Weekly">>, StartTime, 60, RRule
|
||||
),
|
||||
|
||||
CancelTime = {{2026, 6, 8}, {10, 0, 0}},
|
||||
CancelTime = add_days(StartTime, 7),
|
||||
{ok, cancelled} = logic_event:cancel_occurrence(UserId, Event#event.id, CancelTime).
|
||||
|
||||
test_occurrences_with_cancelled() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
RRule = #{<<"freq">> => <<"WEEKLY">>, <<"interval">> => 1},
|
||||
|
||||
{ok, Event} = logic_event:create_recurring_event(
|
||||
UserId, CalendarId, <<"Weekly">>, StartTime, 60, RRule
|
||||
),
|
||||
|
||||
CancelTime = {{2026, 6, 8}, {10, 0, 0}},
|
||||
CancelTime = add_days(StartTime, 7),
|
||||
{ok, cancelled} = logic_event:cancel_occurrence(UserId, Event#event.id, CancelTime),
|
||||
|
||||
RangeEnd = {{2026, 6, 29}, {10, 0, 0}},
|
||||
RangeEnd = add_days(StartTime, 28),
|
||||
{ok, Occurrences} = logic_event:get_occurrences(UserId, Event#event.id, RangeEnd),
|
||||
|
||||
?assertEqual(4, length(Occurrences)),
|
||||
@@ -131,14 +119,14 @@ test_occurrences_with_cancelled() ->
|
||||
|
||||
test_materialize_for_booking() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
RRule = #{<<"freq">> => <<"WEEKLY">>, <<"interval">> => 1},
|
||||
|
||||
{ok, Event} = logic_event:create_recurring_event(
|
||||
UserId, CalendarId, <<"Weekly">>, StartTime, 60, RRule
|
||||
),
|
||||
|
||||
OccurrenceStart = {{2026, 6, 8}, {10, 0, 0}},
|
||||
OccurrenceStart = add_days(StartTime, 7),
|
||||
SpecialistId = <<"specialist123">>,
|
||||
|
||||
{ok, Instance} = logic_event:materialize_for_booking(
|
||||
@@ -148,4 +136,4 @@ test_materialize_for_booking() ->
|
||||
?assertEqual(Event#event.id, Instance#event.master_id),
|
||||
?assertEqual(OccurrenceStart, Instance#event.start_time),
|
||||
?assertEqual(SpecialistId, Instance#event.specialist_id),
|
||||
?assertEqual(true, Instance#event.is_instance).
|
||||
?assertEqual(true, Instance#event.is_instance).
|
||||
|
||||
@@ -2,27 +2,16 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [user, calendar, event]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(user, [
|
||||
{attributes, record_info(fields, user)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(calendar, [
|
||||
{attributes, record_info(fields, calendar)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(event, [
|
||||
{attributes, record_info(fields, event)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(event),
|
||||
mnesia:delete_table(calendar),
|
||||
mnesia:delete_table(user),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
logic_event_test_() ->
|
||||
@@ -54,10 +43,14 @@ create_test_user_and_calendar() ->
|
||||
{ok, Calendar} = logic_calendar:create_calendar(UserId, <<"Test Calendar">>, <<"">>, manual),
|
||||
{UserId, Calendar#calendar.id}.
|
||||
|
||||
add_days(DateTime, Days) ->
|
||||
Sec = calendar:datetime_to_gregorian_seconds(DateTime) + Days * 86400,
|
||||
calendar:gregorian_seconds_to_datetime(Sec).
|
||||
|
||||
test_create_event() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
Title = <<"Test Event">>,
|
||||
StartTime = {{2026, 5, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
Duration = 60,
|
||||
|
||||
{ok, Event} = logic_event:create_event(UserId, CalendarId, Title, StartTime, Duration),
|
||||
@@ -67,22 +60,26 @@ test_create_event() ->
|
||||
|
||||
test_get_event() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
{ok, Event} = logic_event:create_event(UserId, CalendarId, <<"Test">>, {{2026, 5, 1}, {10, 0, 0}}, 60),
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, Event} = logic_event:create_event(UserId, CalendarId, <<"Test">>, StartTime, 60),
|
||||
|
||||
{ok, Found} = logic_event:get_event(UserId, Event#event.id),
|
||||
?assertEqual(Event#event.id, Found#event.id).
|
||||
|
||||
test_list_events() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
{ok, _} = logic_event:create_event(UserId, CalendarId, <<"Event 1">>, {{2026, 5, 1}, {10, 0, 0}}, 60),
|
||||
{ok, _} = logic_event:create_event(UserId, CalendarId, <<"Event 2">>, {{2026, 5, 2}, {11, 0, 0}}, 90),
|
||||
Start1 = eh_test_support:future_start(),
|
||||
Start2 = add_days(Start1, 1),
|
||||
{ok, _} = logic_event:create_event(UserId, CalendarId, <<"Event 1">>, Start1, 60),
|
||||
{ok, _} = logic_event:create_event(UserId, CalendarId, <<"Event 2">>, Start2, 90),
|
||||
|
||||
{ok, Events} = logic_event:list_events(UserId, CalendarId),
|
||||
?assertEqual(2, length(Events)).
|
||||
|
||||
test_update_event() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
{ok, Event} = logic_event:create_event(UserId, CalendarId, <<"Original">>, {{2026, 5, 1}, {10, 0, 0}}, 60),
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, Event} = logic_event:create_event(UserId, CalendarId, <<"Original">>, StartTime, 60),
|
||||
|
||||
Updates = [{title, <<"Updated">>}, {capacity, 50}],
|
||||
{ok, Updated} = logic_event:update_event(UserId, Event#event.id, Updates),
|
||||
@@ -91,7 +88,8 @@ test_update_event() ->
|
||||
|
||||
test_delete_event() ->
|
||||
{UserId, CalendarId} = create_test_user_and_calendar(),
|
||||
{ok, Event} = logic_event:create_event(UserId, CalendarId, <<"Test">>, {{2026, 5, 1}, {10, 0, 0}}, 60),
|
||||
StartTime = eh_test_support:future_start(),
|
||||
{ok, Event} = logic_event:create_event(UserId, CalendarId, <<"Test">>, StartTime, 60),
|
||||
|
||||
{ok, Deleted} = logic_event:delete_event(UserId, Event#event.id),
|
||||
?assertEqual(deleted, Deleted#event.status).
|
||||
@@ -102,5 +100,5 @@ test_time_validation() ->
|
||||
PastTime = {{2020, 1, 1}, {10, 0, 0}},
|
||||
{error, event_in_past} = logic_event:create_event(UserId, CalendarId, <<"Past">>, PastTime, 60),
|
||||
|
||||
FutureTime = {{2030, 1, 1}, {10, 0, 0}},
|
||||
?assertEqual(ok, logic_event:validate_event_time(FutureTime)).
|
||||
FutureTime = eh_test_support:future_start(),
|
||||
?assertEqual(ok, logic_event:validate_event_time(FutureTime)).
|
||||
|
||||
@@ -2,34 +2,20 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [user, admin, calendar, event, report, banned_word]).
|
||||
|
||||
%% ----------------------------------------------------------------
|
||||
%% Фикстуры
|
||||
%% ----------------------------------------------------------------
|
||||
setup() ->
|
||||
catch mnesia:stop(),
|
||||
case mnesia:start() of
|
||||
{atomic, ok} -> ok;
|
||||
ok -> ok
|
||||
end,
|
||||
{atomic, ok} = mnesia:create_table(user, [
|
||||
{attributes, record_info(fields, user)}, {ram_copies, [node()]}]),
|
||||
{atomic, ok} = mnesia:create_table(calendar, [
|
||||
{attributes, record_info(fields, calendar)}, {ram_copies, [node()]}]),
|
||||
{atomic, ok} = mnesia:create_table(event, [
|
||||
{attributes, record_info(fields, event)}, {ram_copies, [node()]}]),
|
||||
{atomic, ok} = mnesia:create_table(report, [
|
||||
{attributes, record_info(fields, report)}, {ram_copies, [node()]}]),
|
||||
{atomic, ok} = mnesia:create_table(banned_word, [
|
||||
{attributes, record_info(fields, banned_word)}, {ram_copies, [node()]}]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(banned_word),
|
||||
mnesia:delete_table(report),
|
||||
mnesia:delete_table(event),
|
||||
mnesia:delete_table(calendar),
|
||||
mnesia:delete_table(user),
|
||||
mnesia:stop().
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
%% ----------------------------------------------------------------
|
||||
%% Тесты
|
||||
@@ -48,33 +34,30 @@ logic_moderation_test_() ->
|
||||
]}.
|
||||
|
||||
%% ── Вспомогательные функции ──────────────────────────────
|
||||
create_test_user(Role) ->
|
||||
create_test_user() ->
|
||||
UserId = base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}),
|
||||
User = #user{
|
||||
id = UserId,
|
||||
email = <<>>,
|
||||
password_hash = <<"hash">>,
|
||||
role = Role,
|
||||
status = active,
|
||||
created_at = calendar:universal_time(),
|
||||
updated_at = calendar:universal_time()
|
||||
},
|
||||
User = eh_test_support:make_user(#{id => UserId, email => <<UserId/binary, "@test.com">>}),
|
||||
mnesia:dirty_write(User),
|
||||
UserId.
|
||||
|
||||
create_test_admin() ->
|
||||
AdminId = base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}),
|
||||
Admin = eh_test_support:seed_admin(#{id => AdminId, email => <<AdminId/binary, "@admin.test">>}),
|
||||
Admin#admin.id.
|
||||
|
||||
create_test_calendar(OwnerId) ->
|
||||
{ok, Calendar} = core_calendar:create(OwnerId, <<"Test">>, <<"">>, manual),
|
||||
Calendar#calendar.id.
|
||||
|
||||
create_test_event(CalendarId) ->
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Event">>,
|
||||
{{2026, 6, 1}, {10, 0, 0}}, 60),
|
||||
eh_test_support:future_start(), 60),
|
||||
Event#event.id.
|
||||
|
||||
%% ── Тесты ─────────────────────────────────────────────────
|
||||
test_create_report() ->
|
||||
ReporterId = create_test_user(user),
|
||||
OwnerId = create_test_user(user),
|
||||
ReporterId = create_test_user(),
|
||||
OwnerId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
{ok, Report} = logic_moderation:create_report(ReporterId, event, EventId, <<"Bad content">>),
|
||||
@@ -82,19 +65,19 @@ test_create_report() ->
|
||||
?assertEqual(pending, Report#report.status).
|
||||
|
||||
test_get_reports() ->
|
||||
AdminId = create_test_user(admin),
|
||||
ReporterId = create_test_user(user),
|
||||
OwnerId = create_test_user(user),
|
||||
AdminId = create_test_admin(),
|
||||
ReporterId = create_test_user(),
|
||||
OwnerId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
{ok, _} = logic_moderation:create_report(ReporterId, event, EventId, <<"">>),
|
||||
{ok, Reports} = logic_moderation:get_reports(AdminId),
|
||||
Reports = logic_moderation:get_reports(AdminId),
|
||||
?assertEqual(1, length(Reports)).
|
||||
|
||||
test_resolve_report() ->
|
||||
AdminId = create_test_user(admin),
|
||||
ReporterId = create_test_user(user),
|
||||
OwnerId = create_test_user(user),
|
||||
AdminId = create_test_admin(),
|
||||
ReporterId = create_test_user(),
|
||||
OwnerId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
{ok, Report} = logic_moderation:create_report(ReporterId, event, EventId, <<"">>),
|
||||
@@ -103,22 +86,22 @@ test_resolve_report() ->
|
||||
?assertEqual(AdminId, Resolved#report.resolved_by).
|
||||
|
||||
test_add_banned_word() ->
|
||||
AdminId = create_test_user(admin),
|
||||
AdminId = create_test_admin(),
|
||||
{ok, BW} = logic_moderation:add_banned_word(AdminId, <<"badword">>),
|
||||
?assertEqual(<<"badword">>, BW#banned_word.word),
|
||||
?assertEqual(AdminId, BW#banned_word.added_by).
|
||||
|
||||
test_remove_banned_word() ->
|
||||
AdminId = create_test_user(admin),
|
||||
AdminId = create_test_admin(),
|
||||
{ok, _} = logic_moderation:add_banned_word(AdminId, <<"badword">>),
|
||||
{ok, deleted} = logic_moderation:remove_banned_word(AdminId, <<"badword">>),
|
||||
?assertEqual([], core_banned_words:list_banned_words()).
|
||||
|
||||
test_auto_freeze() ->
|
||||
Reporter1 = create_test_user(user),
|
||||
Reporter2 = create_test_user(user),
|
||||
Reporter3 = create_test_user(user),
|
||||
OwnerId = create_test_user(user),
|
||||
Reporter1 = create_test_user(),
|
||||
Reporter2 = create_test_user(),
|
||||
Reporter3 = create_test_user(),
|
||||
OwnerId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
{ok, _} = logic_moderation:create_report(Reporter1, event, EventId, <<"">>),
|
||||
@@ -128,8 +111,8 @@ test_auto_freeze() ->
|
||||
?assertEqual(frozen, Event#event.status).
|
||||
|
||||
test_freeze_calendar() ->
|
||||
AdminId = create_test_user(admin),
|
||||
OwnerId = create_test_user(user),
|
||||
AdminId = create_test_admin(),
|
||||
OwnerId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
{ok, Frozen} = logic_moderation:freeze_calendar(AdminId, CalendarId),
|
||||
?assertEqual(frozen, Frozen#calendar.status),
|
||||
@@ -137,8 +120,8 @@ test_freeze_calendar() ->
|
||||
?assertEqual(active, Unfrozen#calendar.status).
|
||||
|
||||
test_freeze_event() ->
|
||||
AdminId = create_test_user(admin),
|
||||
OwnerId = create_test_user(user),
|
||||
AdminId = create_test_admin(),
|
||||
OwnerId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
{ok, Frozen} = logic_moderation:freeze_event(AdminId, EventId),
|
||||
@@ -147,9 +130,9 @@ test_freeze_event() ->
|
||||
?assertEqual(active, Unfrozen#event.status).
|
||||
|
||||
test_check_content() ->
|
||||
AdminId = create_test_user(admin),
|
||||
AdminId = create_test_admin(),
|
||||
{ok, _} = logic_moderation:add_banned_word(AdminId, <<"bad">>),
|
||||
?assertNot(logic_moderation:check_content(<<"Hello">>)),
|
||||
?assert(logic_moderation:check_content(<<"This is bad">>)),
|
||||
?assertEqual(<<"Hello">>, logic_moderation:auto_moderate(<<"Hello">>)),
|
||||
?assertEqual(<<"This is ***">>, logic_moderation:auto_moderate(<<"This is bad">>)).
|
||||
?assertEqual(<<"This is ***">>, logic_moderation:auto_moderate(<<"This is bad">>)).
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [booking, calendar, event]).
|
||||
|
||||
setup() ->
|
||||
pg:start_link(),
|
||||
mnesia:start(),
|
||||
mnesia:create_table(booking, [{attributes, record_info(fields, booking)}, {ram_copies, [node()]}]),
|
||||
mnesia:create_table(calendar, [{attributes, record_info(fields, calendar)}, {ram_copies, [node()]}]),
|
||||
mnesia:create_table(event, [{attributes, record_info(fields, event)}, {ram_copies, [node()]}]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(event),
|
||||
mnesia:delete_table(calendar),
|
||||
mnesia:delete_table(booking),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
logic_notification_test_() ->
|
||||
@@ -66,7 +64,7 @@ create_test_event() ->
|
||||
title = <<"Test Event">>,
|
||||
description = <<"">>,
|
||||
event_type = single,
|
||||
start_time = {{2026, 6, 1}, {10, 0, 0}},
|
||||
start_time = eh_test_support:future_start(),
|
||||
duration = 60,
|
||||
recurrence_rule = undefined,
|
||||
master_id = undefined,
|
||||
@@ -89,7 +87,6 @@ test_notify_booking() ->
|
||||
Booking = create_test_booking(),
|
||||
UserId = <<"user123">>,
|
||||
|
||||
% Функция возвращает список пидов (может быть пустым)
|
||||
Result = logic_notification:notify_booking(UserId, Booking),
|
||||
?assert(is_list(Result)).
|
||||
|
||||
@@ -105,4 +102,4 @@ test_notify_event_update() ->
|
||||
|
||||
test_notify_admin() ->
|
||||
Result = logic_notification:notify_admin(report_created, #{report_id => <<"rep123">>}),
|
||||
?assertEqual(ok, Result).
|
||||
?assertEqual(ok, Result).
|
||||
|
||||
@@ -2,22 +2,16 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [user, admin, calendar, event, booking, review]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(user, [{attributes, record_info(fields, user)}, {ram_copies, [node()]}]),
|
||||
mnesia:create_table(calendar, [{attributes, record_info(fields, calendar)}, {ram_copies, [node()]}]),
|
||||
mnesia:create_table(event, [{attributes, record_info(fields, event)}, {ram_copies, [node()]}]),
|
||||
mnesia:create_table(booking, [{attributes, record_info(fields, booking)}, {ram_copies, [node()]}]),
|
||||
mnesia:create_table(review, [{attributes, record_info(fields, review)}, {ram_copies, [node()]}]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(review),
|
||||
mnesia:delete_table(booking),
|
||||
mnesia:delete_table(event),
|
||||
mnesia:delete_table(calendar),
|
||||
mnesia:delete_table(user),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
logic_review_test_() ->
|
||||
@@ -36,29 +30,35 @@ logic_review_test_() ->
|
||||
{"Rating updates target", fun test_rating_updates_target/0}
|
||||
]}.
|
||||
|
||||
create_test_user(Role) ->
|
||||
create_test_user() ->
|
||||
UserId = base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}),
|
||||
User = #user{id = UserId, email = <<UserId/binary, "@test.com">>, password_hash = <<"hash">>,
|
||||
role = Role, status = active, created_at = calendar:universal_time(), updated_at = calendar:universal_time()},
|
||||
User = eh_test_support:make_user(#{id => UserId, email => <<UserId/binary, "@test.com">>}),
|
||||
mnesia:dirty_write(User),
|
||||
UserId.
|
||||
|
||||
create_test_admin() ->
|
||||
AdminId = base64:encode(crypto:strong_rand_bytes(16), #{mode => urlsafe, padding => false}),
|
||||
Admin = eh_test_support:seed_admin(#{id => AdminId, email => <<AdminId/binary, "@admin.test">>}),
|
||||
Admin#admin.id.
|
||||
|
||||
create_test_calendar(OwnerId) ->
|
||||
{ok, Calendar} = core_calendar:create(OwnerId, <<"Test">>, <<"">>, manual),
|
||||
Calendar#calendar.id.
|
||||
|
||||
create_test_event(CalendarId) ->
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Event">>, {{2026, 6, 1}, {10, 0, 0}}, 60),
|
||||
{ok, Event} = core_event:create(CalendarId, <<"Event">>, eh_test_support:future_start(), 60),
|
||||
Event#event.id.
|
||||
|
||||
create_booking(UserId, EventId) ->
|
||||
{ok, Booking} = core_booking:create(EventId, UserId),
|
||||
core_booking:update_status(Booking#booking.id, confirmed),
|
||||
{ok, Booking} = core_booking:create(EventId, UserId, pending),
|
||||
Now = calendar:universal_time(),
|
||||
{ok, _} = core_booking:update(Booking#booking.id,
|
||||
[{status, confirmed}, {confirmed_at, Now}]),
|
||||
Booking#booking.id.
|
||||
|
||||
test_create_event_review() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
OwnerId = create_test_user(),
|
||||
ParticipantId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
create_booking(ParticipantId, EventId),
|
||||
@@ -66,30 +66,29 @@ test_create_event_review() ->
|
||||
{ok, Review} = logic_review:create_review(ParticipantId, event, EventId, 5, <<"Great!">>),
|
||||
?assertEqual(5, Review#review.rating),
|
||||
|
||||
% Проверяем, что рейтинг события обновился
|
||||
{ok, Event} = core_event:get_by_id(EventId),
|
||||
?assertEqual(5.0, Event#event.rating_avg),
|
||||
?assertEqual(1, Event#event.rating_count).
|
||||
|
||||
test_create_calendar_review() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ReviewerId = create_test_user(user),
|
||||
OwnerId = create_test_user(),
|
||||
ReviewerId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
|
||||
{ok, Review} = logic_review:create_review(ReviewerId, calendar, CalendarId, 4, <<"Nice">>),
|
||||
?assertEqual(4, Review#review.rating).
|
||||
|
||||
test_cannot_review_without_booking() ->
|
||||
OwnerId = create_test_user(user),
|
||||
UserId = create_test_user(user),
|
||||
OwnerId = create_test_user(),
|
||||
UserId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
|
||||
{error, cannot_review} = logic_review:create_review(UserId, event, EventId, 5, <<"">>).
|
||||
|
||||
test_cannot_review_twice() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
OwnerId = create_test_user(),
|
||||
ParticipantId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
create_booking(ParticipantId, EventId),
|
||||
@@ -98,8 +97,8 @@ test_cannot_review_twice() ->
|
||||
{error, already_reviewed} = logic_review:create_review(ParticipantId, event, EventId, 4, <<"">>).
|
||||
|
||||
test_update_own_review() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
OwnerId = create_test_user(),
|
||||
ParticipantId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
create_booking(ParticipantId, EventId),
|
||||
@@ -109,9 +108,9 @@ test_update_own_review() ->
|
||||
?assertEqual(5, Updated#review.rating).
|
||||
|
||||
test_cannot_update_others() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
OtherId = create_test_user(user),
|
||||
OwnerId = create_test_user(),
|
||||
ParticipantId = create_test_user(),
|
||||
OtherId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
create_booking(ParticipantId, EventId),
|
||||
@@ -120,8 +119,8 @@ test_cannot_update_others() ->
|
||||
{error, access_denied} = logic_review:update_review(OtherId, Review#review.id, [{rating, 5}]).
|
||||
|
||||
test_delete_own_review() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
OwnerId = create_test_user(),
|
||||
ParticipantId = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
create_booking(ParticipantId, EventId),
|
||||
@@ -130,9 +129,9 @@ test_delete_own_review() ->
|
||||
{ok, deleted} = logic_review:delete_review(ParticipantId, Review#review.id).
|
||||
|
||||
test_admin_hide_review() ->
|
||||
OwnerId = create_test_user(user),
|
||||
ParticipantId = create_test_user(user),
|
||||
AdminId = create_test_user(admin),
|
||||
OwnerId = create_test_user(),
|
||||
ParticipantId = create_test_user(),
|
||||
AdminId = create_test_admin(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
create_booking(ParticipantId, EventId),
|
||||
@@ -142,9 +141,9 @@ test_admin_hide_review() ->
|
||||
?assertEqual(hidden, Hidden#review.status).
|
||||
|
||||
test_rating_updates_target() ->
|
||||
OwnerId = create_test_user(user),
|
||||
P1 = create_test_user(user),
|
||||
P2 = create_test_user(user),
|
||||
OwnerId = create_test_user(),
|
||||
P1 = create_test_user(),
|
||||
P2 = create_test_user(),
|
||||
CalendarId = create_test_calendar(OwnerId),
|
||||
EventId = create_test_event(CalendarId),
|
||||
create_booking(P1, EventId),
|
||||
@@ -158,4 +157,4 @@ test_rating_updates_target() ->
|
||||
{ok, _} = logic_review:create_review(P2, event, EventId, 3, <<"">>),
|
||||
{ok, Event2} = core_event:get_by_id(EventId),
|
||||
?assertEqual(4.0, Event2#event.rating_avg),
|
||||
?assertEqual(2, Event2#event.rating_count).
|
||||
?assertEqual(2, Event2#event.rating_count).
|
||||
|
||||
@@ -2,27 +2,16 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include("records.hrl").
|
||||
|
||||
-define(TABLES, [user, calendar, event]).
|
||||
|
||||
setup() ->
|
||||
mnesia:start(),
|
||||
mnesia:create_table(user, [
|
||||
{attributes, record_info(fields, user)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(calendar, [
|
||||
{attributes, record_info(fields, calendar)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
mnesia:create_table(event, [
|
||||
{attributes, record_info(fields, event)},
|
||||
{ram_copies, [node()]}
|
||||
]),
|
||||
eh_test_support:start_mnesia(),
|
||||
eh_test_support:ensure_tables(?TABLES),
|
||||
ok.
|
||||
|
||||
cleanup(_) ->
|
||||
mnesia:delete_table(event),
|
||||
mnesia:delete_table(calendar),
|
||||
mnesia:delete_table(user),
|
||||
mnesia:stop(),
|
||||
eh_test_support:delete_tables(?TABLES),
|
||||
eh_test_support:stop_mnesia(),
|
||||
ok.
|
||||
|
||||
logic_search_test_() ->
|
||||
@@ -75,89 +64,96 @@ create_test_event(CalendarId, Title, Description, StartTime, Tags, Location) ->
|
||||
{ok, Updated} = core_event:get_by_id(Event#event.id),
|
||||
Updated#event.id.
|
||||
|
||||
add_days(DateTime, Days) ->
|
||||
Sec = calendar:datetime_to_gregorian_seconds(DateTime) + Days * 86400,
|
||||
calendar:gregorian_seconds_to_datetime(Sec).
|
||||
|
||||
events_from({ok, Total, #{<<"events">> := Events}}) ->
|
||||
{Total, Events}.
|
||||
|
||||
calendars_from({ok, Total, #{<<"calendars">> := Calendars}}) ->
|
||||
{Total, Calendars}.
|
||||
|
||||
%% Тесты
|
||||
test_search_events_by_text() ->
|
||||
OwnerId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, personal, []),
|
||||
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
create_test_event(CalendarId, <<"Python Workshop">>, <<"Learn Python">>, StartTime, [], undefined),
|
||||
create_test_event(CalendarId, <<"JavaScript Conference">>, <<"JS for everyone">>, StartTime, [], undefined),
|
||||
create_test_event(CalendarId, <<"Yoga Class">>, <<"Morning yoga">>, StartTime, [], undefined),
|
||||
|
||||
Params = #{},
|
||||
{ok, Total, Results} = logic_search:search(<<"event">>, <<"Python">>, OwnerId, Params),
|
||||
{Total, Results} = events_from(logic_search:search(<<"event">>, <<"Python">>, OwnerId, Params)),
|
||||
?assertEqual(1, Total),
|
||||
?assertEqual(1, length(Results)),
|
||||
|
||||
{ok, Total2, _Results2} = logic_search:search(<<"event">>, <<"conference">>, OwnerId, Params),
|
||||
{Total2, _} = events_from(logic_search:search(<<"event">>, <<"conference">>, OwnerId, Params)),
|
||||
?assertEqual(1, Total2).
|
||||
|
||||
test_search_events_by_tags() ->
|
||||
OwnerId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, personal, []),
|
||||
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
create_test_event(CalendarId, <<"Event 1">>, <<"">>, StartTime, [<<"python">>, <<"workshop">>], undefined),
|
||||
create_test_event(CalendarId, <<"Event 2">>, <<"">>, StartTime, [<<"javascript">>, <<"conference">>], undefined),
|
||||
create_test_event(CalendarId, <<"Event 3">>, <<"">>, StartTime, [<<"python">>, <<"advanced">>], undefined),
|
||||
|
||||
% Поиск по одному тегу
|
||||
Params = #{tags => <<"python">>},
|
||||
{ok, Total, _} = logic_search:search(<<"event">>, undefined, OwnerId, Params),
|
||||
?assertEqual(2, Total), % Event 1 и Event 3
|
||||
{Total, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{tags => <<"python">>})),
|
||||
?assertEqual(2, Total),
|
||||
|
||||
% Поиск по тегу, который есть только у одного события
|
||||
Params2 = #{tags => <<"workshop">>},
|
||||
{ok, Total2, _} = logic_search:search(<<"event">>, undefined, OwnerId, Params2),
|
||||
?assertEqual(1, Total2), % Только Event 1
|
||||
{Total2, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{tags => <<"workshop">>})),
|
||||
?assertEqual(1, Total2),
|
||||
|
||||
% Поиск по тегу, которого нет
|
||||
Params3 = #{tags => <<"nonexistent">>},
|
||||
{ok, Total3, _} = logic_search:search(<<"event">>, undefined, OwnerId, Params3),
|
||||
{Total3, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{tags => <<"nonexistent">>})),
|
||||
?assertEqual(0, Total3).
|
||||
|
||||
test_search_events_by_date() ->
|
||||
OwnerId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, personal, []),
|
||||
|
||||
create_test_event(CalendarId, <<"June Event">>, <<"">>, {{2026, 6, 15}, {10, 0, 0}}, [], undefined),
|
||||
create_test_event(CalendarId, <<"July Event">>, <<"">>, {{2026, 7, 15}, {10, 0, 0}}, [], undefined),
|
||||
create_test_event(CalendarId, <<"August Event">>, <<"">>, {{2026, 8, 15}, {10, 0, 0}}, [], undefined),
|
||||
Base = eh_test_support:future_start(),
|
||||
create_test_event(CalendarId, <<"Near Event">>, <<"">>, Base, [], undefined),
|
||||
create_test_event(CalendarId, <<"Mid Event">>, <<"">>, add_days(Base, 30), [], undefined),
|
||||
create_test_event(CalendarId, <<"Far Event">>, <<"">>, add_days(Base, 60), [], undefined),
|
||||
|
||||
Params = #{from => {{2026, 6, 1}, {0, 0, 0}}, to => {{2026, 6, 30}, {23, 59, 59}}},
|
||||
{ok, Total, _} = logic_search:search(<<"event">>, undefined, OwnerId, Params),
|
||||
Params = #{from => add_days(Base, -1), to => add_days(Base, 7)},
|
||||
{Total, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, Params)),
|
||||
?assertEqual(1, Total).
|
||||
|
||||
test_search_events_by_location() ->
|
||||
OwnerId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, personal, []),
|
||||
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
MoscowLoc = #location{address = <<"Moscow">>, lat = 55.7558, lon = 37.6173},
|
||||
SpbLoc = #location{address = <<"SPb">>, lat = 59.9343, lon = 30.3351},
|
||||
|
||||
create_test_event(CalendarId, <<"Moscow Event">>, <<"">>, StartTime, [], MoscowLoc),
|
||||
create_test_event(CalendarId, <<"SPb Event">>, <<"">>, StartTime, [], SpbLoc),
|
||||
|
||||
Params = #{lat => 55.7558, lon => 37.6173, radius => 10},
|
||||
{ok, Total, _Results} = logic_search:search(<<"event">>, undefined, OwnerId, Params),
|
||||
{Total, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
|
||||
#{lat => 55.7558, lon => 37.6173, radius => 10})),
|
||||
?assertEqual(1, Total),
|
||||
|
||||
Params2 = #{lat => 59.9343, lon => 30.3351, radius => 10},
|
||||
{ok, Total2, _} = logic_search:search(<<"event">>, undefined, OwnerId, Params2),
|
||||
{Total2, _} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
|
||||
#{lat => 59.9343, lon => 30.3351, radius => 10})),
|
||||
?assertEqual(1, Total2).
|
||||
|
||||
test_combined_search() ->
|
||||
OwnerId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, personal, []),
|
||||
|
||||
StartTime = {{2026, 6, 15}, {10, 0, 0}},
|
||||
create_test_event(CalendarId, <<"Python Workshop">>, <<"Learn Python">>, StartTime, [<<"python">>, <<"workshop">>], undefined),
|
||||
create_test_event(CalendarId, <<"Python Advanced">>, <<"Advanced Python">>, {{2026, 7, 15}, {10, 0, 0}}, [<<"python">>, <<"advanced">>], undefined),
|
||||
Base = eh_test_support:future_start(),
|
||||
create_test_event(CalendarId, <<"Python Workshop">>, <<"Learn Python">>, Base,
|
||||
[<<"python">>, <<"workshop">>], undefined),
|
||||
create_test_event(CalendarId, <<"Python Advanced">>, <<"Advanced Python">>, add_days(Base, 30),
|
||||
[<<"python">>, <<"advanced">>], undefined),
|
||||
|
||||
Params = #{from => {{2026, 6, 1}, {0, 0, 0}}, to => {{2026, 6, 30}, {23, 59, 59}}, tags => <<"python">>},
|
||||
{ok, Total, _} = logic_search:search(<<"event">>, <<"Workshop">>, OwnerId, Params),
|
||||
Params = #{from => add_days(Base, -1), to => add_days(Base, 7), tags => <<"python">>},
|
||||
{Total, _} = events_from(logic_search:search(<<"event">>, <<"Workshop">>, OwnerId, Params)),
|
||||
?assertEqual(1, Total).
|
||||
|
||||
test_search_calendars() ->
|
||||
@@ -165,60 +161,57 @@ test_search_calendars() ->
|
||||
create_test_calendar(OwnerId, personal, [<<"tech">>, <<"programming">>]),
|
||||
create_test_calendar(OwnerId, commercial, [<<"yoga">>, <<"wellness">>]),
|
||||
|
||||
Params = #{tags => <<"tech">>},
|
||||
{ok, Total, _} = logic_search:search(<<"calendar">>, undefined, OwnerId, Params),
|
||||
{Total, _} = calendars_from(logic_search:search(<<"calendar">>, undefined, OwnerId, #{tags => <<"tech">>})),
|
||||
?assertEqual(1, Total),
|
||||
|
||||
{ok, Total2, _} = logic_search:search(<<"calendar">>, <<"Calendar">>, OwnerId, #{}),
|
||||
{Total2, _} = calendars_from(logic_search:search(<<"calendar">>, <<"Calendar">>, OwnerId, #{})),
|
||||
?assertEqual(2, Total2).
|
||||
|
||||
test_search_all() ->
|
||||
OwnerId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, personal, []),
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
create_test_event(CalendarId, <<"Test Event">>, <<"">>, StartTime, [], undefined),
|
||||
|
||||
{ok, Total, Results} = logic_search:search(undefined, <<"Test">>, OwnerId, #{}),
|
||||
?assert(Total >= 2),
|
||||
?assert(maps:is_key(events, Results)),
|
||||
?assert(maps:is_key(calendars, Results)).
|
||||
?assert(maps:is_key(<<"events">>, Results)),
|
||||
?assert(maps:is_key(<<"calendars">>, Results)).
|
||||
|
||||
test_pagination() ->
|
||||
OwnerId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, personal, []),
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
|
||||
lists:foreach(fun(I) ->
|
||||
Title = iolist_to_binary(["Event ", integer_to_binary(I)]),
|
||||
create_test_event(CalendarId, Title, <<"">>, StartTime, [], undefined)
|
||||
end, [1, 2, 3, 4, 5]),
|
||||
|
||||
Params = #{limit => 2, offset => 0},
|
||||
{ok, Total, Results} = logic_search:search(<<"event">>, undefined, OwnerId, Params),
|
||||
{Total, Results} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{limit => 2, offset => 0})),
|
||||
?assertEqual(5, Total),
|
||||
?assertEqual(2, length(Results)),
|
||||
|
||||
Params2 = #{limit => 2, offset => 2},
|
||||
{ok, _, Results2} = logic_search:search(<<"event">>, undefined, OwnerId, Params2),
|
||||
{_, Results2} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{limit => 2, offset => 2})),
|
||||
?assertEqual(2, length(Results2)),
|
||||
|
||||
Params3 = #{limit => 2, offset => 4},
|
||||
{ok, _, Results3} = logic_search:search(<<"event">>, undefined, OwnerId, Params3),
|
||||
{_, Results3} = events_from(logic_search:search(<<"event">>, undefined, OwnerId, #{limit => 2, offset => 4})),
|
||||
?assertEqual(1, length(Results3)).
|
||||
|
||||
test_sorting() ->
|
||||
OwnerId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, personal, []),
|
||||
|
||||
create_test_event(CalendarId, <<"Early">>, <<"">>, {{2026, 6, 1}, {10, 0, 0}}, [], undefined),
|
||||
create_test_event(CalendarId, <<"Late">>, <<"">>, {{2026, 6, 30}, {10, 0, 0}}, [], undefined),
|
||||
Base = eh_test_support:future_start(),
|
||||
create_test_event(CalendarId, <<"Early">>, <<"">>, Base, [], undefined),
|
||||
create_test_event(CalendarId, <<"Late">>, <<"">>, add_days(Base, 29), [], undefined),
|
||||
|
||||
Params = #{sort => <<"start_time">>, order => <<"asc">>},
|
||||
{ok, _, [First | _]} = logic_search:search(<<"event">>, undefined, OwnerId, Params),
|
||||
{_, [First | _]} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
|
||||
#{sort => <<"start_time">>, order => <<"asc">>})),
|
||||
?assertMatch(#{title := <<"Early">>}, First),
|
||||
|
||||
Params2 = #{sort => <<"start_time">>, order => <<"desc">>},
|
||||
{ok, _, [First2 | _]} = logic_search:search(<<"event">>, undefined, OwnerId, Params2),
|
||||
{_, [First2 | _]} = events_from(logic_search:search(<<"event">>, undefined, OwnerId,
|
||||
#{sort => <<"start_time">>, order => <<"desc">>})),
|
||||
?assertMatch(#{title := <<"Late">>}, First2).
|
||||
|
||||
test_access_control() ->
|
||||
@@ -228,12 +221,11 @@ test_access_control() ->
|
||||
PersonalId = create_test_calendar(OwnerId, personal, []),
|
||||
CommercialId = create_test_calendar(OwnerId, commercial, []),
|
||||
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
create_test_event(PersonalId, <<"Personal Event">>, <<"">>, StartTime, [], undefined),
|
||||
create_test_event(CommercialId, <<"Public Event">>, <<"">>, StartTime, [], undefined),
|
||||
|
||||
% Другой пользователь видит только коммерческие события
|
||||
{ok, Total, Results} = logic_search:search(<<"event">>, undefined, OtherId, #{}),
|
||||
{Total, Results} = events_from(logic_search:search(<<"event">>, undefined, OtherId, #{})),
|
||||
?assertEqual(1, Total),
|
||||
[Event] = Results,
|
||||
?assertMatch(#{title := <<"Public Event">>}, Event).
|
||||
@@ -241,9 +233,9 @@ test_access_control() ->
|
||||
test_empty_search() ->
|
||||
OwnerId = create_test_user(user),
|
||||
CalendarId = create_test_calendar(OwnerId, personal, []),
|
||||
StartTime = {{2026, 6, 1}, {10, 0, 0}},
|
||||
StartTime = eh_test_support:future_start(),
|
||||
create_test_event(CalendarId, <<"Test">>, <<"">>, StartTime, [], undefined),
|
||||
|
||||
{ok, Total, Results} = logic_search:search(<<"event">>, <<"nonexistent">>, OwnerId, #{}),
|
||||
{Total, Results} = events_from(logic_search:search(<<"event">>, <<"nonexistent">>, OwnerId, #{})),
|
||||
?assertEqual(0, Total),
|
||||
?assertEqual([], Results).
|
||||
?assertEqual([], Results).
|
||||
|
||||
@@ -25,7 +25,12 @@ ws_handler_test_() ->
|
||||
|
||||
test_websocket_info_booking() ->
|
||||
State = #state{user_id = <<"user123">>, subscriptions = []},
|
||||
Data = #{booking_id => <<"book123">>, event_id => <<"ev123">>, status => confirmed},
|
||||
Data = #{
|
||||
user_id => <<"user123">>,
|
||||
booking_id => <<"book123">>,
|
||||
event_id => <<"ev123">>,
|
||||
status => confirmed
|
||||
},
|
||||
Msg = {notification, booking_update, Data},
|
||||
|
||||
case ws_handler:websocket_info(Msg, State) of
|
||||
@@ -58,4 +63,4 @@ test_websocket_info_event() ->
|
||||
Decoded = jsx:decode(Reply, [return_maps]),
|
||||
?assertEqual(<<"event_update">>, maps:get(<<"type">>, Decoded));
|
||||
_ -> ?assert(false, "Expected reply")
|
||||
end.
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user