Current section

Files

Jump to
telega src telega@testing@mock.erl
Raw

src/telega@testing@mock.erl

-module(telega@testing@mock).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega/testing/mock.gleam").
-export([ok_response/1, bool_response/0, message_response/0, client/0, message_client/0, client_with/1, route/2, route_with_body/2, route_with_response/2, routed_client/1, stateful_client/1, get_calls/1, assert_call_count/2, assert_no_calls/1, assert_called_with_path/2, assert_called_with_body/3]).
-export_type([api_call/0, mock_route/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Mock client and assertion utilities for testing bot API interactions.\n"
"\n"
" ```gleam\n"
" import telega/testing/mock\n"
"\n"
" let #(client, calls) = mock.client()\n"
" // ... use client in your bot setup ...\n"
" mock.assert_call_count(from: calls, expected: 1)\n"
" ```\n"
).
-type api_call() :: {api_call, gleam@http@request:request(binary())}.
-type mock_route() :: {mock_route,
binary(),
fun((gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()})}.
-file("src/telega/testing/mock.gleam", 32).
?DOC(" Wraps a `Json` value into `{\"ok\":true,\"result\":...}` Telegram API response format.\n").
-spec ok_response(gleam@json:json()) -> binary().
ok_response(Result) ->
gleam@json:to_string(
gleam@json:object(
[{<<"ok"/utf8>>, gleam@json:bool(true)},
{<<"result"/utf8>>, Result}]
)
).
-file("src/telega/testing/mock.gleam", 38).
?DOC(
" Returns a `{\"ok\":true,\"result\":true}` response string.\n"
" Use for endpoints that return a boolean (e.g. `answerCallbackQuery`, `deleteMessage`).\n"
).
-spec bool_response() -> binary().
bool_response() ->
ok_response(gleam@json:bool(true)).
-file("src/telega/testing/mock.gleam", 44).
?DOC(
" Returns a JSON string representing a valid `{\"ok\":true,\"result\":{...message...}}` response.\n"
" Useful for building custom mock clients that need to return valid Message responses.\n"
).
-spec message_response() -> binary().
message_response() ->
ok_response(
telega@model@encoder:encode_message(
telega@testing@factory:message(<<""/utf8>>)
)
).
-file("src/telega/testing/mock.gleam", 52).
?DOC(
" Creates a mock Telegram client that records all API calls\n"
" and returns 200 OK with an empty successful response.\n"
" Note: This response will NOT decode as a valid Message.\n"
" Use `message_client()` if your handlers call reply functions.\n"
).
-spec client() -> {telega@client:telegram_client(),
gleam@erlang@process:subject(api_call())}.
client() ->
Calls = gleam@erlang@process:new_subject(),
Fetch_client = fun(Req) ->
gleam@erlang@process:send(Calls, {api_call, Req}),
{ok,
begin
_pipe = gleam@http@response:new(200),
gleam@http@response:set_body(
_pipe,
<<"{\"ok\":true,\"result\":{}}"/utf8>>
)
end}
end,
Telegram_client = telega@client:new(<<"test_token"/utf8>>, Fetch_client),
{Telegram_client, Calls}.
-file("src/telega/testing/mock.gleam", 65).
?DOC(
" Creates a mock Telegram client that records all API calls\n"
" and returns 200 OK with a valid Message response.\n"
" Use this when your handlers call `reply.with_text` or other reply functions.\n"
).
-spec message_client() -> {telega@client:telegram_client(),
gleam@erlang@process:subject(api_call())}.
message_client() ->
Response_body = message_response(),
Calls = gleam@erlang@process:new_subject(),
Fetch_client = fun(Req) ->
gleam@erlang@process:send(Calls, {api_call, Req}),
{ok,
begin
_pipe = gleam@http@response:new(200),
gleam@http@response:set_body(_pipe, Response_body)
end}
end,
Telegram_client = telega@client:new(<<"test_token"/utf8>>, Fetch_client),
{Telegram_client, Calls}.
-file("src/telega/testing/mock.gleam", 77).
?DOC(" Creates a mock client with a custom response handler.\n").
-spec client_with(
fun((gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()})
) -> {telega@client:telegram_client(), gleam@erlang@process:subject(api_call())}.
client_with(Handler) ->
Calls = gleam@erlang@process:new_subject(),
Fetch_client = fun(Req) ->
gleam@erlang@process:send(Calls, {api_call, Req}),
Handler(Req)
end,
Telegram_client = telega@client:new(<<"test_token"/utf8>>, Fetch_client),
{Telegram_client, Calls}.
-file("src/telega/testing/mock.gleam", 104).
?DOC(
" Creates a route that matches requests whose path contains `path_contains`\n"
" and delegates to the given handler function.\n"
).
-spec route(
binary(),
fun((gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()})
) -> mock_route().
route(Path_contains, Handler) ->
{mock_route, Path_contains, Handler}.
-file("src/telega/testing/mock.gleam", 114).
?DOC(
" Creates a route that matches requests whose path contains `path_contains`\n"
" and returns a 200 OK with the given body string.\n"
).
-spec route_with_body(binary(), binary()) -> mock_route().
route_with_body(Path_contains, Body) ->
{mock_route,
Path_contains,
fun(_) ->
{ok,
begin
_pipe = gleam@http@response:new(200),
gleam@http@response:set_body(_pipe, Body)
end}
end}.
-file("src/telega/testing/mock.gleam", 126).
?DOC(
" Creates a route that matches requests whose path contains `path_contains`\n"
" and returns a 200 OK with the given response string (from `ok_response`, `bool_response`, etc.).\n"
" Prefer this over `route_with_body` for type-safe response building.\n"
).
-spec route_with_response(binary(), binary()) -> mock_route().
route_with_response(Path_contains, Response_body) ->
{mock_route,
Path_contains,
fun(_) ->
{ok,
begin
_pipe = gleam@http@response:new(200),
gleam@http@response:set_body(_pipe, Response_body)
end}
end}.
-file("src/telega/testing/mock.gleam", 154).
-spec find_matching_route(
list(mock_route()),
gleam@http@request:request(binary())
) -> {ok, mock_route()} | {error, nil}.
find_matching_route(Routes, Req) ->
gleam@list:find(
Routes,
fun(R) ->
gleam_stdlib:contains_string(
erlang:element(8, Req),
erlang:element(2, R)
)
end
).
-file("src/telega/testing/mock.gleam", 138).
?DOC(
" Creates a mock client that routes requests through a list of `MockRoute`s.\n"
" Routes are checked in order; first match wins. Unmatched requests fall back\n"
" to a default `message_response()`.\n"
).
-spec routed_client(list(mock_route())) -> {telega@client:telegram_client(),
gleam@erlang@process:subject(api_call())}.
routed_client(Routes) ->
Default_body = message_response(),
Calls = gleam@erlang@process:new_subject(),
Fetch_client = fun(Req) ->
gleam@erlang@process:send(Calls, {api_call, Req}),
case find_matching_route(Routes, Req) of
{ok, Matched_route} ->
(erlang:element(3, Matched_route))(Req);
{error, _} ->
{ok,
begin
_pipe = gleam@http@response:new(200),
gleam@http@response:set_body(_pipe, Default_body)
end}
end
end,
Telegram_client = telega@client:new(<<"test_token"/utf8>>, Fetch_client),
{Telegram_client, Calls}.
-file("src/telega/testing/mock.gleam", 167).
?DOC(
" Creates a mock client that passes a 0-based call counter to the handler.\n"
" Useful for returning different responses based on call order.\n"
).
-spec stateful_client(
fun((gleam@http@request:request(binary()), integer()) -> {ok,
gleam@http@response:response(binary())} |
{error, telega@error:telega_error()})
) -> {telega@client:telegram_client(), gleam@erlang@process:subject(api_call())}.
stateful_client(Handler) ->
Calls = gleam@erlang@process:new_subject(),
Counter = gleam@erlang@process:new_subject(),
gleam@erlang@process:send(Counter, 0),
Fetch_client = fun(Req) ->
N@1 = case gleam@erlang@process:'receive'(Counter, 1000) of
{ok, N} -> N;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"telega/testing/mock"/utf8>>,
function => <<"stateful_client"/utf8>>,
line => 175,
value => _assert_fail,
start => 6324,
'end' => 6373,
pattern_start => 6335,
pattern_end => 6340})
end,
gleam@erlang@process:send(Counter, N@1 + 1),
gleam@erlang@process:send(Calls, {api_call, Req}),
Handler(Req, N@1)
end,
Telegram_client = telega@client:new(<<"test_token"/utf8>>, Fetch_client),
{Telegram_client, Calls}.
-file("src/telega/testing/mock.gleam", 189).
-spec drain_calls(gleam@erlang@process:subject(api_call()), list(api_call())) -> list(api_call()).
drain_calls(Subject, Acc) ->
case gleam@erlang@process:'receive'(Subject, 0) of
{ok, Call} ->
drain_calls(Subject, [Call | Acc]);
{error, _} ->
lists:reverse(Acc)
end.
-file("src/telega/testing/mock.gleam", 185).
?DOC(" Drains all recorded API calls from the subject.\n").
-spec get_calls(gleam@erlang@process:subject(api_call())) -> list(api_call()).
get_calls(Subject) ->
drain_calls(Subject, []).
-file("src/telega/testing/mock.gleam", 197).
?DOC(" Asserts the number of recorded API calls equals the expected count.\n").
-spec assert_call_count(gleam@erlang@process:subject(api_call()), integer()) -> list(api_call()).
assert_call_count(Subject, Expected) ->
Calls = get_calls(Subject),
Count = erlang:length(Calls),
case Count =:= Expected of
true ->
Calls;
false ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<"Expected "/utf8,
(gleam@string:inspect(Expected))/binary>>/binary,
" API calls, got "/utf8>>/binary,
(gleam@string:inspect(Count))/binary>>),
file => <<?FILEPATH/utf8>>,
module => <<"telega/testing/mock"/utf8>>,
function => <<"assert_call_count"/utf8>>,
line => 206})
end.
-file("src/telega/testing/mock.gleam", 216).
?DOC(" Asserts no API calls were recorded.\n").
-spec assert_no_calls(gleam@erlang@process:subject(api_call())) -> nil.
assert_no_calls(Subject) ->
Calls = get_calls(Subject),
case gleam@list:is_empty(Calls) of
true ->
nil;
false ->
erlang:error(#{gleam_error => panic,
message => (<<"Expected no API calls, got "/utf8,
(gleam@string:inspect(erlang:length(Calls)))/binary>>),
file => <<?FILEPATH/utf8>>,
module => <<"telega/testing/mock"/utf8>>,
function => <<"assert_no_calls"/utf8>>,
line => 221})
end.
-file("src/telega/testing/mock.gleam", 228).
?DOC(" Asserts at least one API call was made to a path containing the given string.\n").
-spec assert_called_with_path(
gleam@erlang@process:subject(api_call()),
binary()
) -> api_call().
assert_called_with_path(Subject, Path_contains) ->
Calls = get_calls(Subject),
case gleam@list:find(
Calls,
fun(Call) ->
gleam_stdlib:contains_string(
erlang:element(8, erlang:element(2, Call)),
Path_contains
)
end
) of
{ok, Call@1} ->
Call@1;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<"No API call found with path containing '"/utf8,
Path_contains/binary>>/binary,
"'. Calls: "/utf8>>/binary,
(gleam@string:inspect(Calls))/binary>>),
file => <<?FILEPATH/utf8>>,
module => <<"telega/testing/mock"/utf8>>,
function => <<"assert_called_with_path"/utf8>>,
line => 240})
end.
-file("src/telega/testing/mock.gleam", 251).
?DOC(
" Asserts at least one API call was made to a path containing `path_contains`\n"
" and whose request body contains `body_contains`.\n"
).
-spec assert_called_with_body(
gleam@erlang@process:subject(api_call()),
binary(),
binary()
) -> api_call().
assert_called_with_body(Subject, Path_contains, Body_contains) ->
Calls = get_calls(Subject),
case gleam@list:find(
Calls,
fun(Call) ->
gleam_stdlib:contains_string(
erlang:element(8, erlang:element(2, Call)),
Path_contains
)
andalso gleam_stdlib:contains_string(
erlang:element(4, erlang:element(2, Call)),
Body_contains
)
end
) of
{ok, Call@1} ->
Call@1;
{error, _} ->
erlang:error(#{gleam_error => panic,
message => (<<<<<<<<<<"No API call found with path containing '"/utf8,
Path_contains/binary>>/binary,
"' and body containing '"/utf8>>/binary,
Body_contains/binary>>/binary,
"'. Calls: "/utf8>>/binary,
(gleam@string:inspect(Calls))/binary>>),
file => <<?FILEPATH/utf8>>,
module => <<"telega/testing/mock"/utf8>>,
function => <<"assert_called_with_body"/utf8>>,
line => 265})
end.