%%%------------------------------------------------------------------- %%% @doc Тесты административного API для управления тикетами. %%% %%% Покрывает эндпоинты: %%% GET /v1/admin/tickets %%% GET /v1/admin/tickets/:id %%% PUT /v1/admin/tickets/:id %%% DELETE /v1/admin/tickets/:id %%% %%% Проверяет: %%% - получение списка тикетов %%% - получение тикета по ID %%% - разрешение (resolve) и закрытие (close) тикета %%% - назначение исполнителя %%% - удаление тикета %%% - фильтрацию по статусу %%% - пагинацию %%% @end %%%------------------------------------------------------------------- -module(admin_tickets_tests). -include_lib("eunit/include/eunit.hrl"). -export([test/0]). %%%=================================================================== %%% Главная тестовая функция %%%=================================================================== -spec test() -> ok. test() -> ct:pal("=== Admin Tickets Tests ==="), Token = api_test_runner:get_admin_token(), UserToken = api_test_runner:get_user_token(), % Создаём два тикета для разных проверок Ticket1 = api_test_runner:client_post(<<"/v1/tickets">>, UserToken, #{<<"error_message">> => <<"Test bug">>, <<"stacktrace">> => <<"trace">>}), #{<<"id">> := Ticket1Id} = Ticket1, Ticket2 = api_test_runner:client_post(<<"/v1/tickets">>, UserToken, #{<<"error_message">> => <<"Another bug">>, <<"stacktrace">> => <<"trace2">>}), #{<<"id">> := Ticket2Id} = Ticket2, test_list_tickets(Token), test_get_ticket(Token, Ticket1Id), test_resolve_ticket(Token, Ticket1Id), test_close_ticket(Token, Ticket1Id), test_assign_ticket(Token, Ticket1Id), test_filter_tickets(Token), test_ticket_pagination(Token, Ticket2Id), test_delete_ticket(Token, Ticket1Id), test_delete_ticket(Token, Ticket2Id), ct:pal("=== All admin tickets tests passed ==="), ok. %%%=================================================================== %%% Тестовые функции %%%=================================================================== %% @doc GET /v1/admin/tickets – проверяет получение списка тикетов. %% Убеждается, что список не пуст. -spec test_list_tickets(binary()) -> ok. test_list_tickets(Token) -> ct:pal(" TEST: List all tickets"), Tickets = api_test_runner:admin_get(<<"/v1/admin/tickets">>, Token), ?assert(is_list(Tickets)), ?assert(length(Tickets) >= 1), ct:pal(" OK: ~p tickets", [length(Tickets)]). %% @doc GET /v1/admin/tickets/:id – проверяет получение тикета по ID. %% Убеждается, что статус нового тикета – open. -spec test_get_ticket(binary(), binary()) -> ok. test_get_ticket(Token, TicketId) -> ct:pal(" TEST: Get ticket by ID"), Path = <<"/v1/admin/tickets/", TicketId/binary>>, Ticket = api_test_runner:admin_get(Path, Token), ?assertEqual(TicketId, maps:get(<<"id">>, Ticket)), ?assertEqual(<<"open">>, maps:get(<<"status">>, Ticket)), ct:pal(" OK"). %% @doc PUT /v1/admin/tickets/:id – разрешение тикета (resolve). %% Ожидается статус resolved после успешного выполнения. -spec test_resolve_ticket(binary(), binary()) -> ok. test_resolve_ticket(Token, TicketId) -> ct:pal(" TEST: Resolve ticket"), Path = <<"/v1/admin/tickets/", TicketId/binary>>, Updated = api_test_runner:admin_put(Path, Token, #{ <<"status">> => <<"resolved">>, <<"resolution_note">> => <<"Fixed">> }), ?assertEqual(<<"resolved">>, maps:get(<<"status">>, Updated)), ?assertEqual(<<"Fixed">>, maps:get(<<"resolution_note">>, Updated)), ct:pal(" OK"). %% @doc PUT /v1/admin/tickets/:id – закрытие тикета (close). -spec test_close_ticket(binary(), binary()) -> ok. test_close_ticket(Token, TicketId) -> ct:pal(" TEST: Close ticket"), Path = <<"/v1/admin/tickets/", TicketId/binary>>, Updated = api_test_runner:admin_put(Path, Token, #{<<"status">> => <<"closed">>}), ?assertEqual(<<"closed">>, maps:get(<<"status">>, Updated)), ct:pal(" OK"). %% @doc PUT /v1/admin/tickets/:id – назначение исполнителя. -spec test_assign_ticket(binary(), binary()) -> ok. test_assign_ticket(Token, TicketId) -> ct:pal(" TEST: Assign ticket"), Me = api_test_runner:admin_get(<<"/v1/admin/me">>, Token), AdminId = maps:get(<<"id">>, Me), Path = <<"/v1/admin/tickets/", TicketId/binary>>, Updated = api_test_runner:admin_put(Path, Token, #{<<"assigned_to">> => AdminId}), ?assertEqual(AdminId, maps:get(<<"assigned_to">>, Updated)), ct:pal(" OK"). %% @doc DELETE /v1/admin/tickets/:id – удаление тикета. -spec test_delete_ticket(binary(), binary()) -> ok. test_delete_ticket(Token, TicketId) -> ct:pal(" TEST: Delete ticket"), Path = <<"/v1/admin/tickets/", TicketId/binary>>, Deleted = api_test_runner:admin_delete(Path, Token), ?assertEqual(<<"deleted">>, maps:get(<<"status">>, Deleted)), ct:pal(" OK"). %% @doc GET /v1/admin/tickets?status=... – проверяет фильтрацию по статусу open. %% Использует второй тикет, который всё ещё open. -spec test_filter_tickets(binary()) -> ok. test_filter_tickets(Token) -> ct:pal(" TEST: Filter tickets by status=open"), Tickets = api_test_runner:admin_get(<<"/v1/admin/tickets?status=open">>, Token), ?assert(is_list(Tickets)), ?assert(length(Tickets) >= 1), [?assertEqual(<<"open">>, maps:get(<<"status">>, T)) || T <- Tickets], ct:pal(" OK: ~p open tickets", [length(Tickets)]). %% @doc GET /v1/admin/tickets?limit=...&offset=... – проверяет пагинацию. %% Использует второй тикет как гарантированно существующий. -spec test_ticket_pagination(binary(), binary()) -> ok. test_ticket_pagination(Token, _TicketId) -> ct:pal(" TEST: Ticket pagination"), Page1 = api_test_runner:admin_get(<<"/v1/admin/tickets?limit=1&offset=0">>, Token), ?assert(length(Page1) >= 1), Page2 = api_test_runner:admin_get(<<"/v1/admin/tickets?limit=1&offset=1">>, Token), ?assert(length(Page2) >= 0), case {Page1, Page2} of {[First|_], [Second|_]} -> Id1 = maps:get(<<"id">>, First), Id2 = maps:get(<<"id">>, Second), ?assertNotEqual(Id1, Id2); _ -> ok end, ct:pal(" OK").