182 lines
7.8 KiB
Erlang
182 lines
7.8 KiB
Erlang
%%%-------------------------------------------------------------------
|
||
%%% @doc Тесты клиентского API для работы с тикетами.
|
||
%%%
|
||
%%% Покрывает эндпоинты:
|
||
%%% POST /v1/tickets
|
||
%%% GET /v1/tickets
|
||
%%% GET /v1/tickets/:id
|
||
%%%
|
||
%%% Проверяет:
|
||
%%% - успешное создание тикета
|
||
%%% - получение списка своих тикетов
|
||
%%% - получение конкретного тикета по ID
|
||
%%% - ошибку 400 при отсутствии обязательных полей
|
||
%%% - ошибку 401 без токена
|
||
%%% - ошибку 403 при попытке доступа к чужому тикету
|
||
%%% @end
|
||
%%%-------------------------------------------------------------------
|
||
-module(user_tickets_tests).
|
||
-include_lib("eunit/include/eunit.hrl").
|
||
|
||
-export([test/0]).
|
||
|
||
%%%===================================================================
|
||
%%% Главная тестовая функция
|
||
%%%===================================================================
|
||
|
||
-spec test() -> ok.
|
||
test() ->
|
||
ct:pal("=== User Tickets Tests ==="),
|
||
Token = api_test_runner:get_user_token(),
|
||
StrangerEmail = api_test_runner:unique_email(<<"stranger">>),
|
||
StrangerToken = api_test_runner:register_and_login(StrangerEmail, <<"pass">>),
|
||
|
||
% Создаём тикет
|
||
#{<<"id">> := TicketId} = api_test_runner:client_post(<<"/v1/tickets">>, Token,
|
||
#{error_message => <<"Something broke">>, stacktrace => <<"line 42">>}),
|
||
|
||
test_create_ticket(Token),
|
||
test_create_ticket_dedupe(Token),
|
||
test_create_ticket_manual(Token),
|
||
test_create_ticket_missing_fields(Token),
|
||
test_create_ticket_unauthorized(),
|
||
test_list_tickets(Token, TicketId),
|
||
test_list_tickets_unauthorized(),
|
||
test_get_ticket(Token, TicketId),
|
||
test_get_ticket_forbidden(StrangerToken, TicketId),
|
||
test_get_ticket_not_found(Token),
|
||
test_get_ticket_unauthorized(TicketId),
|
||
|
||
ct:pal("=== All user tickets tests passed ==="),
|
||
ok.
|
||
%%%===================================================================
|
||
%%% Тестовые функции
|
||
%%%===================================================================
|
||
|
||
%% @doc Успешное создание тикета: 201 Created.
|
||
-spec test_create_ticket(binary()) -> ok.
|
||
test_create_ticket(Token) ->
|
||
ct:pal(" TEST: Create a ticket"),
|
||
Resp = api_test_runner:client_request(post, <<"/v1/tickets">>, Token,
|
||
jsx:encode(#{
|
||
error_message => <<"Test bug">>,
|
||
stacktrace => <<"trace">>,
|
||
source => <<"frontend">>,
|
||
context => #{route => <<"/test">>, build => <<"dev">>}
|
||
})),
|
||
{ok, 201, _, Body} = Resp,
|
||
Decoded = jsx:decode(list_to_binary(Body), [return_maps]),
|
||
#{<<"id">> := Id, <<"status">> := Status, <<"source">> := Source, <<"error_hash">> := Hash} = Decoded,
|
||
?assert(is_binary(Id)),
|
||
?assertEqual(<<"open">>, Status),
|
||
?assertEqual(<<"frontend">>, Source),
|
||
?assert(is_binary(Hash)),
|
||
?assert(byte_size(Hash) > 0),
|
||
ct:pal(" OK: ticket ~s created", [Id]).
|
||
|
||
%% @doc Повторный POST с тем же сообщением увеличивает count.
|
||
-spec test_create_ticket_dedupe(binary()) -> ok.
|
||
test_create_ticket_dedupe(Token) ->
|
||
ct:pal(" TEST: Dedupe ticket by hash"),
|
||
Payload = jsx:encode(#{
|
||
error_message => <<"Dedupe me">>,
|
||
stacktrace => <<"same stack">>,
|
||
source => <<"frontend">>
|
||
}),
|
||
{ok, 201, _, Body1} = api_test_runner:client_request(post, <<"/v1/tickets">>, Token, Payload),
|
||
#{<<"id">> := Id1, <<"count">> := Count1} = jsx:decode(list_to_binary(Body1), [return_maps]),
|
||
{ok, 201, _, Body2} = api_test_runner:client_request(post, <<"/v1/tickets">>, Token, Payload),
|
||
#{<<"id">> := Id2, <<"count">> := Count2} = jsx:decode(list_to_binary(Body2), [return_maps]),
|
||
?assertEqual(Id1, Id2),
|
||
?assertEqual(Count1 + 1, Count2),
|
||
ct:pal(" OK: count bumped to ~p", [Count2]).
|
||
|
||
%% @doc Ручной клиентский репорт: source=manual без stacktrace.
|
||
-spec test_create_ticket_manual(binary()) -> ok.
|
||
test_create_ticket_manual(Token) ->
|
||
ct:pal(" TEST: Create manual ticket"),
|
||
Resp = api_test_runner:client_request(post, <<"/v1/tickets">>, Token,
|
||
jsx:encode(#{
|
||
error_message => <<"Button does nothing">>,
|
||
source => <<"manual">>,
|
||
context => #{steps => <<"1. Open calendar\n2. Click share">>}
|
||
})),
|
||
{ok, 201, _, Body} = Resp,
|
||
#{<<"source">> := Source, <<"error_hash">> := Hash} =
|
||
jsx:decode(list_to_binary(Body), [return_maps]),
|
||
?assertEqual(<<"manual">>, Source),
|
||
?assert(is_binary(Hash)),
|
||
?assert(byte_size(Hash) > 0),
|
||
ct:pal(" OK: manual ticket created").
|
||
|
||
%% @doc Отсутствие обязательного поля error_message: 400 Bad Request.
|
||
-spec test_create_ticket_missing_fields(binary()) -> ok.
|
||
test_create_ticket_missing_fields(Token) ->
|
||
ct:pal(" TEST: Create ticket without error_message"),
|
||
Resp = api_test_runner:client_request(post, <<"/v1/tickets">>, Token,
|
||
jsx:encode(#{stacktrace => <<"trace">>})),
|
||
?assertMatch({ok, 400, _, _}, Resp),
|
||
ct:pal(" OK: got 400").
|
||
|
||
%% @doc Создание тикета без токена: 401 Unauthorized.
|
||
-spec test_create_ticket_unauthorized() -> ok.
|
||
test_create_ticket_unauthorized() ->
|
||
ct:pal(" TEST: Create ticket without token"),
|
||
Resp = api_test_runner:client_request(post, <<"/v1/tickets">>, <<>>,
|
||
jsx:encode(#{error_message => <<"bug">>})),
|
||
?assertMatch({ok, 401, _, _}, Resp),
|
||
ct:pal(" OK: got 401").
|
||
|
||
%% @doc GET /v1/tickets – получение списка своих тикетов.
|
||
-spec test_list_tickets(binary(), binary()) -> ok.
|
||
test_list_tickets(Token, ExpectedTicketId) ->
|
||
ct:pal(" TEST: List my tickets"),
|
||
Tickets = api_test_runner:client_get(<<"/v1/tickets">>, Token),
|
||
?assert(is_list(Tickets)),
|
||
?assert(length(Tickets) >= 1),
|
||
?assert(lists:any(fun(T) -> maps:get(<<"id">>, T) =:= ExpectedTicketId end, Tickets)),
|
||
ct:pal(" OK: my ticket found").
|
||
|
||
%% @doc GET /v1/tickets без токена: 401 Unauthorized.
|
||
-spec test_list_tickets_unauthorized() -> ok.
|
||
test_list_tickets_unauthorized() ->
|
||
ct:pal(" TEST: List tickets without token"),
|
||
Resp = api_test_runner:client_request(get, <<"/v1/tickets">>, <<>>),
|
||
?assertMatch({ok, 401, _, _}, Resp),
|
||
ct:pal(" OK: got 401").
|
||
|
||
%% @doc GET /v1/tickets/:id – получение своего тикета.
|
||
-spec test_get_ticket(binary(), binary()) -> ok.
|
||
test_get_ticket(Token, TicketId) ->
|
||
ct:pal(" TEST: Get my ticket by ID"),
|
||
Path = <<"/v1/tickets/", TicketId/binary>>,
|
||
Ticket = api_test_runner:client_get(Path, Token),
|
||
?assertEqual(TicketId, maps:get(<<"id">>, Ticket)),
|
||
?assertEqual(<<"Something broke">>, maps:get(<<"error_message">>, Ticket)),
|
||
ct:pal(" OK: got my ticket").
|
||
|
||
%% @doc GET /v1/tickets/:id – попытка доступа к чужому тикету (403).
|
||
-spec test_get_ticket_forbidden(binary(), binary()) -> ok.
|
||
test_get_ticket_forbidden(StrangerToken, TicketId) ->
|
||
ct:pal(" TEST: Get ticket that is not mine"),
|
||
Path = <<"/v1/tickets/", TicketId/binary>>,
|
||
Resp = api_test_runner:client_request(get, Path, StrangerToken),
|
||
?assertMatch({ok, 403, _, _}, Resp),
|
||
ct:pal(" OK: got 403").
|
||
|
||
%% @doc GET /v1/tickets/:id – несуществующий тикет (404).
|
||
-spec test_get_ticket_not_found(binary()) -> ok.
|
||
test_get_ticket_not_found(Token) ->
|
||
ct:pal(" TEST: Get non-existent ticket"),
|
||
Resp = api_test_runner:client_request(get, <<"/v1/tickets/fakeid">>, Token),
|
||
?assertMatch({ok, 404, _, _}, Resp),
|
||
ct:pal(" OK: got 404").
|
||
|
||
%% @doc GET /v1/tickets/:id – без токена (401).
|
||
-spec test_get_ticket_unauthorized(binary()) -> ok.
|
||
test_get_ticket_unauthorized(TicketId) ->
|
||
ct:pal(" TEST: Get ticket without token"),
|
||
Path = <<"/v1/tickets/", TicketId/binary>>,
|
||
Resp = api_test_runner:client_request(get, Path, <<>>),
|
||
?assertMatch({ok, 401, _, _}, Resp),
|
||
ct:pal(" OK: got 401"). |